diff --git a/firmware/README.md b/firmware/README.md index 04e584d..eb84103 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -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 diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index 3e17fd4..34aeb49 100644 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRCS main.c wifi_provisioning.c frame_client.c qr_onboarding.c status_screen.c epd_draw.c reset_button.c next_button.c manage_button.c manage_qr_overlay.c - PRIV_REQUIRES esp_event nvs_flash esp_wifi esp_netif esp_http_server esp_http_client dns_server epd7in3e qrcode epaper_fonts esp_driver_gpio + PRIV_REQUIRES esp_event nvs_flash esp_wifi esp_netif esp_http_server esp_http_client mbedtls dns_server epd7in3e qrcode epaper_fonts esp_driver_gpio EMBED_FILES root.html - EMBED_TXTFILES certs/cloudflare_origin_ca.pem) + EMBED_TXTFILES certs/tools_server_ca.pem) diff --git a/firmware/main/certs/cloudflare_origin_ca.pem b/firmware/main/certs/cloudflare_origin_ca.pem deleted file mode 100644 index 9e69b3f..0000000 --- a/firmware/main/certs/cloudflare_origin_ca.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIID+rOSdTGfGcwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNV -BAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTQwMgYDVQQLEytDbG91 -ZEZsYXJlIE9yaWdpbiBTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQH -Ew1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMB4XDTE5MDgyMzIx -MDgwMFoXDTI5MDgxNTE3MDAwMFowgYsxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBD -bG91ZEZsYXJlLCBJbmMuMTQwMgYDVQQLEytDbG91ZEZsYXJlIE9yaWdpbiBTU0wg -Q2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMw -EQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAwEiVZ/UoQpHmFsHvk5isBxRehukP8DG9JhFev3WZtG76WoTthvLJFRKFCHXm -V6Z5/66Z4S09mgsUuFwvJzMnE6Ej6yIsYNCb9r9QORa8BdhrkNn6kdTly3mdnykb -OomnwbUfLlExVgNdlP0XoRoeMwbQ4598foiHblO2B/LKuNfJzAMfS7oZe34b+vLB -yrP/1bgCSLdc1AxQc1AC0EsQQhgcyTJNgnG4va1c7ogPlwKyhbDyZ4e59N5lbYPJ -SmXI/cAe3jXj1FBLJZkwnoDKe0v13xeF+nF32smSH0qB7aJX2tBMW4TWtFPmzs5I -lwrFSySWAdwYdgxw180yKU0dvwIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYD -VR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUJOhTV118NECHqeuU27rhFnj8KaQw -HwYDVR0jBBgwFoAUJOhTV118NECHqeuU27rhFnj8KaQwDQYJKoZIhvcNAQELBQAD -ggEBAHwOf9Ur1l0Ar5vFE6PNrZWrDfQIMyEfdgSKofCdTckbqXNTiXdgbHs+TWoQ -wAB0pfJDAHJDXOTCWRyTeXOseeOi5Btj5CnEuw3P0oXqdqevM1/+uWp0CM35zgZ8 -VD4aITxity0djzE6Qnx3Syzz+ZkoBgTnNum7d9A66/V636x4vTeqbZFBr9erJzgz -hhurjcoacvRNhnjtDRM0dPeiCJ50CP3wEYuvUzDHUaowOsnLCjQIkWbR7Ni6KEIk -MOz2U0OBSif3FTkhCgZWQKOOLo1P42jHC3ssUZAtVNXrCk3fw9/E15k8NPkBazZ6 -0iykLhH1trywrKRMVw67F44IE8Y= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICiTCCAi6gAwIBAgIUXZP3MWb8MKwBE1Qbawsp1sfA/Y4wCgYIKoZIzj0EAwIw -gY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T -YW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYDVQQL -Ey9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhvcml0 -eTAeFw0xOTA4MjMyMTA4MDBaFw0yOTA4MTUxNzAwMDBaMIGPMQswCQYDVQQGEwJV -UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEZ -MBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjE4MDYGA1UECxMvQ2xvdWRGbGFyZSBP -cmlnaW4gU1NMIEVDQyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwWTATBgcqhkjOPQIB -BggqhkjOPQMBBwNCAASR+sGALuaGshnUbcxKry+0LEXZ4NY6JUAtSeA6g87K3jaA -xpIg9G50PokpfWkhbarLfpcZu0UAoYy2su0EhN7wo2YwZDAOBgNVHQ8BAf8EBAMC -AQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUhTBdOypw1O3VkmcH/es5 -tBoOOKcwHwYDVR0jBBgwFoAUhTBdOypw1O3VkmcH/es5tBoOOKcwCgYIKoZIzj0E -AwIDSQAwRgIhAKilfntP2ILGZjwajktkBtXE1pB4Y/fjAfLkIRUzrI15AiEA5UCL -XYZZ9m2c3fKwIenMMojL1eqydsgqj/wK4p5kagQ= ------END CERTIFICATE----- diff --git a/firmware/main/certs/tools_server_ca.pem b/firmware/main/certs/tools_server_ca.pem new file mode 100644 index 0000000..99cad3d --- /dev/null +++ b/firmware/main/certs/tools_server_ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDejCCAmKgAwIBAgIQf+UwvzMTQ77dghYQST2KGzANBgkqhkiG9w0BAQsFADBX +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE +CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIzMTEx +NTAzNDMyMVoXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT +GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFI0 +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE83Rzp2iLYK5DuDXFgTB7S0md+8Fhzube +Rr1r1WEYNa5A3XP3iZEwWus87oV8okB2O6nGuEfYKueSkWpz6bFyOZ8pn6KY019e +WIZlD6GEZQbR3IvJx3PIjGov5cSr0R2Ko4H/MIH8MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUgEzW63T/STaj1dj8tT7FavCUHYwwHwYDVR0jBBgwFoAUYHtmGkUN +l8qJUC99BM00qP/8/UswNgYIKwYBBQUHAQEEKjAoMCYGCCsGAQUFBzAChhpodHRw +Oi8vaS5wa2kuZ29vZy9nc3IxLmNydDAtBgNVHR8EJjAkMCKgIKAehhxodHRwOi8v +Yy5wa2kuZ29vZy9yL2dzcjEuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMA0GCSqG +SIb3DQEBCwUAA4IBAQAYQrsPBtYDh5bjP2OBDwmkoWhIDDkic574y04tfzHpn+cJ +odI2D4SseesQ6bDrarZ7C30ddLibZatoKiws3UL9xnELz4ct92vID24FfVbiI1hY ++SW6FoVHkNeWIP0GCbaM4C6uVdF5dTUsMVs/ZbzNnIdCp5Gxmx5ejvEau8otR/Cs +kGN+hr/W5GvT1tMBjgWKZ1i4//emhA1JG1BbPzoLJQvyEotc03lXjTaCzv8mEbep +8RqZ7a2CPsgRbuvTPBwcOMBBmuFeU88+FSBX6+7iP0il8b4Z0QFqIwwMHfs/L6K1 +vepuoxtGzi4CZ68zJpiq1UvSqTbFJjtbD4seiMHl +-----END CERTIFICATE----- diff --git a/firmware/main/frame_client.c b/firmware/main/frame_client.c index 4ac9bee..1b8df32 100644 --- a/firmware/main/frame_client.c +++ b/firmware/main/frame_client.c @@ -25,12 +25,18 @@ static const char *TAG = "frame_client"; static EventGroupHandle_t s_sta_event_group; -/* Cloudflare's Origin CA root certs (RSA + ECC, both concatenated -- - * mbedtls_x509_crt_parse() chains every cert in a PEM buffer into the - * trust store, so either one nginx presents validates), for trusting an - * https tools server whose reverse proxy terminates TLS with a - * Cloudflare-issued origin certificate. See firmware/main/certs/. */ -extern const char cloudflare_origin_ca_pem_start[] asm("_binary_cloudflare_origin_ca_pem_start"); +/* The exact CA certificate the tools server's reverse proxy presents, + * embedded verbatim (not sourced from ESP-IDF's built-in root bundle -- + * that does an exact byte-level match against its compiled-in table, + * and a semantically-identical "same name, same public key" root + * re-issued under a different serial/signature, as Google did for GTS + * Root R4, doesn't match it; confirmed on hardware, see + * firmware/main/certs/tools_server_ca.pem for how this was extracted). + * If the proxy's CA ever changes, re-extract with: + * openssl s_client -connect :443 -showcerts toolsserver + a path (no leading slash), * appending cfg->access_token as ?token= if one's set. toolsserver is @@ -237,7 +243,7 @@ static frame_server_config_t fetch_frame_config(const frame_config_t *cfg) .url = url, .method = HTTP_METHOD_GET, .timeout_ms = CONFIG_FRAME_SERVER_CHECK_TIMEOUT_MS, - .cert_pem = cloudflare_origin_ca_pem_start, + .cert_pem = tools_server_ca_pem_start, }; esp_http_client_handle_t client = esp_http_client_init(&config); @@ -298,7 +304,7 @@ static void fetch_photo_info(const frame_config_t *cfg, char *location_line1, si .url = url, .method = HTTP_METHOD_GET, .timeout_ms = CONFIG_FRAME_SERVER_CHECK_TIMEOUT_MS, - .cert_pem = cloudflare_origin_ca_pem_start, + .cert_pem = tools_server_ca_pem_start, }; esp_http_client_handle_t client = esp_http_client_init(&config); @@ -358,7 +364,7 @@ static int fetch_face_labels(const frame_config_t *cfg, manage_face_label_t *out .url = url, .method = HTTP_METHOD_GET, .timeout_ms = CONFIG_FRAME_SERVER_CHECK_TIMEOUT_MS, - .cert_pem = cloudflare_origin_ca_pem_start, + .cert_pem = tools_server_ca_pem_start, }; esp_http_client_handle_t client = esp_http_client_init(&config); @@ -498,7 +504,7 @@ static esp_err_t fetch_and_display(const frame_config_t *cfg, bool force_advance .url = url, .method = force_advance ? HTTP_METHOD_POST : HTTP_METHOD_GET, .timeout_ms = CONFIG_FRAME_FETCH_TIMEOUT_MS, - .cert_pem = cloudflare_origin_ca_pem_start, + .cert_pem = tools_server_ca_pem_start, }; esp_http_client_handle_t client = esp_http_client_init(&config);