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 *password = "password"; const char *dname = "esp8266"; BearSSL::ESP8266WebServerSecure server(443); 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.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey)); server.on("/", showWebpage); server.begin(); Serial.println("Server is ready"); }
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.sendHeader("Location", String("https://esp8266.local"), true); 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.
Great job!.
ReplyDeleteI have been testing it with your certificate before I get mine and I get an error accessing the server either by IP adress or domain. The google chrome browser error is: DNS_PROBE_FINISHED_NXDOMAIN.
Some idea will be of great help.
Thanks a lot.
Make sure ESP8266 is running and connected to WiFi (see serial monitor log) and try to go to the IP address using Incognito/InPrivate mode (or clear browser cache). After every attempt to connect to server close the all Incognito tabs and reopen to access the server again. You're getting a strange error. If the ESP8266 server isn't running you will get ERR_ADDRESS_UNREACHABLE, not DNS errors.
DeleteThank you very much for your answer.I will work on it. You are very polite.
DeleteHi, I have the same error: "DNS_PROBE_FINISHED_NXDOMAIN" but only when I try to access with Google Chrome on Android phone.
DeleteCorrection: There should be ":" instead of "=" after IP in "subjectAltName=DNS:esp8266.local,IP=192.168.1.10"
ReplyDeleteThank you! I fixed it.
DeleteHi, I'm a Beginner and i tried your code, but am getting an error using ESP8266WebServerSecure = class esp8266webserver::ESP8266WebServerTemplate' has no member named 'setRSACert'
ReplyDeletewhat might be the problem. please guide me.
thank you
The line:
Deleteserver.setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
should be replaced with:
server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
Arduino: 1.8.10 (Linux), Board: "WeMos D1 R1, 80 MHz, Flash, Legacy (new can return nullptr), All SSL ciphers (most compatible), 4MB (FS:2MB OTA:~1019KB), v2 Lower Memory, Disabled, None, Only Sketch, 921600"
ReplyDelete/App/wmos-pro/wmos-pro.ino: In function 'void setup()':
wmos-pro:165:12: error: 'using ESP8266WebServerSecure = class esp8266webserver::ESP8266WebServerTemplate' has no member named 'setRSACert'
server.setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
^
Multiple libraries were found for "SPI.h"
Used: /root/.arduino15/packages/esp8266/hardware/esp8266/2.6.3/libraries/SPI
Multiple libraries were found for "Wire.h"
Used: /root/.arduino15/packages/esp8266/hardware/esp8266/2.6.3/libraries/Wire
Multiple libraries were found for "ArduinoJson.h"
Used: /root/Arduino/libraries/ArduinoJson
Multiple libraries were found for "ESP8266mDNS.h"
Used: /root/.arduino15/packages/esp8266/hardware/esp8266/2.6.3/libraries/ESP8266mDNS
Multiple libraries were found for "ESP8266WiFi.h"
Used: /root/Arduino/libraries/ESP8266WiFi
Not used: /root/.arduino15/packages/esp8266/hardware/esp8266/2.6.3/libraries/ESP8266WiFi
Multiple libraries were found for "ESP8266WebServer.h"
Used: /root/Arduino/libraries/ESP8266WebServer
Not used: /root/.arduino15/packages/esp8266/hardware/esp8266/2.6.3/libraries/ESP8266WebServer
Multiple libraries were found for "ESP8266HTTPClient.h"
Used: /root/Arduino/libraries/ESP8266HTTPClient
Not used: /root/.arduino15/packages/esp8266/hardware/esp8266/2.6.3/libraries/ESP8266HTTPClient
exit status 1
'using ESP8266WebServerSecure = class esp8266webserver::ESP8266WebServerTemplate' has no member named 'setRSACert'
This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
The line:
Deleteserver.setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
should be replaced with:
server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
The source code has been modified on GitHub to reflect this change.
I tried to edit the code and put my webpage in content, but when i acess the IP its all blank, can u help me???
ReplyDeleteDoes this work on the ESP32? Is there a way to have it generate it's on certificate on-board the ESP32?
ReplyDeleteNo, this does not work on ESP32.
DeleteHi. I am using esp8266 asyncwebserver on an IOT device I'm building to handle APIs (GET and POST). I would like to use the SSL webserver for authentication and change password functions only, and use non-ssl for everything else.
ReplyDeleteIs this possible?
Yeap, me too, I'm trying to implement the SSL webserver on esp8266 for the authentication login too. I this even possible to do so?
DeleteIt should be possible, but don't expect too much from ESP8266 with complex web server and SSL. I suggest, for reliable operation, to use plain HTTP server on ESP8266 and use a secured proxy or VPN to connect to it.
DeleteI followed these lines up to add an SSL webserver to an already existing project with webserver on esp8266. The normal userinterface should be still available via httpServer, but an api endpount should be available via httpsServer (in order mobil applications could consume it).
ReplyDeleteAll I got calling the website via https is an ERR_CONNECTION_CLOSED.
Hi, is there anyway we can use SSL with Websockets? or with ESPAsyncTCP and ESPAsyncWebServer? Thank yiu
ReplyDeleteHi, I would like to use a secure server on eap8266 in order to securely enter WiFi AP login/password - so that the esp8266 can connect (as a station later on) to such WiFi AP. This example seem to expect that the esp8266 is already connected to AP and if it is not then it resets itself - is that correct? If so, why is that important - just to be able to acquire current time? Can I enter the current time via non-secure http form (or possibly enter it automatically via jvascript that gets the time from the connected browser), then redirect to https? Would that work? Or is there another reason why esp8266 needs to be connected to Wifi before the Http(s) servers are started? Thank you.
ReplyDelete