From b1b66a4b43ce5913b8038e1644c31593e35b27b8 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 21 Sep 2018 14:30:59 -0700 Subject: [PATCH 1/3] Add SSL Session capability to speed reconnections SSL Sessions enable most of the SSL handshake to be skipped when both client and server agree to use them. Add a BearSSLSession class and an optional setting to the SSL client to enable this. Note that SSL sessions are unrelated to HTTP sessions. They are ephemeral and only relate to the SSL parameters, not anything at the HTTP protocol level. Fixes #4796 --- .../BearSSL_Sessions/BearSSL_Sessions.ino | 153 ++++++++++++++++++ libraries/ESP8266WiFi/src/BearSSLHelpers.h | 17 ++ .../src/WiFiClientSecureBearSSL.cpp | 13 +- .../ESP8266WiFi/src/WiFiClientSecureBearSSL.h | 7 + 4 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino diff --git a/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino b/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino new file mode 100644 index 0000000000..3f7ac2b0f6 --- /dev/null +++ b/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino @@ -0,0 +1,153 @@ +// Example of using SSL sessions to speed up SSL connection initiation +// +// September 2018 by Earle F. Philhower, III +// Released to the public domain + +#include +#include + +const char *ssid="...."; +const char *pass="...."; + +const char * host = "api.github.com"; +const uint16_t port = 443; +const char * path = "/"; + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println(); + + Serial.printf("Connecting to %s\n", ssid); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nConnected"); + Serial.println("IP Address: "); + Serial.println(WiFi.localIP()); + + // Set up time to allow for certificate validation + configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 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 to specified host:port and dump HTTP response +void fetchURL(BearSSL::WiFiClientSecure *client, const char *host, const uint16_t port, const char *path) { + if (!path) { + path = "/"; + } + + Serial.printf("Trying: %s:443...", host); + client->connect(host, port); + if (!client->connected()) { + Serial.printf("*** Can't connect. ***\n-------\n"); + return; + } + Serial.printf("Connected!\n-------\n"); + client->write("GET "); + client->write(path); + client->write(" HTTP/1.0\r\nHost: "); + client->write(host); + client->write("\r\nUser-Agent: ESP8266\r\n"); + client->write("\r\n"); + uint32_t to = millis() + 5000; + if (client->connected()) { + do { + char tmp[32]; + memset(tmp, 0, 32); + int rlen = client->read((uint8_t*)tmp, sizeof(tmp) - 1); + yield(); + if (rlen < 0) { + break; + } + // Only print out first line up to \r, then abort connection + char *nl = strchr(tmp, '\r'); + if (nl) { + *nl = 0; + Serial.print(tmp); + break; + } + Serial.print(tmp); + } while (millis() < to); + } + client->stop(); + Serial.printf("\n-------\n\n"); +} + + +void loop() { + static const char digicert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- +)EOF"; + uint32_t start, finish; + BearSSL::WiFiClientSecure client; + BearSSLX509List cert(digicert); + client.setTrustAnchors(&cert); + + Serial.printf("Connecting without sessions..."); + start = millis(); + fetchURL(&client, host, port, path); + finish = millis(); + Serial.printf("Total time: %dms\n", finish - start); + + BearSSLSession session; + client.setSession(&session); + Serial.printf("Connecting with an unitialized session..."); + start = millis(); + fetchURL(&client, host, port, path); + finish = millis(); + Serial.printf("Total time: %dms\n", finish - start); + + Serial.printf("Connecting with the just initialized session..."); + start = millis(); + fetchURL(&client, host, port, path); + finish = millis(); + Serial.printf("Total time: %dms\n", finish - start); + + Serial.printf("Connecting again with the initialized session..."); + start = millis(); + fetchURL(&client, host, port, path); + finish = millis(); + Serial.printf("Total time: %dms\n", finish - start); + + delay(10000); // Avoid DDOSing github +} + diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.h b/libraries/ESP8266WiFi/src/BearSSLHelpers.h index 55b4970a42..6cfe4e4aec 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.h +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.h @@ -119,4 +119,21 @@ class BearSSLX509List { br_x509_trust_anchor *_ta; }; +// Opaque object which wraps the BearSSL SSL session to make repeated connections +// significantly faster. Completely optional. +namespace BearSSL { + class WiFiClientSecure; +}; + +class BearSSLSession { + friend class BearSSL::WiFiClientSecure; + + public: + BearSSLSession() { memset(&_session, 0, sizeof(_session)); } + private: + br_ssl_session_parameters *getSession() { return &_session; } + // The actual BearSSL ession information + br_ssl_session_parameters _session; +}; + #endif diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp index 45b7eec62c..23594ff7b9 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp @@ -72,6 +72,7 @@ void WiFiClientSecure::_clear() { _recvapp_len = 0; _oom_err = false; _deleteChainKeyTA = false; + _session = nullptr; } void WiFiClientSecure::_clearAuthenticationSettings() { @@ -179,8 +180,11 @@ void WiFiClientSecure::stop() { _client->abort(); } WiFiClient::stop(); - // Only if we've already connected, clear the connection options + // Only if we've already connected, istore session params and clear the connection options if (_handshake_done) { + if (_session) { + br_ssl_engine_get_session_parameters(_eng, _session->getSession()); + } _clearAuthenticationSettings(); } _freeSSL(); @@ -805,7 +809,12 @@ bool WiFiClientSecure::_connectSSL(const char* hostName) { _cert_issuer_key_type, br_ec_get_default(), br_ecdsa_sign_asn1_get_default()); } - if (!br_ssl_client_reset(_sc.get(), hostName, 0)) { + // Restore session from the storage spot, if present + if (_session) { + br_ssl_engine_set_session_parameters(_eng, _session->getSession()); + } + + if (!br_ssl_client_reset(_sc.get(), hostName, _session?1:0)) { _freeSSL(); return false; } diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h index 365cb9bd01..a699902154 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h +++ b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h @@ -57,6 +57,9 @@ class WiFiClientSecure : public WiFiClient { void stop() override; void flush() override; + // Allow sessions to be saved/restored automatically to a memory area + void setSession(BearSSLSession *session) { _session = session; } + // Don't validate the chain, just accept whatever is given. VERY INSECURE! void setInsecure() { _clearAuthenticationSettings(); @@ -163,6 +166,10 @@ class WiFiClientSecure : public WiFiClient { bool _handshake_done; bool _oom_err; + // Optional storage space pointer for session parameters + // Will be used on connect and updated on close + BearSSLSession *_session; + bool _use_insecure; bool _use_fingerprint; uint8_t _fingerprint[20]; From 8c7c9523083a9ef4c0bae124a0c1190d60ae7b50 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 23 Sep 2018 12:14:17 -0700 Subject: [PATCH 2/3] Commit the example code changes, d'oh! --- .../examples/BearSSL_Sessions/BearSSL_Sessions.ino | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino b/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino index 3f7ac2b0f6..4fd8fbcac2 100644 --- a/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino +++ b/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino @@ -120,10 +120,10 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep uint32_t start, finish; BearSSL::WiFiClientSecure client; BearSSLX509List cert(digicert); - client.setTrustAnchors(&cert); Serial.printf("Connecting without sessions..."); start = millis(); + client.setTrustAnchors(&cert); fetchURL(&client, host, port, path); finish = millis(); Serial.printf("Total time: %dms\n", finish - start); @@ -132,18 +132,21 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep client.setSession(&session); Serial.printf("Connecting with an unitialized session..."); start = millis(); + client.setTrustAnchors(&cert); fetchURL(&client, host, port, path); finish = millis(); Serial.printf("Total time: %dms\n", finish - start); Serial.printf("Connecting with the just initialized session..."); start = millis(); + client.setTrustAnchors(&cert); fetchURL(&client, host, port, path); finish = millis(); Serial.printf("Total time: %dms\n", finish - start); Serial.printf("Connecting again with the initialized session..."); start = millis(); + client.setTrustAnchors(&cert); fetchURL(&client, host, port, path); finish = millis(); Serial.printf("Total time: %dms\n", finish - start); From 7406c334737f4a8050914491829b60c155ab60cc Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 23 Sep 2018 13:14:38 -0700 Subject: [PATCH 3/3] Fix example style check error. --- .../examples/BearSSL_Sessions/BearSSL_Sessions.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino b/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino index 4fd8fbcac2..55604a3f73 100644 --- a/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino +++ b/libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino @@ -6,8 +6,8 @@ #include #include -const char *ssid="...."; -const char *pass="...."; +const char *ssid = "...."; +const char *pass = "...."; const char * host = "api.github.com"; const uint16_t port = 443;