1+ /******************************************************************************
2+ * Project Name: EmberIoT
3+ *
4+ * Ember IoT is a simple proof of concept for a Firebase-hosted IoT
5+ * cloud designed to work with Arduino-based devices and an Android mobile app.
6+ * It enables microcontrollers to connect to the cloud, sync data,
7+ * and interact with a mobile interface using Firebase Authentication and
8+ * Firebase Realtime Database services. This project simplifies creating IoT
9+ * infrastructure without the need for a dedicated server.
10+ *
11+ * Copyright (c) 2025 davirxavier
12+ *
13+ * MIT License
14+ *
15+ * Permission is hereby granted, free of charge, to any person obtaining a copy
16+ * of this software and associated documentation files (the "Software"), to deal
17+ * in the Software without restriction, including without limitation the rights
18+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19+ * copies of the Software, and to permit persons to whom the Software is
20+ * furnished to do so, subject to the following conditions:
21+ *
22+ * The above copyright notice and this permission notice shall be included in all
23+ * copies or substantial portions of the Software.
24+ *
25+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31+ * SOFTWARE.
32+ *****************************************************************************/
33+
34+ #ifndef FIREPROP_CRYPTUTIL_H
35+ #define FIREPROP_CRYPTUTIL_H
36+
37+ #include <Arduino.h>
38+
39+ #ifdef ESP32
40+ extern "C" {
41+ #include <mbedtls/sha256.h>
42+ #include <mbedtls/pk.h>
43+ #include <mbedtls/ctr_drbg.h>
44+ #include <mbedtls/entropy.h>
45+ #include <mbedtls/base64.h>
46+ }
47+ #elif ESP8266
48+ #include <BearSSLHelpers.h>
49+ #include <Ember_BearSSL_RSA.h>
50+ #endif
51+
52+ #ifdef ESP8266
53+ static const char B64_TABLE [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
54+
55+ inline size_t base64_encode (const uint8_t * data , size_t len , unsigned char * out , size_t out_size , size_t * written ) {
56+ // Each 3 bytes → 4 chars, plus padding. Total length = 4 * ceil(len/3)
57+ size_t needed = 4 * ((len + 2 ) / 3 );
58+ if (out_size < needed + 1 ) return -1 ; // +1 for null terminator
59+
60+ size_t i = 0 , o = 0 ;
61+ // Full 3‑byte chunks
62+ for (; i + 2 < len ; i += 3 ) {
63+ uint32_t v = (data [i ] << 16 ) | (data [i + 1 ] << 8 ) | data [i + 2 ];
64+ out [o ++ ] = B64_TABLE [(v >> 18 ) & 0x3F ];
65+ out [o ++ ] = B64_TABLE [(v >> 12 ) & 0x3F ];
66+ out [o ++ ] = B64_TABLE [(v >> 6 ) & 0x3F ];
67+ out [o ++ ] = B64_TABLE [v & 0x3F ];
68+ }
69+ // Remainder
70+ if (i < len ) {
71+ int rem = len - i ;
72+ uint32_t v = data [i ] << 16 ;
73+ if (rem == 2 ) v |= data [i + 1 ] << 8 ;
74+ out [o ++ ] = B64_TABLE [(v >> 18 ) & 0x3F ];
75+ out [o ++ ] = B64_TABLE [(v >> 12 ) & 0x3F ];
76+ out [o ++ ] = (rem == 2 ) ? B64_TABLE [(v >> 6 ) & 0x3F ] : '=' ;
77+ out [o ++ ] = '=' ;
78+ }
79+ out [o ] = '\0' ;
80+ * written = o ;
81+ return 0 ;
82+ }
83+ #endif
84+
85+ inline int base64urlencode (char * arr )
86+ {
87+ size_t length = strlen (arr );
88+ for (size_t i = 0 ; i < length ; i ++ )
89+ {
90+ if (arr [i ] == '+' )
91+ {
92+ arr [i ] = '-' ;
93+ }
94+ else if (arr [i ] == '/' )
95+ {
96+ arr [i ] = '_' ;
97+ }
98+
99+ if (arr [i ] == '=' )
100+ {
101+ return length - i ;
102+ }
103+ }
104+
105+ return 0 ;
106+ }
107+
108+ inline int base64encode (unsigned char * dst , size_t dlen , size_t * olen , const unsigned char * src , size_t slen )
109+ {
110+ #ifdef ESP32
111+ return mbedtls_base64_encode ((unsigned char * ) dst , dlen , olen , src , slen );
112+ #elif ESP8266
113+ return base64_encode (src , slen , dst , dlen , olen );
114+ #endif
115+ }
116+
117+ inline void sha256Hash (const char * input , byte hash [32 ]) {
118+ #ifdef ESP32
119+ mbedtls_md_context_t md_ctx ;
120+ mbedtls_md_init (& md_ctx );
121+ mbedtls_md_setup (& md_ctx , mbedtls_md_info_from_type (MBEDTLS_MD_SHA256 ), 0 );
122+ mbedtls_md_starts (& md_ctx );
123+ mbedtls_md_update (& md_ctx , (const unsigned char * ) input , strlen (input ));
124+ mbedtls_md_finish (& md_ctx , hash );
125+ mbedtls_md_free (& md_ctx );
126+ #elif ESP8266
127+ #endif
128+ }
129+
130+ inline int signRS256 (const char * message , const char * privateKey , char * output , size_t outputSize ) {
131+ #ifdef ESP32
132+ // Prepare mbedTLS contexts
133+ mbedtls_pk_context pk ;
134+ mbedtls_pk_init (& pk );
135+ mbedtls_entropy_context entropy ;
136+ mbedtls_ctr_drbg_context ctr_drbg ;
137+ mbedtls_entropy_init (& entropy );
138+ mbedtls_ctr_drbg_init (& ctr_drbg );
139+
140+ // Seed the random generator (entropy)
141+ const char * pers = "esp32_jwt" ;
142+
143+
144+ mbedtls_ctr_drbg_seed (& ctr_drbg , mbedtls_entropy_func , & entropy , (const unsigned char * )pers , strlen (pers ));
145+
146+ // Parse the RSA private key (PEM)
147+ #if ESP_ARDUINO_VERSION_MAJOR >= 3
148+ int ret = mbedtls_pk_parse_key (& pk , (const unsigned char * ) privateKey ,strlen (privateKey )+ 1 , nullptr , 0 , mbedtls_ctr_drbg_random , & ctr_drbg );
149+ #else
150+ int ret = mbedtls_pk_parse_key (& pk , (const unsigned char * ) privateKey ,strlen (privateKey )+ 1 , nullptr , 0 );
151+ #endif
152+
153+ if (ret != 0 ) {
154+ Serial .printf ("Failed to parse private key: %d\n" , ret );
155+ return ret ;
156+ }
157+
158+ // Compute SHA-256 hash of the message
159+ uint8_t hash [32 ];
160+ mbedtls_sha256 ((const unsigned char * ) message , strlen (message ), hash , 0 );
161+
162+ // Sign the hash
163+ size_t sig_len = 0 ;
164+ uint8_t sig [512 ]; // enough for RSA-2048
165+ #if ESP_ARDUINO_VERSION_MAJOR >= 3
166+ ret = mbedtls_pk_sign (& pk , MBEDTLS_MD_SHA256 , hash , 32 ,sig , sizeof (sig ), & sig_len , mbedtls_ctr_drbg_random , & ctr_drbg );
167+ #else
168+ ret = mbedtls_pk_sign (& pk , MBEDTLS_MD_SHA256 , hash , 32 ,sig , & sig_len , mbedtls_ctr_drbg_random , & ctr_drbg );
169+ #endif
170+ if (ret != 0 ) {
171+ Serial .printf ("Failed to sign JWT: %d\n" , ret );
172+ return ret ;
173+ }
174+
175+ // Clean up mbedTLS contexts
176+ mbedtls_pk_free (& pk );
177+ mbedtls_ctr_drbg_free (& ctr_drbg );
178+ mbedtls_entropy_free (& entropy );
179+
180+ // Base64url-encode the signature
181+ size_t written = 0 ;
182+ base64encode ((unsigned char * ) output , outputSize , & written , sig , sig_len );
183+ return 0 ;
184+ #elif ESP8266
185+ br_sha256_context sha_ctx ;
186+ br_sha256_init (& sha_ctx );
187+ br_sha256_update (& sha_ctx , message , strlen (message ));
188+ uint8_t hash [32 ];
189+ br_sha256_out (& sha_ctx , hash );
190+
191+ BearSSL ::PrivateKey rsaKey (privateKey );
192+ if (!rsaKey .isRSA ())
193+ {
194+ Serial .println ("Provided key isn't RSA!" );
195+ return -1 ;
196+ }
197+
198+ static const uint8_t SHA256_OID [11 ] = {
199+ 0x06 ,0x09 ,0x60 ,0x86 ,0x48 ,0x01 ,0x65 ,0x03 ,0x04 ,0x02 ,0x01
200+ };
201+
202+ size_t sig_len = (rsaKey .getRSA ()-> n_bitlen + 7 )/8 ;
203+ uint8_t signature [sig_len ];
204+
205+ bool ok = EmberIotRSA ::br_rsa_i31_pkcs1_sign (
206+ SHA256_OID ,
207+ hash ,
208+ sizeof (hash ),
209+ rsaKey .getRSA (),
210+ signature
211+ );
212+
213+ if (!ok ) {
214+ Serial .println ("RSA sign failed" );
215+ return -1 ;
216+ }
217+
218+ size_t written = 0 ;
219+ base64encode ((unsigned char * ) output , outputSize , & written , signature , sig_len );
220+ return 0 ;
221+ #endif
222+ }
223+
224+ #endif
0 commit comments