Description
Basic Infos
- [ X] This issue complies with the issue POLICY doc.
- [X ] I have read the documentation at readthedocs and the issue is not addressed there.
- [X ] I have tested that the issue is present in current master branch (aka latest git).
- [X ] I have searched the issue tracker for a similar issue.
- If there is a stack dump, I have decoded it.
- [X ] I have filled out all fields below.
Platform
- Hardware: [ESP-12]
- Core Version: [2.5.2]
- Development Env: [Arduino IDE]
- Operating System: [Ubuntu]
Settings in IDE
- Module: [Nodemcu]
- Flash Mode: [qio]
- Flash Size: [4MB]
- lwip Variant: [v2 Lower Memory]
- Reset Method: [none]
- Flash Frequency: [40Mhz]
- CPU Frequency: [160MHz]
- Upload Using: [SERIAL]
- Upload Speed: [115200] (serial upload only)
Problem Description
I wish to use BearSSL (TLS) for secure socket connection to servers (for example, a MQTT broker running on a RPi). I suppose a domestic LAN infrastructure based on a cheap ISP router (so, no DNS server is available for LAN hostnames).
I would like to select the best solution for that scenario. I wish server & client authentication and, of course, strong encryption with a high security level immune to MITM attacks.
I have considered three options for server authentication: 1 setTrustAnchors(), 2 setKnownKey() and 3 setFingerprint().
The option 1 setTrustAnchors() seems very promising but BearSSL implementation is not very suitable for cheap IoT solutions. If CN (x509) is not equal to hostname (client->connect(hostname, port)), the connection to the server is rejected with code 56 (Expected server name was not found in the chain). This implementation is probably 100% compliant with the TLS specification, but it requires specific certs for each installation. And this is useless for many IoT solutions. Some tools like openssl s_client or python module ssl allow connections to servers without hostname verifications (using only x509 cert). I believe that such less restrictive option would be very useful for ESP8266 too and I would like to recommend to include it in a future release.
The option 2 setKnownKey() avoids the mentioned problem. According to the comments of the example BearSSL_validation.ino, the option seems secure in relation with MITM attacks too. However no chain verification is provided using this method. So we have to store several public keys if we wish to contact different servers.
The option 3 setFingerprint() is apparently not secure enough: "The SHA-1 fingerprint of an X.509 certificate can be used to validate it instead of the whole certificate. This is not nearly as secure as real X.509 validation, but is better than nothing". I do not know if the handshake involves the use of the private key on the server side. If so, the method could be considered secure and immune to MITM attacks, IMHO. But the lack of documentation and the comment is a serious problem here. In any case, the limitations of the setTrustAnchors() method related to hostname and CN do not apply in this case. So, if the method would be secure, this will be my preferred option. But, it is really insecure this option?.
Summary:
-
Could be considered a less restrictive option for setTrustAnchors() allowing connections to other hostnames not matching with CN?
-
Is it really insecure the method setFingerprint()?
-
Could be improved the BearSSL documentation related to ESP8266?
Thanks for you help.
MCVE Sketch
// Example of the different modes of the X.509 validation options
// in the WiFiClientBearSSL object
//
// Mar 2018 by Earle F. Philhower, III
// May 2019 by Luis Marmisa
// Released to the public domain
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <time.h>
#ifndef STASSID
#define STASSID "myssid"
#define STAPSK "mysecret"
#endif
const char *ssid = STASSID;
const char *pass = STAPSK;
const char *hostname = "server";
const char *hostip = "192.168.1.77";
const uint16_t port = 8267;
// Set time via NTP, as required for x.509 validation
void setClock() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
while (now < 2 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("");
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print("Current time: ");
Serial.print(asctime(&timeinfo));
}
// Try and connect using a WiFiClientBearSSL and write a text
void writeMsg(BearSSL::WiFiClientSecure *client, const char *host, const uint16_t port, const char *text) {
Serial.printf("Trying: %s:%d...\n", host, port);
client->connect(host, port);
if (!client->connected()) {
int errorcode;
char errortext[128];
if (client) {
errorcode = client->getLastSSLError(errortext, 128);
Serial.printf("SSL error %d -> %s\n", errorcode, errortext);
Serial.println("----- Can't connect -----");
}
return;
}
Serial.println("+++++ Connected! +++++");
client->write(text);
client->write("\r\n");
client->stop();
}
void FingerprintValidation(const char *host) {
Serial.println("1: sha-1 fingerprint validation");
BearSSL::WiFiClientSecure client;
static const char fp[] PROGMEM = "DB:90:C1:20:00:B0:45:19:FB:B4:01:D1:11:34:A8:7A:B0:6E:CE:1C";
client.setFingerprint(fp);
writeMsg(&client, host, port, "1 -> fingerprint");
}
void KnownKeyValidation(const char *host) {
// Extracted by: openssl x509 -pubkey -noout -in servercert.pem
static const char pubkey[] PROGMEM = R"KEY(
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu08Sl7TtqbMQYoJgAjoA
I0eUlP9iXj5MRnMX1nmJFA8t5bXUsX+hVdlqjKk+JWCOQgQjeRI/HyPm7h/MT+gg
W+JogwdGqwYMUqxOig/59/xbA6R3Y2533QQjv6ukURUKXcCPcagVS1CHVC6bjO8T
EIQbdVHbO2ghP7Lf82nGhCN0Ve8yMyRqWG5joX+t5UXiEK2zhoAjZFVqWVv2kxcc
enZNtxmdhcw/HOpCmAj+iTTfgbOkzNRHDZ1jwUj676ZswF1A6yAxdoBQJFwZ8AK5
koJg5WVNZa5XyTpKt7v5JGCj9wtB1axXW83jT0kgLFfjyrpxMkdhEODCwO02Ws/I
NQIDAQAB
-----END PUBLIC KEY-----
)KEY";
Serial.println("2: Public key validation");
BearSSL::WiFiClientSecure client;
BearSSL::PublicKey key(pubkey);
client.setKnownKey(&key);
writeMsg(&client, host, port, "2 -> public key");
}
void ServerCertValidation(const char *host) {
static const char server_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIICnjCCAYYCCQCpQhZsdVjqPjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdj
dWNhX2NhMB4XDTE5MDUyMjA4MDkwMFoXDTMwMDUwNDA4MDkwMFowEDEOMAwGA1UE
AwwFamF2ZWEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7TxKXtO2p
sxBigmACOgAjR5SU/2JePkxGcxfWeYkUDy3ltdSxf6FV2WqMqT4lYI5CBCN5Ej8f
I+buH8xP6CBb4miDB0arBgxSrE6KD/n3/FsDpHdjbnfdBCO/q6RRFQpdwI9xqBVL
UIdULpuM7xMQhBt1Uds7aCE/st/zacaEI3RV7zIzJGpYbmOhf63lReIQrbOGgCNk
VWpZW/aTFxx6dk23GZ2FzD8c6kKYCP6JNN+Bs6TM1EcNnWPBSPrvpmzAXUDrIDF2
gFAkXBnwArmSgmDlZU1lrlfJOkq3u/kkYKP3C0HVrFdbzeNPSSAsV+PKunEyR2EQ
4MLA7TZaz8g1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEXsr+e3D7gmrjt+GyrO
WXo2y7vxp5tW4P0jfHymMVycEYR5bBW9Tg5Q31e4yqWTmhki+IBTKvNlJVu9CcBN
odrGi6eda6a+wY4rUR1oOxjbCHcid7yxz+H8cOGEdnplXNaS/UpJfM5lwVULpRZf
r68Zs/zRF5twQdP11GwMMRWvIsdIfEK8Er3BRDjdzdDLIBSQDC82iEXOkabVIXey
LhMtdiH6z8EeiAoz6XF/MTlL+T1m/qYDGzAqUqvqqR5hj8iRFfyfE+RnIz3koROx
l1TiD4zAfWCQKEyo1JIae9/qEzWnyIE6TmTLrebt8dMN9maNylU3v6x4c9XBc/XP
ooo=
-----END CERTIFICATE-----
)EOF";
Serial.println("3: Server cert validation");
BearSSL::WiFiClientSecure client;
BearSSL::X509List cert(server_cert);
client.setTrustAnchors(&cert);
writeMsg(&client, host, port, "3 -> server_cert");
}
void SecurityChainValidation(const char *host) {
static const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIICtzCCAZ+gAwIBAgIJAPKVT+1I8DbHMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
BAMMB2N1Y2FfY2EwHhcNMTkwNTIxMjAyNzAxWhcNMzAwODA3MjAyNzAxWjASMRAw
DgYDVQQDDAdjdWNhX2NhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
rQDBuVPT89reR4MCkxJG4jryCbJnPmNZR4SKtWONQ1S2MGBsbteQrDHLOQ/QBL7R
zb/zmOtEuBmfFe0glOz0lza573WttbKlneZ1GjRS934l5Oi+KqzgF1JeA+XaZo4A
ZaMBkYrtUrX/MZA7Eww0JprYIAN+b/vQwVsmKOsi/wUG3Jpe8sKoSsZHXt4uVOK5
DSCM31wEVSYVzVyQyNUCYyZ5RmBXNwcFARqygLwRbOgOsnNNBAX6FgIHi9WtpzLe
G0ikQ+mwRMpm9rb0cpFKOVIq/ALIiN3dD7PllGIAEZqCDqGujXCIabmanvDIxAvk
0V2Zz2HkK2eWSu9+9s0SXQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQAbuPMlLtG/GkNQ/l1DbtotlZ6pdLlSN7SiJMqMfbAFoyHS6Wv6
YkhLSvKiB8Ys3EqaJJWPl9eRm2407WwMfJp805fglZULFcNeBo0nus87EFjCzJyn
NcUFgHSw2U0/2WgBylsugz389/2/qUCM5WsfMPu7FIkG335AXzocX+dUIh69xEsJ
vRe6EjKltYoDKARlKnXMwWIDHwbVFpKUOeHlMT/gag303Q/u20EqiSIUx4g5FIYe
qIMNJnHOSpmyynIZzFK706gmiRWUDb3dFVW5L/qGPl2i09JzWZH/Eb0rikFzM5bD
0svfc0CPfqlzgPXX6WF4H3k4338ZiyT/a7BI
-----END CERTIFICATE-----
)EOF";
Serial.println("4: CA cert validation");
BearSSL::WiFiClientSecure client;
BearSSL::X509List cert(ca_cert);
client.setTrustAnchors(&cert);
writeMsg(&client, host, port, "4 -> CA");
}
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.println("");
setClock();
FingerprintValidation(hostname);
FingerprintValidation(hostip);
KnownKeyValidation(hostname);
KnownKeyValidation(hostip);
ServerCertValidation(hostname);
ServerCertValidation(hostip);
SecurityChainValidation(hostname);
SecurityChainValidation(hostip);
}
void loop() {
// Nothing to do here
}
### Debug Messages
1: sha-1 fingerprint validation
Trying: server:8267...
+++++ Connected! +++++
1: sha-1 fingerprint validation
Trying: 192.168.1.77:8267...
+++++ Connected! +++++
2: Public key validation
Trying: server:8267...
+++++ Connected! +++++
2: Public key validation
Trying: 192.168.1.77:8267...
+++++ Connected! +++++
3: Server cert validation
Trying: server:8267...
+++++ Connected! +++++
3: Server cert validation
Trying: 192.168.1.77:8267...
SSL error 56 -> Expected server name was not found in the chain.
----- Can't connect -----
4: CA cert validation
Trying: server:8267...
+++++ Connected! +++++
4: CA cert validation
Trying: 192.168.1.77:8267...
SSL error 56 -> Expected server name was not found in the chain.
----- Can't connect -----