Skip to content

Commit 4768efa

Browse files
authored
Merge pull request #2 from davirxavier/feature/fcm-notifications
Feature/fcm notifications
2 parents 055af83 + e4be913 commit 4768efa

14 files changed

+4596
-490
lines changed

EmberChannelDefinition.h

Lines changed: 993 additions & 0 deletions
Large diffs are not rendered by default.

EmberIot.h

Lines changed: 258 additions & 124 deletions
Large diffs are not rendered by default.

EmberIotAuth.h

Lines changed: 319 additions & 63 deletions
Large diffs are not rendered by default.

EmberIotCertificates.h

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
1-
//
2-
// Created by xav on 3/28/25.
3-
//
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+
*****************************************************************************/
433

534
#ifndef CERTIFICATES_H
635
#define CERTIFICATES_H
@@ -12,10 +41,7 @@ namespace EmberIotCertificates
1241
{
1342
bool init = false;
1443

15-
#ifdef ESP32
16-
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
17-
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
18-
#elif ESP8266
44+
#if ESP8266
1945
inline X509List cert(google_root_ca);
2046
#endif
2147

@@ -30,7 +56,7 @@ namespace EmberIotCertificates
3056
#endif
3157

3258
#ifdef ESP32
33-
client.setCACertBundle(x509_crt_imported_bundle_bin_start);
59+
client.setCACert(google_root_ca);
3460
#elif ESP8266
3561
client.setTrustAnchors(&cert);
3662
#endif

EmberIotCryptUtil.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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

Comments
 (0)