Chapter 94: HTTPS with TLS/SSL in ESP-IDF

Chapter Objectives

After completing this chapter, you will be able to:

  • Understand the fundamental need for secure communication in IoT applications.
  • Grasp the basics of TLS/SSL, including its goals: confidentiality, integrity, and authentication.
  • Learn about Public Key Infrastructure (PKI), digital certificates (X.509), and the role of Certificate Authorities (CAs).
  • Configure the ESP-IDF esp_http_client to make HTTPS requests.
  • Understand and implement server certificate verification on the ESP32.
  • Embed CA root certificates in your ESP32 firmware for trusting servers.
  • Discuss alternative methods for managing certificates.
  • Implement secure HTTPS GET and POST requests to web servers.
  • Be aware of the performance implications and resource considerations of using TLS on ESP32 devices.
  • Troubleshoot common issues related to HTTPS connections.

Introduction

In Chapter 93, we explored how the ESP32 can act as an HTTP client, enabling it to interact with web servers and APIs. While HTTP is functional, it transmits data in plaintext, making it vulnerable to eavesdropping and modification. For any application that handles sensitive information—such as personal data, credentials, financial transactions, or critical control commands for IoT devices—unsecured communication is unacceptable.

graph LR
    subgraph "HTTPS Communication (Secure)"
        direction LR
        HTTPS_ESP32["<br><b>ESP32</b><br><i>Client</i>"]
        HTTPS_DATA["<br>Encrypted Data<br>(Confidential & Integrity-Protected)<br>🔒"]
        HTTPS_TUNNEL["<br>(Secure TLS/SSL Tunnel)<br><i>Protected Channel</i>"]
        HTTPS_SERVER["<br><b>Web Server</b>"]

        HTTPS_ESP32 -- "Sends/Receives" --> HTTPS_DATA
        HTTPS_DATA -- "Transmitted Via" --> HTTPS_TUNNEL
        HTTPS_TUNNEL -- "Reaches" --> HTTPS_SERVER
    end
    subgraph "HTTP Communication (Insecure)"
        direction LR
        HTTP_ESP32["<br><b>ESP32</b><br><i>Client</i>"]
        HTTP_DATA["<br>Plaintext Data<br>(e.g., credentials, sensor readings)<br>🔓"]
        HTTP_INTERNET["<br>(Internet/Network)<br><i>Vulnerable to Eavesdropping</i>"]
        HTTP_SERVER["<br><b>Web Server</b>"]

        HTTP_ESP32 -- "Sends/Receives" --> HTTP_DATA
        HTTP_DATA -- "Transmitted Over" --> HTTP_INTERNET
        HTTP_INTERNET -- "Reaches" --> HTTP_SERVER
    end



    %% Styling
    classDef default fill:#transparent,stroke:#4B5563,stroke-width:1px,color:#1F2937;
    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef danger fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46

    class HTTP_ESP32,HTTPS_ESP32 primary
    class HTTP_SERVER,HTTPS_SERVER primary
    class HTTP_DATA,HTTP_INTERNET danger
    class HTTPS_DATA,HTTPS_TUNNEL success

This is where HTTPS (HTTP Secure) comes into play. HTTPS is essentially the HTTP protocol layered on top of a secure communication channel provided by TLS (Transport Layer Security) or its predecessor SSL (Secure Sockets Layer). TLS/SSL encrypts the data exchanged between the client (your ESP32) and the server, ensuring confidentiality. It also provides mechanisms for data integrity (ensuring data hasn’t been tampered with) and server authentication (verifying that you are talking to the legitimate server and not an imposter).

This chapter will guide you through the principles of TLS/SSL and how to implement HTTPS client functionality on your ESP32 using the esp_http_client component. We will focus on enabling your ESP32 to securely connect to servers, verify their identities using digital certificates, and exchange data over an encrypted channel. Mastering HTTPS is a cornerstone of developing robust and trustworthy IoT solutions.

Theory

What is TLS/SSL?

Transport Layer Security (TLS) is a cryptographic protocol designed to provide secure communication over a computer network. It is the successor to Secure Sockets Layer (SSL). Although the term SSL is still widely used, modern secure connections almost exclusively use TLS. For simplicity, we’ll often use “TLS” or “TLS/SSL”.

Goal Description Achieved Via (in TLS)
Confidentiality Ensures that the data exchanged between the client and server is unreadable to any third party who might intercept it. Symmetric encryption (e.g., AES, ChaCha20) using session keys negotiated during the handshake.
Integrity Ensures that the data has not been altered or corrupted during transmission, either accidentally or maliciously. Message Authentication Codes (MACs) or Authenticated Encryption with Associated Data (AEAD) ciphers (e.g., HMAC-SHA256, AES-GCM).
Authentication Verifies the identity of one or both parties in the communication. Primarily server authentication (client verifies server’s identity) is common. Digital certificates (X.509) and public-key cryptography. The server presents its certificate, signed by a trusted Certificate Authority (CA).

The primary goals of TLS are:

  1. Confidentiality: Achieved through encryption. Data exchanged between the client and server is encrypted, making it unreadable to any third party who might intercept it.
  2. Integrity: Achieved through message authentication codes (MACs). TLS ensures that the data has not been altered or corrupted during transmission.
  3. Authentication: Primarily server authentication. TLS allows the client to verify the identity of the server it is connecting to, preventing man-in-the-middle (MITM) attacks. Client authentication (where the server also verifies the client’s identity) is also possible but less common for typical web browsing or simple API access.

TLS Handshake

Before any application data can be exchanged securely, the client and server perform a TLS handshake. This is a complex process, but a simplified overview includes:

  1. Client Hello: The client initiates the handshake by sending a “ClientHello” message to the server. This message includes the TLS versions the client supports, a list of cipher suites (cryptographic algorithms for encryption, key exchange, and MACs) it can use, and a random number (client random).
  2. Server Hello: The server responds with a “ServerHello” message, selecting the TLS version and cipher suite from the client’s list, and providing its own random number (server random).
  3. Server Certificate: The server sends its digital certificate (and potentially intermediate certificates) to the client. This certificate contains the server’s public key and information about its identity, signed by a Certificate Authority (CA).
  4. Server Key Exchange (Optional): Depending on the cipher suite, the server might send additional information needed for key exchange.
  5. Server Hello Done: The server indicates it has finished sending its initial handshake messages.
  6. Client Certificate Verification: The client verifies the server’s certificate (see PKI section below). This is a critical step for security.
  7. Client Key Exchange: The client generates a “premaster secret” (a key component for the session keys), encrypts it with the server’s public key (obtained from the server’s certificate), and sends it to the server. Only the server, with its corresponding private key, can decrypt this.
  8. Change Cipher Spec: Both client and server send “ChangeCipherSpec” messages, indicating that subsequent messages will be encrypted using the newly negotiated keys and algorithms.
  9. Finished: Both client and server send “Finished” messages, which are encrypted and MAC’d with the new session keys. These messages confirm that the handshake was successful and that both parties have derived the same session keys.

Once the handshake is complete, the client and server can exchange encrypted application data (e.g., HTTP requests and responses).

sequenceDiagram


    participant C as Client (ESP32)
    participant S as Server

    C->>S: 1. ClientHello <br> (TLS versions, Cipher Suites, Client Random)
    activate S
    S-->>C: 2. ServerHello <br> (Selected TLS version, Cipher Suite, Server Random)
    S-->>C: 3. Server Certificate <br> (Server's Public Key, CA Signature)
    S-->>C: 4. ServerKeyExchange (Optional, e.g., for DHE/ECDHE)
    S-->>C: 5. ServerHelloDone
    deactivate S

    activate C
    C-->>C: 6. Verify Server Certificate <br> (Check signature, validity, trust chain, hostname)
    C->>S: 7. ClientKeyExchange <br> (Premaster Secret, encrypted with Server's Public Key)
    C->>S: 8. ChangeCipherSpec <br> (Future messages will be encrypted)
    activate S
    C->>S: 9. Finished (Encrypted Handshake Verification)
    deactivate C

    S-->>C: 10. ChangeCipherSpec <br> (Future messages will be encrypted)
    S-->>C: 11. Finished (Encrypted Handshake Verification)
    deactivate S

    loop Encrypted Application Data Exchange
        C->>S: HTTPS Request (Encrypted)
        S-->>C: HTTPS Response (Encrypted)
    end


Public Key Infrastructure (PKI)

PKI is a set of roles, policies, hardware, software, and procedures needed to create, manage, distribute, use, store, and revoke digital certificates and manage public-key encryption. It’s the framework that enables trust in TLS/SSL.

Asymmetric vs. Symmetric Encryption

  • Symmetric Encryption: Uses the same key for both encryption and decryption. It’s fast but requires a secure way to share the key initially. TLS uses symmetric encryption for the bulk data transfer after the handshake.
  • Asymmetric Encryption (Public-Key Cryptography): Uses a pair of keys: a public key (which can be shared widely) and a private key (kept secret). Data encrypted with the public key can only be decrypted with the corresponding private key, and vice-versa. TLS uses asymmetric encryption during the handshake (e.g., for the server to prove its identity and for the client to securely send the premaster secret).
Feature Symmetric Encryption Asymmetric Encryption (Public-Key Cryptography)
Key Usage Uses a single, shared secret key for both encryption and decryption. Uses a pair of keys: a public key (for encryption or signature verification) and a private key (for decryption or signing).
Speed Generally faster and more computationally efficient. Slower and more computationally intensive.
Key Exchange Challenge Requires a secure method to exchange the shared secret key initially. Simplifies key distribution as the public key can be shared openly. The private key must remain secret.
Primary Use in TLS Used for bulk encryption/decryption of application data after the handshake is complete (session keys). Used during the TLS handshake for:
– Server authentication (server proves ownership of private key corresponding to public key in its certificate).
– Securely exchanging the premaster secret (client encrypts it with server’s public key).
Examples of Algorithms AES, ChaCha20, DES, 3DES RSA, ECC (Elliptic Curve Cryptography), Diffie-Hellman

Digital Certificates (X.509)

An X.509 digital certificate is an electronic document that uses a digital signature to bind a public key with an identity — information such as the name of a person or an organization, their address, and so forth.

Key information in a server certificate includes:

Field Description Example / Importance for ESP32 Client
Subject Identifies the entity to whom the certificate is issued (e.g., a person, organization, or device). For web servers, this typically includes the domain name(s). Contains the Common Name (CN) (e.g., www.example.com) and/or Subject Alternative Names (SANs) that the ESP32 must match against the requested hostname.
Issuer Identifies the Certificate Authority (CA) that signed and issued the certificate. The ESP32 uses this to build the certificate chain and find the issuer’s certificate for signature verification.
Public Key The public key of the subject. This key corresponds to a private key securely held by the subject. The ESP32 uses this public key to encrypt the premaster secret during the TLS handshake, ensuring only the server (with its private key) can decrypt it. Also used to verify signatures made by the subject if client authentication were used.
Validity Period Specifies the dates during which the certificate is considered valid (e.g., “Not Before” and “Not After” dates). The ESP32 must have accurate time (e.g., via SNTP) to check if the current date falls within this period. An expired or not-yet-valid certificate will be rejected.
Signature Algorithm The cryptographic algorithm used by the CA to sign the certificate (e.g., SHA256withRSA). Indicates how the digital signature was created and how it should be verified.
Digital Signature The CA’s digital signature on the certificate’s content. This signature confirms the certificate’s authenticity and integrity. The ESP32 verifies this signature using the issuer CA’s public key to ensure the certificate hasn’t been tampered with and was indeed issued by the stated CA.
Version Indicates the X.509 version (e.g., v3). Version 3 certificates allow for extensions, like SANs.
Serial Number A unique number assigned by the CA to this certificate. Used for identification and revocation checking (though ESP32 typically doesn’t do full revocation checks by default).
Extensions (Optional) Additional fields, such as Subject Alternative Name (SAN), Key Usage, Extended Key Usage, Basic Constraints. SAN is critical for matching multiple hostnames. Key Usage defines the purpose of the public key (e.g., digital signature, key encipherment).

Certificate Authorities (CAs)

A Certificate Authority is a trusted entity that issues digital certificates. CAs validate the identity of entities (like websites) before issuing them a certificate. Browsers and operating systems (and thus our ESP32 applications) come with a pre-installed list of root CA certificates. These root CAs are globally trusted.

Chain of Trust

Often, server certificates are not directly signed by a root CA. Instead, they are signed by an intermediate CA, which in turn is signed by another intermediate CA or a root CA. This forms a certificate chain (or path).

Server Certificate (e.g., www.example.com) <- Signed by Intermediate CA 1

Intermediate CA 1 Certificate <- Signed by Intermediate CA 2 (or Root CA)

Intermediate CA 2 Certificate <- Signed by Root CA

Root CA Certificate (Self-signed, trusted by client)

To verify a server’s certificate, the client (ESP32) must be able to trace this chain back to a root CA certificate that it trusts (i.e., one that is embedded in its firmware or otherwise made available to it).

graph TD
    RootCA["<br><b>Root CA Certificate</b><br>(e.g., ISRG Root X1)<br><i>Self-Signed</i><br>🛡️ Trusted by Client (ESP32)"]
    IntermediateCA1["<br><b>Intermediate CA Certificate 1</b><br>(e.g., Let's Encrypt R3)"]
    ServerCert["<br><b>Server Certificate</b><br>(e.g., www.example.com)"]

    subgraph "Trust Anchor on Client (ESP32)"
        direction LR
        ClientTrustStore["<br>Client's Trust Store<br>(Contains Root CA Certs)"]
    end
    
    ServerCert -- "<i>Signed by</i><br>(Issuer: Intermediate CA 1)" --> IntermediateCA1
    IntermediateCA1 -- "<i>Signed by</i><br>(Issuer: Root CA)" --> RootCA
    RootCA -. "<i>Verified Against</i>" .-> ClientTrustStore


    %% Optional: Another Intermediate CA
    %% IntermediateCA2["<br><b>Intermediate CA Certificate 2</b><br>(e.g., Another Intermediate)"]
    %% IntermediateCA1 -- "<i>Signed by</i><br>(Issuer: Intermediate CA 2)" --> IntermediateCA2
    %% IntermediateCA2 -- "<i>Signed by</i><br>(Issuer: Root CA)" --> RootCA

    %% Styling
    classDef default fill:#transparent,stroke:#4B5563,color:#1F2937;
    classDef root fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 
    classDef intermediate fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E 
    classDef server_cert fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF 
    classDef trust_store fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 

    class RootCA root
    class IntermediateCA1 intermediate
    %% class IntermediateCA2 intermediate
    class ServerCert server_cert
    class ClientTrustStore trust_store

Server Authentication by the Client (ESP32)

When the ESP32 (as an HTTPS client) receives the server’s certificate during the TLS handshake, it performs several checks:

Verification Check Purpose & How it’s Done by ESP32
1. Signature Verification Ensures each certificate in the chain is authentically signed by its issuer. The ESP32 uses the issuer’s public key (from the issuer’s certificate) to verify the signature on the current certificate. This process is repeated up the chain.
2. Trusted Root CA Confirms that the certificate chain terminates at a Root CA certificate that the ESP32 trusts. The ESP32 must have a local copy of this Root CA’s certificate (e.g., embedded in firmware via cert_pem or from esp_crt_bundle_attach).
3. Validity Period Checks if the current date and time are within the “Not Before” and “Not After” dates specified in each certificate in the chain. This requires the ESP32 to have reasonably accurate system time (ideally synchronized via SNTP).
4. Common Name (CN) / Subject Alternative Name (SAN) Matching Verifies that the hostname the ESP32 is attempting to connect to (e.g., api.example.com from the URL) matches the Common Name (CN) or one of the Subject Alternative Names (SANs) listed in the server’s certificate. Prevents connecting to a server with a valid certificate for a different domain.
5. Revocation Status (Advanced/Optional) Checks if the certificate has been revoked by the CA before its scheduled expiry (e.g., if the private key was compromised). ESP-IDF’s esp_http_client does not perform CRL (Certificate Revocation List) or OCSP (Online Certificate Status Protocol) checks by default, but mbedTLS supports these if custom configured. This is usually not implemented in basic ESP32 client applications due to complexity and resource use.
6. Basic Constraints & Key Usage (Implicit) Implicitly, mbedTLS checks if the CA certificates are actually marked as CAs (Basic Constraints extension) and if the certificate’s key usage is appropriate for server authentication.

If all these checks pass, the server is considered authentic.

ESP-IDF, esp_http_client, and mbedTLS

ESP-IDF uses the mbedTLS library (a popular open-source SSL/TLS library) to provide TLS functionality. The esp_http_client component, when configured for HTTPS, leverages esp_tls (an abstraction layer over mbedTLS) to establish secure connections.

graph TD
    subgraph "User Application Layer"
        App["<br><b>Your ESP32 Application</b><br>(e.g., Sensor Data Upload, API Call)"]
    end

    subgraph "ESP-IDF HTTP Client Layer"
        HTTPClient["<br><b>esp_http_client</b><br>(Manages HTTP/HTTPS requests/responses,<br>headers, methods, etc.)"]
    end

    subgraph "ESP-IDF TLS Abstraction Layer"
        ESPTLS["<br><b>esp_tls</b><br>(Provides a unified API for TLS operations,<br>abstracts underlying TLS library)"]
    end

    subgraph "Core TLS/SSL Library"
        MBEDTLS["<br><b>mbedTLS Library</b><br>(Handles TLS handshake, encryption/decryption,<br>certificate parsing, cryptographic functions)"]
    end

    subgraph "Network Stack & Drivers"
        TCPIP["<br><b>LWIP TCP/IP Stack</b><br>(Manages network connections, sockets)"]
        WiFiDrv["<br><b>Wi-Fi Driver & Hardware</b><br>(Physical layer communication)"]
    end

    App -- "Uses API of" --> HTTPClient
    HTTPClient -- "<i>For HTTPS, uses</i>" --> ESPTLS
    ESPTLS -- "<i>Wraps/Uses</i>" --> MBEDTLS
    MBEDTLS -- "<i>Relies on</i>" --> TCPIP
    TCPIP -- "<i>Interfaces with</i>" --> WiFiDrv

    %% Styling
    classDef default fill:#transparent,stroke:#4B5563,color:#1F2937;
    classDef app fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 
    classDef http fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF 
    classDef tls_abs fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E 
    classDef core_tls fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 
    classDef network fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B 

    class App app
    class HTTPClient http
    class ESPTLS tls_abs
    class MBEDTLS core_tls
    class TCPIP,WiFiDrv network

You typically don’t interact with mbedTLS directly when using esp_http_client for basic HTTPS, but it’s working under the hood.

Practical Examples

The core change from HTTP to HTTPS using esp_http_client involves:

  1. Changing the URL scheme from http:// to https://.
  2. Setting the transport_type in esp_http_client_config_t to HTTP_TRANSPORT_OVER_SSL.
  3. Providing the CA root certificate(s) needed to verify the server via the cert_pem field in the configuration or using esp_crt_bundle_attach for a common bundle of CAs.

Obtaining CA Root Certificates

To verify a server’s certificate, your ESP32 needs the root CA certificate that signed the server’s certificate chain.

  • Browser Method:
    1. Open the HTTPS website in your browser (e.g., Chrome, Firefox).
    2. Click the padlock icon in the address bar.
    3. View certificate details. Look for the certificate chain or path.
    4. Identify the root CA at the top of the chain.
    5. You can often download or export the root CA certificate in PEM format from the browser’s certificate manager or find it on the CA’s website (e.g., Let’s Encrypt, DigiCert, Sectigo).
  • OpenSSL Command Line:openssl s_client -connect example.com:443 -showcerts </dev/null
    This command will display the server’s certificate chain. The last certificate in the chain is usually the root CA (or an intermediate that you might need to trace further). Copy the PEM block (—–BEGIN CERTIFICATE—– … —–END CERTIFICATE—–) for the required root CA.

PEM Format:

A CA certificate in PEM (Privacy Enhanced Mail) format looks like this:

-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
... (many lines of Base64 encoded data) ...
b8ravHNjkOR/ez4KkEUDHYzT7pG9yS+R/0YgiycMO1L2E+xKj2stLAlO9wY0Jud4
JwBSW2fE0P0gP19i8F7PbI8D9p20dgA=
-----END CERTIFICATE-----

You will embed this string into your ESP32 code.

Example 1: Simple HTTPS GET Request

This example makes an HTTPS GET request to https://www.howsmyssl.com/a/check, which returns JSON data about the client’s TLS capabilities. We’ll use its root CA certificate. (As of my last update, howsmyssl.com might use Let’s Encrypt or another common CA. You should verify the current CA). For this example, let’s assume we need the “ISRG Root X1” certificate from Let’s Encrypt.

Project Setup:

  1. Create a new ESP-IDF project: idf.py create-project https_get_example
  2. Navigate into the project: cd https_get_example
  3. Replace main/main.c.
  4. Configure Wi-Fi in menuconfig.
  5. Enable CONFIG_MBEDTLS_CERTIFICATE_BUNDLE in menuconfig (Component config -> mbedTLS -> Certificate Bundle) and select “Provide bundle” or “Default bundle” for easier setup with common CAs. If using a specific cert_pem, this might not be strictly necessary but the bundle is good practice.

main/main.c:

C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_http_client.h"
#include "esp_netif.h"
#include "esp_crt_bundle.h" // For esp_crt_bundle_attach

#define WIFI_SSID      CONFIG_EXAMPLE_WIFI_SSID
#define WIFI_PASS      CONFIG_EXAMPLE_WIFI_PASSWORD
#define MAX_FAILURES   10

#define MAX_HTTP_OUTPUT_BUFFER 2048

static const char *TAG = "https_get_example";

// Wi-Fi event group and event_handler, wifi_init_sta (same as Chapter 93)
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1
static int s_retry_num = 0;

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < MAX_FAILURES) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
    wifi_config_t wifi_config = {
        .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );
    ESP_LOGI(TAG, "wifi_init_sta finished.");
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
    if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "connected to ap SSID:%s", WIFI_SSID); }
    else if (bits & WIFI_FAIL_BIT) { ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID); }
    else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); }
}

// HTTP event handler (same as Chapter 93, Example 1)
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
    static char *output_buffer;  // Buffer to store response of http request
    static int output_len;       // Stores number of bytes read
    switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            // If user_data is not NULL, it means buffer is passed from user, append data to it.
            // Ensure buffer is large enough.
            if (evt->user_data) {
                // Check if buffer is large enough
                char *user_buffer = (char *)evt->user_data;
                if (output_len + evt->data_len < MAX_HTTP_OUTPUT_BUFFER) {
                    memcpy(user_buffer + output_len, evt->data, evt->data_len);
                    output_len += evt->data_len;
                } else {
                    ESP_LOGW(TAG, "Output buffer too small for all data");
                    // Handle buffer overflow, e.g. by processing chunk by chunk or truncating
                }
            } else { // Dynamic allocation (less safe for embedded if not careful)
                if (output_buffer == NULL) {
                    // Consider esp_http_client_get_content_length(evt->client) if available
                    // For this example, let's assume a fixed max or chunked processing
                    output_buffer = (char *) malloc(MAX_HTTP_OUTPUT_BUFFER); // Example fixed size
                    output_len = 0;
                    if (output_buffer == NULL) {
                        ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
                        return ESP_FAIL;
                    }
                }
                 if (output_len + evt->data_len < MAX_HTTP_OUTPUT_BUFFER) {
                    memcpy(output_buffer + output_len, evt->data, evt->data_len);
                    output_len += evt->data_len;
                }
            }
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            char *buffer_to_log = output_buffer;
            if (evt->user_data) { // If user_data was used, log from there
                buffer_to_log = (char *)evt->user_data;
                buffer_to_log[output_len] = '\0'; // Null-terminate
            } else if (output_buffer) {
                 output_buffer[output_len] = '\0'; // Null-terminate
            }

            if (buffer_to_log) {
                ESP_LOGI(TAG, "Received data (len %d):\n%.*s", output_len, output_len, buffer_to_log);
            }
            
            if (output_buffer && !evt->user_data) { // Free only if dynamically allocated
                free(output_buffer);
                output_buffer = NULL;
            }
            output_len = 0; // Reset for next request if client is reused
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
            int mbedtls_err = 0;
            esp_err_t err = esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
            if (err != 0) {
                 ESP_LOGI(TAG, "Last esp_tls error: 0x%x", err);
                 ESP_LOGI(TAG, "Last mbedtls error: 0x%x", mbedtls_err);
            }
            if (output_buffer && !evt->user_data) {
                free(output_buffer);
                output_buffer = NULL;
            }
            output_len = 0;
            break;
        case HTTP_EVENT_REDIRECT:
            ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
            esp_http_client_set_redirection(evt->client);
            break;
    }
    return ESP_OK;
}


// You would get this from the CA's website or by inspecting the server's certificate chain.
// Example: ISRG Root X1 (Let's Encrypt)
// IMPORTANT: This certificate might expire or change. Always use a current one.
// For production, consider using the ESP-IDF certificate bundle feature.
const char *howsmyssl_root_cert_pem_start asm("_binary_howsmyssl_pem_start");
const char *howsmyssl_root_cert_pem_end asm("_binary_howsmyssl_pem_end");
// To use this, create a file named "howsmyssl.pem" in your main directory
// and add its content. Then, in CMakeLists.txt (main), add:
// target_add_binary_data(${COMPONENT_TARGET} "howsmyssl.pem" TEXT)


static void https_get_task(void *pvParameters)
{
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    ESP_LOGI(TAG, "Connected to Wi-Fi, starting HTTPS GET task.");

    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};

    esp_http_client_config_t config = {
        .url = "https://www.howsmyssl.com/a/check",
        .event_handler = _http_event_handler,
        .user_data = local_response_buffer,
        .transport_type = HTTP_TRANSPORT_OVER_SSL,  // Specify HTTPS
        //.cert_pem = howsmyssl_root_cert_pem_start, // Use this if embedding a specific cert
        .crt_bundle_attach = esp_crt_bundle_attach, // Use ESP-IDF's certificate bundle
                                                    // Requires CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTPS GET Status = %d, content_length = %lld",
                esp_http_client_get_status_code(client),
                esp_http_client_get_content_length(client));
    } else {
        ESP_LOGE(TAG, "HTTPS GET request failed: %s (0x%x)", esp_err_to_name(err), err);
    }

    esp_http_client_cleanup(client);
    vTaskDelete(NULL);
}

void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    wifi_init_sta();
    
    // Ensure SNTP is initialized for time synchronization, which is crucial for certificate validation
    // See Chapter 98 for SNTP details. For this example, we assume time might be set or
    // certificate check might be lenient if time is not perfectly synced yet on first boot.
    // esp_sntp_config_t sntp_config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
    // esp_netif_sntp_init(&sntp_config);
    // if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000)) != ESP_OK) {
    //    ESP_LOGW(TAG, "Failed to sync SNTP time!");
    // }


    xTaskCreate(&https_get_task, "https_get_task", 8192 * 2, NULL, 5, NULL); // Increased stack for TLS
}

main/CMakeLists.txt:

Add the following line if you are embedding a specific certificate named howsmyssl.pem:

Plaintext
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS ".")
# Add this line if you create howsmyssl.pem in the main directory:
# target_add_binary_data(${COMPONENT_TARGET} "howsmyssl.pem" TEXT)

If you are using esp_crt_bundle_attach, you don’t need target_add_binary_data.

Build and Flash Instructions:

  1. Set target, configure Wi-Fi.
  2. Enable Certificate Bundle: idf.py menuconfig -> Component config -> mbedTLS -> Certificate Bundle -> Check [*] Provide bundle of common CA certificates. Save and exit.
  3. Build: idf.py build
  4. Flash: idf.py -p /dev/ttyUSB0 flash monitor

Observe:

The ESP32 will connect to Wi-Fi and attempt the HTTPS GET request. The response from howsmyssl.com (JSON detailing TLS status) should be printed. If there are certificate verification errors, they will be logged.

Important Note on cert_pem vs crt_bundle_attach:

  • Using config.cert_pem = specific_ca_pem_string; requires you to manage and embed individual CA certificates.
  • Using config.crt_bundle_attach = esp_crt_bundle_attach; (and enabling CONFIG_MBEDTLS_CERTIFICATE_BUNDLE in menuconfig) tells esp_http_client to use a pre-compiled bundle of common CA certificates provided by ESP-IDF. This is often more convenient for general-purpose HTTPS to public servers, as it includes many common CAs.

Example 2: HTTPS POST Request

This example sends JSON data via an HTTPS POST request to https://httpbin.org/post.

Project Setup: (Similar to HTTPS GET example, new project https_post_example)

main/main.c:

(Wi-Fi init and event handler can be reused from Example 1. The main changes are in the https_post_task and esp_http_client_config_t.)

C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_http_client.h"
#include "esp_netif.h"
#include "esp_crt_bundle.h"

#define WIFI_SSID      CONFIG_EXAMPLE_WIFI_SSID
#define WIFI_PASS      CONFIG_EXAMPLE_WIFI_PASSWORD
#define MAX_FAILURES   10
#define MAX_HTTP_OUTPUT_BUFFER 2048
static const char *TAG = "https_post_example";

// Wi-Fi related code (event_handler, wifi_init_sta, s_wifi_event_group etc.)
// is identical to Example 1. For brevity, it's not repeated here.
// Please copy it from the HTTPS GET example.
// ... (Copy Wi-Fi init and event handler here) ...
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1
static int s_retry_num = 0;

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < MAX_FAILURES) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
    wifi_config_t wifi_config = {
        .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );
    ESP_LOGI(TAG, "wifi_init_sta finished.");
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
    if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "connected to ap SSID:%s", WIFI_SSID); }
    else if (bits & WIFI_FAIL_BIT) { ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID); }
    else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); }
}


// HTTP event handler (can be the same as Example 1 or a simplified one for POST response)
esp_err_t _http_event_handler_for_post(esp_http_client_event_t *evt) {
    static char response_data[MAX_HTTP_OUTPUT_BUFFER];
    static int response_data_len = 0;

    switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            response_data_len = 0; // Reset buffer length for new connection
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            if (response_data_len + evt->data_len < MAX_HTTP_OUTPUT_BUFFER) {
                memcpy(response_data + response_data_len, evt->data, evt->data_len);
                response_data_len += evt->data_len;
            } else {
                ESP_LOGW(TAG, "Response buffer overflow");
            }
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            response_data[response_data_len] = '\0'; // Null-terminate
            ESP_LOGI(TAG, "Received HTTPS POST Response (len %d):\n%s", response_data_len, response_data);
            response_data_len = 0; // Reset for next potential request
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
            int mbedtls_err = 0;
            esp_err_t err = esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
            if (err != 0) {
                 ESP_LOGI(TAG, "Last esp_tls error: 0x%x", err);
                 ESP_LOGI(TAG, "Last mbedtls error: 0x%x", mbedtls_err);
            }
            response_data_len = 0;
            break;
        case HTTP_EVENT_REDIRECT:
            ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
            esp_http_client_set_redirection(evt->client);
            break;
    }
    return ESP_OK;
}


static void https_post_task(void *pvParameters)
{
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    ESP_LOGI(TAG, "Connected to Wi-Fi, starting HTTPS POST task.");

    esp_http_client_config_t config = {
        .url = "https://httpbin.org/post",
        .method = HTTP_METHOD_POST,
        .event_handler = _http_event_handler_for_post,
        .transport_type = HTTP_TRANSPORT_OVER_SSL,
        .crt_bundle_attach = esp_crt_bundle_attach, // Use ESP-IDF's certificate bundle
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    const char *post_data = "{\"device\":\"ESP32\", \"value\":42, \"secure\":true}";
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_post_field(client, post_data, strlen(post_data));

    ESP_LOGI(TAG, "Performing HTTPS POST to %s", config.url);
    ESP_LOGI(TAG, "POST data: %s", post_data);

    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTPS POST Status = %d, content_length = %lld",
                esp_http_client_get_status_code(client),
                esp_http_client_get_content_length(client));
    } else {
        ESP_LOGE(TAG, "HTTPS POST request failed: %s (0x%x)", esp_err_to_name(err), err);
    }

    esp_http_client_cleanup(client);
    vTaskDelete(NULL);
}

void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    wifi_init_sta();
    // SNTP init recommended for time sync

    xTaskCreate(&https_post_task, "https_post_task", 8192 * 2, NULL, 5, NULL); // Increased stack
}

Build and Flash Instructions: (Same as HTTPS GET example, ensure Certificate Bundle is enabled in menuconfig)

Observe:

The ESP32 will send a secure POST request. The response from httpbin.org/post (echoing your request details) will be printed.

Certificate Handling Alternatives

Instead of embedding certificates directly in code or using the global bundle:

Method Description Pros Cons ESP-IDF Implementation
Embedding Specific CA Certificate(s) in Code The PEM-formatted CA certificate string is directly included in the firmware (e.g., as a C string or binary data). – Simple for known, fixed servers.
– No filesystem dependency.
– Fast access.
– Firmware update needed to change/add certificates.
– Increases firmware size for each certificate.
– Less flexible if server’s CA changes.
Set cert_pem in esp_http_client_config_t. Use target_add_binary_data in CMake.
Using ESP-IDF Certificate Bundle Leverages a pre-compiled bundle of common, trusted CA certificates provided by ESP-IDF. – Convenient for general public servers.
– Covers many common CAs.
– Managed by ESP-IDF updates.
– Increases firmware size significantly.
– Might not include very specific or private CAs.
– Bundle updates depend on ESP-IDF release cycle.
Set crt_bundle_attach = esp_crt_bundle_attach in config. Enable CONFIG_MBEDTLS_CERTIFICATE_BUNDLE.
Storing Certificates in NVS or Filesystem (SPIFFS/LittleFS) CA certificates are stored in a flash partition (NVS or a filesystem) and read at runtime. – Certificates can be updated without full firmware OTA (e.g., via a separate provisioning process).
– More flexible for dynamic CA management.
– Can store many certificates without recompiling main firmware.
– More complex to implement (read, parse from flash).
– Slower initial read compared to embedded.
– Requires filesystem/NVS initialization and management code.
– Flash wear if updated frequently.
Read file into a buffer, then pass pointer to cert_pem and set cert_len. Requires custom code for file I/O.

Variant Notes

  • Hardware Cryptographic Acceleration:
    • ESP32, ESP32-S2, ESP32-S3: These variants have dedicated hardware accelerators for cryptographic operations like AES, SHA, RSA, and ECC. This significantly speeds up TLS handshakes and data encryption/decryption compared to pure software implementations.
    • ESP32-C3: Early versions relied more on software for some crypto, but later ROM versions and IDF improvements have optimized this. It generally performs well for typical IoT use cases.
    • ESP32-C6, ESP32-H2: These newer RISC-V based chips also include hardware crypto support, offering good TLS performance.
    • The impact of hardware acceleration is most noticeable during the TLS handshake, which is computationally intensive.
  • Memory Usage (RAM and Flash):
    • mbedTLS Library: The TLS library itself adds to the firmware size (flash).
    • Session Buffers: During a TLS session, mbedTLS requires RAM for input/output buffers, certificate parsing, and session state. This can be significant (tens of KBs).
    • Task Stack Size: Tasks performing HTTPS operations often require larger stack sizes (e.g., 8KB or more, as seen in examples) compared to plain HTTP or other tasks, due to the depth of function calls within mbedTLS.
    • Certificate Storage: Embedding certificates directly increases firmware size. Storing them in NVS/SPIFFS uses flash partition space. The ESP-IDF certificate bundle also adds to firmware size.
    • Resource-constrained variants (e.g., those with less RAM) need careful management. Consider optimizing mbedTLS configuration in menuconfig if memory is extremely tight (Component config -> mbedTLS -> Memory allocation strategy / Tweaks).
  • Performance Impact:
    • Latency: The TLS handshake adds noticeable latency to the initial connection setup (can be hundreds of milliseconds to seconds, depending on server, network, and ESP32 variant).
    • Throughput: Encryption and decryption of data add CPU overhead, which can slightly reduce maximum data throughput compared to plain TCP or HTTP, though often network bandwidth is the bottleneck for typical IoT payloads.
ESP32 Variant Hardware Cryptographic Acceleration Support (Relevant to TLS) Impact on TLS
ESP32 (Original) AES, SHA, RSA, ECC (Elliptic Curve Cryptography) Significant speed-up for TLS handshakes and symmetric encryption/decryption compared to pure software implementations.
ESP32-S2 AES, SHA, RSA, ECC, Digital Signature (DS) peripheral for RSA/ECC private key operations. Similar to ESP32, with potential further optimization for private key operations in TLS client authentication (less common) or server scenarios.
ESP32-S3 AES, SHA, RSA, ECC, Digital Signature (DS), HMAC. Strong hardware support, good performance for TLS operations. HMAC acceleration can benefit integrity checks.
ESP32-C3 (RISC-V) AES, SHA, RSA (software assisted for some aspects in early ROMs, improved in later versions/IDF), Digital Signature (DS). ECC support varies. Generally good TLS performance for typical IoT use cases. Performance can depend on specific crypto operations and IDF/ROM versions.
ESP32-C6 (RISC-V) AES, SHA, RSA, ECC, Digital Signature (DS), HMAC. Wi-Fi 6 and BLE 5. Modern hardware crypto support similar to ESP32-S3, offering good TLS performance.
ESP32-H2 (RISC-V) AES, SHA, RSA, ECC, Digital Signature (DS), HMAC. Thread/Zigbee and BLE 5. Strong hardware crypto support, good TLS performance for its target applications (low-power mesh).

For most common IoT applications (sending sensor data, receiving commands), all ESP32 variants are capable of performing HTTPS, but developers should be mindful of the increased resource usage and handshake latency.

Memory Effects:

Resource Component Memory Type Impact of TLS Considerations / Notes
mbedTLS Library Flash Adds significantly to the firmware size (tens to over a hundred KBs depending on configuration). Can be configured in menuconfig to enable/disable features, affecting size.
TLS Session Buffers RAM (Heap) mbedTLS requires RAM for input/output buffers (typically 16KB each for send/receive during handshake, configurable), certificate parsing, and session state. Can be tens of KBs per active TLS session. Buffer sizes can be tuned in mbedTLS config. Critical for resource-constrained devices. Dynamic allocation is common.
Task Stack Size RAM (Stack) Tasks performing HTTPS operations (which involve deep calls into mbedTLS) require larger stack sizes (e.g., 8KB – 16KB or more) compared to non-TLS tasks to prevent stack overflows. Monitor stack high water mark during development. Insufficient stack is a common cause of crashes.
Certificate Storage (Embedded) Flash Each embedded CA certificate or the ESP-IDF certificate bundle adds directly to the firmware size. The bundle can be large (e.g., ~100KB+). Choose between specific certs or the bundle based on needs vs. flash constraints.
Certificate Storage (Filesystem/NVS) Flash (Partition) Uses space in the NVS or filesystem partition, not directly in the app firmware binary. Requires RAM to read the certificate into a buffer at runtime.
General Heap Usage RAM (Heap) Various dynamic allocations occur within mbedTLS and esp_tls for context structures, certificate data, etc. Careful memory management and monitoring are essential.

Performance Implications:

Performance Aspect Impact of TLS Details and Influencing Factors
Connection Latency (Initial Setup) Increased The TLS handshake adds significant latency to the initial connection establishment. This involves multiple round trips between client and server, and computationally intensive cryptographic operations (especially asymmetric crypto like RSA/ECC).
Can range from hundreds of milliseconds to several seconds.
Data Throughput (Post-Handshake) Slightly Reduced Symmetric encryption/decryption of application data adds CPU overhead. This can slightly reduce the maximum data throughput compared to plain TCP or HTTP.
However, for typical IoT payloads (small to moderate data sizes), network bandwidth or server processing time is often the primary bottleneck, not TLS processing on the ESP32 (especially with hardware crypto acceleration).
CPU Utilization Increased Cryptographic operations, especially during the handshake, consume CPU cycles. Hardware accelerators on ESP32 variants significantly mitigate this, but there’s still more CPU work compared to non-TLS connections.
Power Consumption Increased (during active TLS operations) Higher CPU utilization and potentially more radio time (due to handshake round trips) can lead to increased power consumption, particularly during connection setup. For battery-powered devices, this is a consideration. TLS session resumption can help reduce handshake overhead for subsequent connections.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Missing or Incorrect CA Root Certificate – Connection fails immediately.
esp_http_client_perform() returns errors like ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME, ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST.
– mbedTLS errors like X509_BADCERT_NOT_TRUSTED, MBEDTLS_ERR_X509_CERT_VERIFY_FAILED. (Check with esp_tls_get_and_clear_last_error).
– Ensure correct Root CA certificate for the server is provided via cert_pem in esp_http_client_config_t.
– For common public servers, use crt_bundle_attach = esp_crt_bundle_attach and enable CONFIG_MBEDTLS_CERTIFICATE_BUNDLE in menuconfig.
– Verify the full certificate chain of the server (e.g., using a browser or OpenSSL) to identify the correct Root CA. Do not use the server’s own certificate or an intermediate as the root.
Certificate Expired or Not Yet Valid – Connection fails during TLS handshake.
– mbedTLS errors like X509_BADCERT_EXPIRED or X509_BADCERT_FUTURE.
– This is often a server-side issue. Inform server admin.
Crucially, ensure ESP32 has correct system time. Implement SNTP time synchronization (see Chapter 98). Incorrect ESP32 time can lead to misinterpreting valid certificates as expired/future.
Hostname Mismatch (CN/SAN Mismatch) – Connection fails during TLS handshake after certificate received.
– mbedTLS error like X509_BADCERT_CN_MISMATCH or a generic trust/verification failure.
– Ensure the hostname in your URL (e.g., https://myapi.example.com) exactly matches the Common Name (CN) or one of the Subject Alternative Names (SANs) in the server’s certificate.
– Check for typos or using IP address when certificate is for a hostname.
– Temporarily use skip_common_name_check = true (or similar, name varies by IDF version) for debugging only; never in production.
Insufficient Task Stack Size – Random crashes, reboots, or undefined behavior, especially during HTTPS operations (handshake).
– Guru Meditation Errors related to stack overflow.
– Increase the stack size for the task performing HTTPS requests (e.g., xTaskCreate(…, …, 8192*2, …) or more).
– Monitor stack high water mark using uxTaskGetStackHighWaterMark() during development.
Outdated Certificate Bundle or Embedded Certificate – Previously working connections start failing, especially to servers that have updated their CAs.
– Symptoms similar to “Missing/Incorrect CA”.
– Keep ESP-IDF updated to get the latest certificate bundle.
– If embedding specific CA certs, periodically check their validity and update them in your firmware if they expire or are replaced by the CA.
Wi-Fi or Network Connectivity Issues esp_http_client_perform() returns errors like ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST, ESP_ERR_HTTP_CONNECT, or timeouts.
– ESP32 not getting an IP address.
– Verify Wi-Fi credentials and AP connectivity.
– Check network reachability to the server (DNS resolution, firewall rules).
– Ensure IP stack is initialized correctly.
Incorrectly Formatted cert_pem String – Certificate parsing errors within mbedTLS.
– Symptoms similar to “Missing/Incorrect CA”.
– Ensure the PEM string includes the full —–BEGIN CERTIFICATE—– and —–END CERTIFICATE—– lines.
– Ensure no extra characters or incorrect line endings (use \n for newlines in C strings).
– Copy directly from a valid PEM file.
Server Requires Client Certificate (Mutual TLS – mTLS) – Handshake failure, server might close connection.
– mbedTLS errors related to client authentication or missing certificate (MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE).
– If the server requires mTLS, you need to configure client_cert_pem and client_key_pem (and potentially client_key_password) in esp_http_client_config_t.
– This is an advanced setup not covered by default examples.

Tip: When debugging HTTPS issues, temporarily setting skip_common_name_check = true or skip_cert_common_name_check = true (depending on IDF version, check esp_http_client_config_t fields) in the config can help isolate if the problem is CN mismatch vs. other trust issues. Never leave this enabled in production!

Exercises

  1. Secure Weather API Client:
    • Modify Exercise 1 from Chapter 93 (Fetch and Parse Weather Data) to use HTTPS.
    • Find a weather API that supports HTTPS (most do).
    • Obtain and configure the necessary CA certificate (or use the ESP-IDF certificate bundle).
    • Ensure the data is fetched securely and parsed.
  2. Securely POST ESP32 Health Data:
    • Modify Exercise 2 from Chapter 93 (POST ESP32 Chip Information).
    • Change the endpoint to https://httpbin.org/post.
    • Ensure the chip information is POSTed over HTTPS.
    • Verify the response from httpbin.org.
  3. Certificate Pinning (Conceptual Research & Discussion):
    • Research the concept of “certificate pinning” or “public key pinning.”
    • Discuss its advantages (enhanced security against compromised CAs) and disadvantages (maintenance overhead if pinned certs change).
    • How might you implement a basic form of certificate pinning with esp_http_client? (Hint: You might use a specific server certificate via cert_pem instead of just a root CA, or check the public key of the received certificate). This is more of a research and design exercise.
  4. Error Analysis from esp_tls_get_and_clear_last_error:
    • Intentionally cause an HTTPS connection to fail (e.g., by using a wrong CA certificate for cert_pem, or connecting to a server whose certificate is expired or uses a self-signed cert without providing the CA).
    • In the HTTP_EVENT_DISCONNECTED or HTTP_EVENT_ERROR handler, use esp_tls_get_and_clear_last_error() to get the specific esp_tls error and the underlying mbedTLS error code.
    • Look up the mbedTLS error code (e.g., in mbedtls/error.h or online) to understand the precise reason for the failure. Log these error codes.

Summary

  • HTTPS (HTTP Secure) encrypts HTTP communication using TLS/SSL, providing confidentiality, integrity, and server authentication.
  • TLS/SSL relies on a handshake process to negotiate security parameters and establish shared secret keys.
  • PKI and X.509 digital certificates are fundamental for authentication. Clients verify server certificates against a list of trusted Root CAs.
  • The ESP-IDF esp_http_client supports HTTPS by setting transport_type = HTTP_TRANSPORT_OVER_SSL and providing server CA certificate information (via cert_pem or crt_bundle_attach).
  • Accurate system time on the ESP32 is crucial for validating certificate validity periods.
  • Hardware cryptographic acceleration in many ESP32 variants improves TLS performance.
  • TLS operations increase RAM, flash, and task stack requirements compared to plain HTTP.
  • Troubleshooting HTTPS often involves checking certificates, CAs, system time, and interpreting TLS-specific error codes.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top