Skip to content

Commit f9059ba

Browse files
authored
Merge pull request #167 from hoeken/master
Add Optional Support for ESP-IDF Bundled CA Certificates
2 parents 65c9183 + 110b971 commit f9059ba

File tree

5 files changed

+95
-20
lines changed

5 files changed

+95
-20
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,26 @@ $ gzip -c esp32-fota-http-2.bin > esp32-fota-http-2.bin.gz
328328

329329
### Root Certificates
330330

331+
#### Certificate Bundles
332+
333+
Arduino 3.x now supports bundled root certificates, which means 99% of sites (including github.com) will work over https and you don't need to maintain a custom certificate on your firmware.
334+
335+
To enable this functionality, simply call this during your setup:
336+
337+
```C++
338+
esp32FOTA.useBundledCerts();
339+
```
340+
341+
If you are using Platformio / PIOArduino, the certificates are not automatically bundled and you will need to download them from [CURL](https://curl.se/docs/caextract.html).
342+
343+
Save that file to your project root directory and then add this line to your platformio.ini:
344+
345+
```board_build.embed_txtfiles=ca_cert_bundle```
346+
347+
Make sure it is named exactly ca_cert_bundle with no extension and located in the top level of your project.
348+
349+
#### Custom Certificates
350+
331351
Certificates and signatures can be stored in different places: any fs::FS filesystem or progmem as const char*.
332352

333353
Filesystems:

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "esp32FOTA",
3-
"version": "0.2.9",
3+
"version": "0.3.0",
44
"keywords": "firmware, OTA, Over The Air Updates, ArduinoOTA",
55
"description": "Allows for firmware to be updated from a webserver, the device can check for updates at any time. Uses a simple JSON file to outline if a new firmware is avaiable.",
66
"examples": "examples/*/*.ino",

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=esp32FOTA
2-
version=0.2.9
2+
version=0.3.0
33
author=Chris Joyce
44
maintainer=Chris Joyce <chris@joyce.id.au>
55
sentence=A simple library for firmware OTA updates

src/esp32FOTA.cpp

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ void esp32FOTA::setConfig( FOTAConfig_t cfg )
191191
_cfg.signature_len = cfg.signature_len;
192192
_cfg.allow_reuse = cfg.allow_reuse;
193193
_cfg.use_http10 = cfg.use_http10;
194+
_cfg.use_bundled_certs = cfg.use_bundled_certs;
194195
}
195196

196197

@@ -239,7 +240,15 @@ void esp32FOTA::setupCryptoAssets()
239240
}
240241

241242

242-
243+
/* Enable ESP-IDF bundled certs */
244+
void esp32FOTA::useBundledCerts(bool enable)
245+
{
246+
_cfg.use_bundled_certs = enable;
247+
if (enable) {
248+
_cfg.unsafe = false; // strict
249+
_cfg.root_ca = nullptr; // disable custom CA
250+
}
251+
}
243252
void esp32FOTA::handle()
244253
{
245254
if ( execHTTPcheck() ) {
@@ -370,34 +379,75 @@ bool esp32FOTA::setupHTTP( const char* url )
370379
_http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
371380
_http.setReuse(_cfg.allow_reuse);
372381
_http.useHTTP10(_cfg.use_http10);
373-
374382

375383
log_i("Connecting to: %s", url );
376-
if( String(url).startsWith("https") ) {
377-
if (!_cfg.unsafe) {
378-
if( !_cfg.root_ca ) {
379-
log_e("A strict security context has been set but no RootCA was provided");
380-
return false;
384+
385+
bool is_https = String(url).startsWith("https");
386+
387+
if (is_https) {
388+
389+
bool https_initialized = false;
390+
391+
/* ================================
392+
1. Bundled ESP-IDF CA certificate
393+
================================ */
394+
#if ESP_ARDUINO_VERSION_MAJOR >= 3
395+
if (!https_initialized && _cfg.use_bundled_certs) {
396+
__attribute__((weak)) extern const uint8_t ca_cert_bundle_start[] asm("_binary_ca_cert_bundle_start");
397+
__attribute__((weak)) extern const uint8_t ca_cert_bundle_end[] asm("_binary_ca_cert_bundle_end");
398+
399+
if (ca_cert_bundle_start != nullptr && ca_cert_bundle_end != nullptr) {
400+
size_t bundle_size = ca_cert_bundle_end - ca_cert_bundle_start;
401+
log_i("Using built-in ESP-IDF certificate bundle (%u bytes)", bundle_size);
402+
403+
_client.setCACertBundle(ca_cert_bundle_start, bundle_size);
404+
_http.begin(_client, url);
405+
https_initialized = true;
406+
} else {
407+
log_w("Bundled certs requested, but CA bundle not linked. Falling back.");
381408
}
382-
if( _cfg.root_ca->size() == 0 ) {
383-
log_e("A strict security context has been set but an empty RootCA was provided");
409+
}
410+
#endif
411+
412+
/* ================================
413+
2. Custom root CA certificate
414+
================================ */
415+
if (!https_initialized && !_cfg.unsafe && _cfg.root_ca) {
416+
if (_cfg.root_ca->size() == 0) {
417+
log_e("Strict HTTPS but empty RootCA provided");
384418
return false;
385419
}
386420
rootcastr = _cfg.root_ca->get();
387-
if( !rootcastr ) {
388-
log_e("Unable to get RootCA, aborting");
389-
log_e("rootcastr=%s", rootcastr);
421+
if (!rootcastr) {
422+
log_e("Strict HTTPS but failed to get RootCA contents");
390423
return false;
391424
}
392-
log_d("Loading root_ca.pem");
393-
_client.setCACert( rootcastr );
394-
} else {
395-
// We're downloading from a secure URL, but we don't want to validate the root cert.
425+
log_i("Using custom RootCA for TLS");
426+
_client.setCACert(rootcastr);
427+
_http.begin(_client, url);
428+
https_initialized = true;
429+
}
430+
431+
/* ================================
432+
3. Insecure HTTPS
433+
================================ */
434+
if (!https_initialized && _cfg.unsafe) {
435+
log_w("Insecure HTTPS enabled");
396436
_client.setInsecure();
437+
_http.begin(_client, url);
438+
https_initialized = true;
397439
}
398-
_http.begin( _client, url );
440+
441+
/* ================================
442+
4. No CA available (strict)
443+
================================ */
444+
if (!https_initialized) {
445+
log_e("Strict HTTPS enabled but no CA source available (neither bundled nor custom)");
446+
return false;
447+
}
448+
399449
} else {
400-
_http.begin( url );
450+
_http.begin(url);
401451
}
402452

403453
if( extraHTTPHeaders.size() > 0 ) {

src/esp32FOTA.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ struct FOTAConfig_t
222222
size_t signature_len {FW_SIGNATURE_LENGTH};
223223
bool allow_reuse { true };
224224
bool use_http10 { false }; // Use HTTP 1.0 (WARNING: setting to 'true' disables chunked transfers)
225+
bool use_bundled_certs { false }; // use built-in ESP-IDF CA bundle
225226
FOTAConfig_t() = default;
226227
};
227228

@@ -287,6 +288,10 @@ class esp32FOTA
287288
void setCertFileSystem( fs::FS *cert_filesystem = nullptr );
288289

289290
// this is passed to Update.onProgress()
291+
292+
// enable bundled CA certificates
293+
void useBundledCerts(bool enable = true);
294+
290295
typedef std::function<void(size_t,size_t)> ProgressCallback_cb; // size_t progress, size_t size
291296
void setProgressCb(ProgressCallback_cb fn) { onOTAProgress = fn; } // callback setter
292297

0 commit comments

Comments
 (0)