HTTPS and certificates
TLS on a microcontroller works, and it fails in two ways that do not look like certificate problems - a handshake that cannot find enough contiguous heap, and a root certificate that expires on a date you chose years earlier.
The two real failures
setInsecure() to make a problem go away: it turns off the verification that is the entire point, and a device that accepts any certificate will happily send its readings to whoever is nearest.What a certificate on the board is for
The board is checking that the server is who it claims to be. That check needs a root certificate — the one that signed the server's certificate — compiled into the firmware, because the board has no operating system certificate store.
Pin the root, not the server's own certificate. Servers rotate theirs every few months; roots last a decade. Pinning the leaf means an outage each renewal.
Prefer EC over RSA
An ECDSA P-256 root costs roughly half the memory and half the traffic of RSA-2048 for the same security. On a chip where the handshake is the largest allocation the firmware ever makes, that is the difference between working and not.
Sync the clock first
Certificate validity is a date range. A board that boots at 1 January 1970 will reject every valid certificate it is shown. NTP before TLS, always.
The code
A root certificate in the sketch, a client that verifies against it, and the connection closed when it is done. setInsecure is the line to never write in something that leaves your desk.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
// ISRG Root X1 - abbreviated. Paste the whole PEM in real code.
const char *ROOT_CA = R"(-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
...
-----END CERTIFICATE-----)";
void setup() {
Serial.begin(115200);
WiFi.begin("your-network", "your-password");
while (WiFi.status() != WL_CONNECTED) delay(250);
Serial.printf("largest free block %u\n",
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
WiFiClientSecure client;
client.setCACert(ROOT_CA); // never setInsecure() in production
HTTPClient http;
if (http.begin(client, "https://example.com/api/readings")) {
int code = http.POST("{\"temp\":21.4}");
Serial.printf("HTTP %d\n", code);
http.end(); // free the TLS buffers now
}
}
void loop() {}Only the root is pinned here, not the server's own certificate. That survives the server rotating its certificate every 90 days, which pinning the leaf does not.
MicroPython verifies only if you give it a certificate. Without one, ssl wraps the socket and checks nothing, which is encryption without authentication - and that is not the same as security.
import network, ssl, socket
with open('isrgrootx1.der', 'rb') as f:
ca = f.read()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cadata=ca)
addr = socket.getaddrinfo('example.com', 443)[0][-1]
s = socket.socket()
s.connect(addr)
s = ctx.wrap_socket(s, server_hostname='example.com')
s.write(b'GET /api/readings HTTP/1.0\r\nHost: example.com\r\n\r\n')
print(s.read(256))
s.close()Convert the PEM to DER before putting it on the board. MicroPython's ssl wants DER, and a PEM string fails with an unhelpful error.
When it does not work
Out of memory. The handshake needs tens of kilobytes in one contiguous block. Open the connection before allocating display or camera buffers, and close it afterwards.
The root certificate expired. Nothing changed on the device - the date passed. This is why HTTPS and OTA belong together, and why bundling two roots is worth the flash.
Verification checks the date. Sync NTP before the first HTTPS connection or every certificate looks not yet valid.
It did not fix it, it switched off the check. The connection is still encrypted and now anybody who can answer that hostname is trusted, which is most of the point gone.
All of this assumed a radio. The next page is what to do when the board lives in a metal cabinet.
Ethernet instead of Wi-Fi →Edit this page — content/esp32/https-and-certificates.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.