Background
OtaUpdater::downloadAndApply() pins the ISRG Root X1 (Let's Encrypt) certificate, but GitHub release download URLs are served via GitHub's CDN which may use Sectigo or other CA chains. When the pinned root CA does not match, TLS fails and every OTA update is blocked.
Additionally, fetchLatestRelease() uses setInsecure() (no cert validation), defeating TLS entirely for the metadata fetch.
Required Changes
- Use CA bundle: Replace
setCACert(kGitHubRootCA) with WiFiClientSecure::setCACertBundle() on ESP32
- Fix metadata TLS: Replace
setInsecure() in fetchLatestRelease() with the same CA trust store
- Fallback for ESP8266: Provide curated fallback CA bundle for flash-constrained devices
Affected Files
src/OtaUpdater.cpp — Both downloadAndApply() and fetchLatestRelease()
src/OtaUpdater.hpp — Compile-time flag OTA_USE_CA_BUNDLE
platformio.ini — Add -DCORE_SSL_CERT_BUNDLE build flag
src/CACertBundle.hpp — New: fallback bundle
OpenSpec
Full specification: openspec/specs/github-ca-chain.spec.md
Change marker: openspec/changes/p1-github-ca-chain/
Related
Background
OtaUpdater::downloadAndApply()pins the ISRG Root X1 (Let's Encrypt) certificate, but GitHub release download URLs are served via GitHub's CDN which may use Sectigo or other CA chains. When the pinned root CA does not match, TLS fails and every OTA update is blocked.Additionally,
fetchLatestRelease()usessetInsecure()(no cert validation), defeating TLS entirely for the metadata fetch.Required Changes
setCACert(kGitHubRootCA)withWiFiClientSecure::setCACertBundle()on ESP32setInsecure()infetchLatestRelease()with the same CA trust storeAffected Files
src/OtaUpdater.cpp— BothdownloadAndApply()andfetchLatestRelease()src/OtaUpdater.hpp— Compile-time flagOTA_USE_CA_BUNDLEplatformio.ini— Add-DCORE_SSL_CERT_BUNDLEbuild flagsrc/CACertBundle.hpp— New: fallback bundleOpenSpec
Full specification:
openspec/specs/github-ca-chain.spec.mdChange marker:
openspec/changes/p1-github-ca-chain/Related