TCP connection SSL support

Warning

SSL support is currently in experimental mode. API changes may occur in the future.

ESP-AT binary, running on Espressif chips, supports SLL connection types. Such connections, to work properly, require client or server certificates to be loaded to Espressif device.

With the recent update, July 29th, 2023, library has been updated to support AT commands for flash and MFG operations, allowing host microcontroller to load required certificates to the Espressif device.

Note

Minimum required ESP-AT library running on ESP device is now v3.2.0, which supports new AT+SYSMFG command, that is required to load custom data to the device.

Note

SSL connections mentioned on this page are secure from Espressif device towards network. Data between host MCU and Espressif MCU is not protected and may be exposed to an attacker

Prepare the certificate

Assuming we would like to establish connection to another server with secure SSL connection, ESP device shall have up to 3 certificates loaded in its own system flash. These are:

  • client_ca - Client root CA certificate - client uses this certificate to verify server. Example

  • client_cert - Client certificate. Example

  • client_key - Client private key. Example

ESP-AT website includes some test certificates, that could be used for test purposes: Description of all slots

LwESP repository contains aforementioned certificates in the certificates folder. There are 2 files for each certificate:

  • Original .crt or .key file

  • Original .crt or .key file converted to .hex array for easier include in the C project.

Load to ESP device

Loading can be done as part of custom AT firmware build, or by using AT commands. LwESP library has the support for system flash and manufacturing NVS data operation, that is required for certificate load.

All combined, steps to establish SSL connection is to:

  • Have certificates loaded to ESP-AT device with Espressif format

  • Have configured connections to use your certificates, if SSL type is used on them

  • Have valid time in ESP device. SNTP module can help with that

Example

Below is the up-to-date netconn API example using SSL connection. Example file is located in snippets/netconn_client_ssl.c

Netconn example with SSL
  1/*
  2 * Netconn client demonstrates how to connect as a client to server
  3 * using sequential API from separate thread.
  4 * 
  5 * it does not use callbacks to obtain connection status.
  6 * 
  7 * Demo connects to NETCONN_HOST at NETCONN_PORT and sends GET request header,
  8 * then waits for respond and expects server to close the connection accordingly.
  9 */
 10#include "lwesp/lwesp.h"
 11#include "lwesp/lwesp_netconn.h"
 12#include "netconn_client.h"
 13
 14/* Certificates, ready to be loaded to the flash */
 15static uint8_t client_ca[] = {
 16#include "../certificates/client_ca_generated_atpki.hex"
 17};
 18static uint8_t client_cert[] = {
 19#include "../certificates/client_cert_generated_atpki.hex"
 20};
 21static uint8_t client_key[] = {
 22#include "../certificates/client_key_generated_atpki.hex"
 23};
 24
 25/**
 26 * \brief           Host and port settings
 27 */
 28#define NETCONN_HOST "example.com"
 29#define NETCONN_PORT 443
 30
 31/**
 32 * \brief           Request header to send on successful connection
 33 */
 34static const char request_header[] = ""
 35                                     "GET / HTTP/1.1\r\n"
 36                                     "Host: " NETCONN_HOST "\r\n"
 37                                     "Connection: close\r\n"
 38                                     "\r\n";
 39
 40/**
 41 * \brief           Netconn client thread implementation
 42 * \param[in]       arg: User argument
 43 */
 44void
 45netconn_client_ssl_thread(void const* arg) {
 46    lwespr_t res;
 47    lwesp_pbuf_p pbuf;
 48    lwesp_netconn_p client;
 49    lwesp_sys_sem_t* sem = (void*)arg;
 50
 51    /* Make sure we are connected to access point first */
 52    while (!lwesp_sta_has_ip()) {
 53        lwesp_delay(1000);
 54    }
 55
 56    /*
 57     * First create a new instance of netconn
 58     * connection and initialize system message boxes
 59     * to accept received packet buffers
 60     */
 61    client = lwesp_netconn_new(LWESP_NETCONN_TYPE_SSL);
 62    if (client != NULL) {
 63        struct tm dt;
 64        uint32_t sntp_interval = 0;
 65        uint8_t sntp_en = 0;
 66
 67        /* Write data to coresponding manuf NVS */
 68        res = lwesp_mfg_write(LWESP_MFG_NAMESPACE_CLIENT_CA, "client_ca.0", LWESP_MFG_VALTYPE_BLOB, client_ca,
 69                              sizeof(client_ca), NULL, NULL, 1);
 70        res = lwesp_mfg_write(LWESP_MFG_NAMESPACE_CLIENT_CERT, "client_cert.0", LWESP_MFG_VALTYPE_BLOB, client_cert,
 71                              sizeof(client_cert), NULL, NULL, 1);
 72        res = lwesp_mfg_write(LWESP_MFG_NAMESPACE_CLIENT_KEY, "client_key.0", LWESP_MFG_VALTYPE_BLOB, client_key,
 73                              sizeof(client_key), NULL, NULL, 1);
 74
 75        /* Configure SSL for all connections */
 76        for (size_t i = 0; i < LWESP_CFG_MAX_CONNS; ++i) {
 77            lwesp_conn_ssl_set_config(i, 1, 0, 0, NULL, NULL, 1);
 78        }
 79
 80        /* Ensure SNTP is enabled, time is required for SSL */
 81        if (lwesp_sntp_get_config(&sntp_en, NULL, NULL, NULL, NULL, NULL, NULL, 1) == lwespOK) {
 82            if (!sntp_en) {
 83                lwesp_sntp_set_config(1, 2, NULL, NULL, NULL, NULL, NULL, 1);
 84            }
 85            lwesp_sntp_get_interval(&sntp_interval, NULL, NULL, 1);
 86            printf("SNTP interval: %u seconds\r\n", (unsigned)sntp_interval);
 87            do {
 88                lwesp_sntp_gettime(&dt, NULL, NULL, 1);
 89                if (dt.tm_year > 100) {
 90                    break;
 91                }
 92                lwesp_delay(1000);
 93            } while (1);
 94        }
 95
 96        /*
 97         * Connect to external server as client
 98         * with custom NETCONN_CONN_HOST and CONN_PORT values
 99         *
100         * Function will block thread until we are successfully connected (or not) to server
101         */
102        res = lwesp_netconn_connect(client, NETCONN_HOST, NETCONN_PORT);
103        if (res == lwespOK) { /* Are we successfully connected? */
104            printf("Connected to " NETCONN_HOST "\r\n");
105            res = lwesp_netconn_write(client, request_header, sizeof(request_header) - 1); /* Send data to server */
106            if (res == lwespOK) {
107                res = lwesp_netconn_flush(client); /* Flush data to output */
108            }
109            if (res == lwespOK) { /* Were data sent? */
110                printf("Data were successfully sent to server\r\n");
111
112                /*
113                 * Since we sent HTTP request,
114                 * we are expecting some data from server
115                 * or at least forced connection close from remote side
116                 */
117                do {
118                    /*
119                     * Receive single packet of data
120                     *
121                     * Function will block thread until new packet
122                     * is ready to be read from remote side
123                     *
124                     * After function returns, don't forgot the check value.
125                     * Returned status will give you info in case connection
126                     * was closed too early from remote side
127                     */
128                    res = lwesp_netconn_receive(client, &pbuf);
129                    if (res
130                        == lwespCLOSED) { /* Was the connection closed? This can be checked by return status of receive function */
131                        printf("Connection closed by remote side...\r\n");
132                        break;
133                    } else if (res == lwespTIMEOUT) {
134                        printf("Netconn timeout while receiving data. You may try multiple readings before deciding to "
135                               "close manually\r\n");
136                    }
137
138                    if (res == lwespOK && pbuf != NULL) { /* Make sure we have valid packet buffer */
139                        /*
140                         * At this point, read and manipulate
141                         * with received buffer and check if you expect more data
142                         *
143                         * After you are done using it, it is important
144                         * you free the memory, or memory leaks will appear
145                         */
146                        printf("Received new data packet of %d bytes\r\n", (int)lwesp_pbuf_length(pbuf, 1));
147                        lwesp_pbuf_free_s(&pbuf); /* Free the memory after usage */
148                    }
149                } while (1);
150            } else {
151                printf("Error writing data to remote host!\r\n");
152            }
153
154            /*
155             * Check if connection was closed by remote server
156             * and in case it wasn't, close it manually
157             */
158            if (res != lwespCLOSED) {
159                lwesp_netconn_close(client);
160            }
161        } else {
162            printf("Cannot connect to remote host %s:%d!\r\n", NETCONN_HOST, NETCONN_PORT);
163        }
164        lwesp_netconn_delete(client); /* Delete netconn structure */
165    }
166
167    printf("Terminating thread\r\n");
168    if (lwesp_sys_sem_isvalid(sem)) {
169        lwesp_sys_sem_release(sem);
170    }
171    lwesp_sys_thread_terminate(NULL); /* Terminate current thread */
172}