HTTPS Server on the ESP8266 NodeMCU

Generate a self signed SSL certificate and use it to secure an ESP8266 web server

NodeMcu is a development board based on ESP8266. This microcontroller is made for IoT applications and features WiFi connectivity. There is an easy way to program ESP8266 boards using Arduino IDE. This is what I will use here too. Nowadays, internet security is very important. Maybe you'll use ESP8266 only in the local network or you'll allow external access to it. Unless it's behind a proxy, leaving it unsecured is not a good idea. In the last years, most of the websites switched to HTTPS and modern browsers display warnings when requesting an unsecured HTTP page.

To offer secured content, a server greets the client with a trusted certificate, issued by a known authority. The certificate has a limited time validity and must be renewed from time to time. In this post, we'll generate a SSL certificate and use it on ESP8266 web server. You can buy the certificate from a known authority or you can generate it for free on your computer. I'll use the second method although is comes with a glitch. The browser will not trust the certificate. But that's OK, you can trust it as long as you generated it and you keep it private.

SSL encryption makes use of a public certificate (with public key) and a private key, known only by the server. In order to decrypt traffic between devices, someone should own both the public key and the private one. As long as you keep the private key... private, the connection is safe even if the browser complains it doesn't trust your self signed certificate.

Remember that ESP8266 is not optimized for SSL cryptography. You should set clock frequency to 160 MHz when using SSL. Even so, some exchanges between server and clients may take too long and trigger a software reset. However, using the latest SDK for Arduino IDE, I was able to run the HTTPS server without resetting the board.

Certificate and key

You may get the certificate and key from a trusted CA, if you want to. For ESP8266 compatibility, the certificate must use SHA256 and the key length must be either 512 or 1024 bits. A 512 bits RSA key will make ESP8266 respond faster, but it is considered weak by modern browsers. For better security, use 1024 bits RSA key. The trusted CA should give you both the certificate and the private RSA key.

Like I said, I intend to use OpenSSL to generate the certificate and the private RSA key. Getting OpenSSL on Linux is easy since most distributions already have it installed and you can find it in software repositories otherwise. Windows builds are available on slproweb.com. Choose the exe, Light version for your system architecture. Run the installer.

Launch openssl on the command line, from the folder where you want certificate and key to be generated. Here's how it's done in Linux terminal and Windows PowerShell:

cd ~/Desktop
openssl
cd ~/Desktop
&"C:/Program Files/OpenSSL-Win64/bin/openssl.exe"

It is possible to generate both key and certificate using a single command:

req -x509 -newkey rsa:1024 -sha256 -keyout key.txt -out cert.txt -days 365 -nodes -subj "/C=RO/ST=B/L=Bucharest/O=OneTransistor [RO]/OU=OneTransistor/CN=esp8266.local" -addext subjectAltName=DNS:esp8266.local

or multiple commands:

genrsa -out key.txt 1024
rsa -in key.txt -out key.txt
req -sha256 -new -nodes -key key.txt -out cert.csr -subj '/C=RO/ST=B/L=Bucharest/O=OneTransistor [RO]/OU=OneTransistor/CN=esp8266.local' -addext subjectAltName=DNS:esp8266.local
x509 -req -sha256 -days 365 -in cert.csr -signkey key.txt -out cert.txt

In the first command, rsa:1024 specifies key length in bits, while in the second approach, last argument of genrsa is used for this. The -days parameter specifies certificate validity starting from the generation time.

You'll find the key in key.txt file and the certificate in cert.txt. Before generating them, is useful to know about the parameters of -subj argument, which you can set as you want.

• C - country, short name
• ST - state or province
• L - locality or city
• O - organization
• OU - organizational unit
• CN - common name (domain name)

The subjectAltName parameter must contain the domain name(s) where your server is accessible. It can specify also IP addresses like this: subjectAltName=DNS:esp8266.local,IP=192.168.1.10. When requesting a webpage secured with this certificate, the browser will complain it does not know the CA ("ERR_CERT_AUTHORITY_INVALID" is the message displayed by Google Chrome).

The web server

Let's get to the Arduino code. If you type just esp8266.local in the browser's address bar, the initial connection attempt will be made over HTTP (port 80). This means that if you only have the HTTPS server running (port 443) you'll get a connection refused over HTTP. Since this is not user friendly, we'll run two servers on ESP8266, one over HTTP which will send 301 headers pointing to the HTTPS one. Redirection is instant.

The key and certificate must be pasted into the sketch:

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <ESP8266WebServerSecure.h>

const char *ssid = "ssid";
const char *dname = "esp8266";

ESP8266WebServer serverHTTP(80);

static const char serverCert[] PROGMEM = R"EOF(
paste here content of "cert.txt"
)EOF";

static const char serverKey[] PROGMEM =  R"EOF(
paste here content of "key.txt"
)EOF";


In setup() function, before configuring the servers, ESP8266 must know the current date and time from a NTP server. That's easy since we have configTime() function which takes as first parameter the GMT offset in seconds. After getting the time, we can start the HTTP server and configure it to handle requests by responding with the redirection header. Lastly, we configure HTTPS server with key and certificate and turn it on.

void setup() {
pinMode(D0, OUTPUT);
Serial.begin(115200);

if (!connectToWifi()) {
delay(60000);
ESP.restart();
}

configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");

serverHTTP.on("/", secureRedirect);
serverHTTP.begin();

server.on("/", showWebpage);
server.begin();

}


In loop() we make sure both servers handle requests.

void loop() {
serverHTTP.handleClient();
server.handleClient();
MDNS.update();
}


HTTPS redirection routine is simple. However, no matter of you reach the server by the multicast DNS name or by its IP, this function will point to the mDNS name and that could be an issue for clients that do not support mDNS. I could have sent the local IP in the redirection header, but that would raise other certificate errors. Since ESP8266 is a client in a DHCP enabled network, it gets an IP from a router. And since I can't know what is that IP in advance, I can't generate a certificate with that IP in SAN field or CN attribute.

void secureRedirect() {
serverHTTP.send(301, "text/plain", "");
}


Server's answer is managed by showWebpage() function. LED status is changed using HTTP GET method.

The server page viewed in Google Chrome

No errors in Chrome

There is a way to get rid of the "Not secure" error in Google Chrome. First of all you must use 1024 bits key since 512 is considered weak.

The certificate must be imported into the system's (browser's) trusted list. On Linux you can go straight to chrome://settings/certificates, the Authorities tab. On Windows operating systems, go to Settings - Advanced - Manage Certificates and select Trusted Root Certification Authorities tab. Click the Import button and select your generated cert.txt file (select all files type in the open dialog to see it). Import it and give it trust for site identification. On Windows, after import, find and select it in the list, then click Advanced. Check Client Authentication. Close the browser and reopen it to see the changes. In Windows, the certificate installation is system wide.

Check site security (Chrome - F12)

Resources

The complete source code is availabe on GitHub. Modify SSID and password to match your network and don't forget to use your own certificate since this one is no longer secure after being made public.