Secure WebSockets on ESP32 with a CA Certificate

Adding TLS validation to an ESP32 project is an important step towards a more secure connection.

In this post, we will open a secure WebSocket connection and use a CA certificate to verify the identity of the remote server.

For this example, we will connect the ESP32 to echo.websocket.org, a public WebSocket echo server commonly used for testing.

 


 

Requirements

You will need:

  • SSID – your Wi-Fi network name

  • Password – the corresponding Wi-Fi password

  • Root CA certificate used by echo.websocket.org

 


 

Getting the Root Certificate

We can obtain the root certificate used in the TLS chain by using OpenSSL.

Run the following command:

openssl s_client -showcerts -connect echo.websocket.org:443

 

This command prints the full TLS certificate chain presented by the server.
The output will contain several certificates in PEM format, like this:


-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

The last certificate in the chain usually corresponds to the Root CA.
We will use this certificate in the Arduino script.


Windows users

OpenSSL is not available natively on Windows. A convenient option is to install Git for Windows, which includes OpenSSL inside Git Bash. You can then run the command above directly from the Git Bash terminal.

Alternatively, you can refer to this post where I explain how to collect the certificate trust chain of a website using a Python script.

Getting the Complete SSL Trust Chain of a Website

 


 

Arduino code


Before uploading, replace the following values:

  • SSID – your Wi-Fi network name

  • Password – the corresponding Wi-Fi password

  • Root CA certificate used by echo.websocket.org

 

#include <WiFi.h>
#include <WebSocketsClient.h>

const char* ssid = "******";
const char* password = "******";

const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
******
-----END CERTIFICATE-----
)EOF";

WebSocketsClient webSocket;

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {

  switch(type) {

    case WStype_CONNECTED:
      Serial.println("Connected to server");

      webSocket.sendTXT("Hello from ESP32");

      break;

    case WStype_TEXT:
      Serial.print("Received: ");
      Serial.println((char*)payload);
      break;

    case WStype_DISCONNECTED:
      Serial.println("Disconnected");
      break;

  }
}

void setup() {

  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  Serial.println("WiFi connected");

  webSocket.beginSslWithCA("echo.websocket.org", 443, "/", ca_cert);

  webSocket.onEvent(webSocketEvent);
}

void loop() {

  webSocket.loop();

}

 

 

If the certificate chain presented by the server matches the trusted CA, the connection is accepted. Otherwise, the connection will be rejected.

 

This process ensures that the ESP32 is communicating with the expected server and helps protect the device against man-in-the-middle attacks.

 



Expected Output
 

WiFi connected
Connected to server
Received: Request served by ...
Received: Hello from ESP32

 


ESP32