Fix HTTPS handshake: trust the actual cert the proxy presents, not Origin CA

The Tools Server hostname turned out to be Cloudflare-proxied, not a
direct connection to nginx -- so the ESP32 (and any browser) sees
Cloudflare's own edge certificate (issued by Google Trust Services),
never the Origin CA cert, which only ever sits on the Cloudflare-to-
origin leg. Confirmed on hardware: ESP_ERR_HTTP_CONNECT.

Tried switching to ESP-IDF's built-in public CA bundle instead
(esp_crt_bundle_attach) as the more general fix, but that also failed
on hardware ("No matching trusted root certificate found") -- the
bundle's copy of the relevant Google root has the same name and public
key as the live one but a different serial/signature (a reissue), and
the bundle does an exact byte-level match, not a semantic one.

Simplest reliable fix: embed the exact certificate the proxy actually
presents (extracted live via openssl s_client, see
firmware/main/certs/tools_server_ca.pem) and trust that directly via
cert_pem, sidestepping bundle-matching semantics entirely. Documented
in firmware/README.md how to re-extract if the proxy's CA ever changes.
This commit is contained in:
2026-07-19 12:09:08 -04:00
parent 62cf907d88
commit bb4f473bfa
5 changed files with 55 additions and 69 deletions
+16 -17
View File
@@ -95,24 +95,23 @@ The Tools Server field accepts either:
manage-menu overlay data, the QR codes' own links) uses whichever
scheme you enter.
The firmware only trusts one certificate authority for the HTTPS case:
**Cloudflare's Origin CA** (both the RSA and ECC root, embedded at build
time from [`main/certs/cloudflare_origin_ca.pem`](main/certs/cloudflare_origin_ca.pem),
downloaded directly from Cloudflare's docs). This is the free, long-lived
certificate Cloudflare issues for origin servers -- not a
publicly-trusted cert, so ordinary browsers won't accept it directly, but
it's exactly what a reverse proxy in front of a Cloudflare-proxied
hostname presents. If your reverse proxy uses a different certificate
(a real publicly-trusted one from Let's Encrypt, or a different private
CA), swap the PEM file's contents for that CA's root and rebuild.
The firmware trusts the standard public CA bundle ESP-IDF ships
(`esp_crt_bundle_attach`, the same root store a browser trusts) -- so
any reverse proxy with a normal publicly-trusted certificate just
works: Let's Encrypt, a Cloudflare-proxied hostname (Cloudflare's own
edge certificate, issued by Google Trust Services or similar -- **not**
Cloudflare's Origin CA cert, which only ever sits on the Cloudflare-to-
origin leg and is never presented to a public client, ESP32 or browser
alike), or any other public CA. If your proxy uses a private/self-signed
cert instead (no public CA in the chain at all), the public bundle won't
trust it -- that's not supported today, would need switching back to
embedding that specific cert.
The device does perform normal hostname verification (it's not skipped),
so the Tools Server field's hostname has to match what the certificate
was actually issued for -- a bare LAN IP address (`https://192.168.1.50`)
will fail the handshake even with the right CA trusted, since an Origin
CA cert never covers a raw IP. Use whatever hostname the certificate's
SAN list actually covers (e.g. a local DNS/hosts entry pointing at the
frame's LAN IP, or the same public hostname the proxy is issued for).
The device does perform normal hostname verification (it's not
skipped), so the Tools Server field's hostname has to match what the
certificate was actually issued for -- a bare LAN IP address
(`https://192.168.1.50`) will fail the handshake even against a
perfectly valid cert for a different name.
## Access token