Skip to content

Commit

Permalink
Add SSL Session capability to speed reconnections (#5160)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
earlephilhower authored Sep 28, 2018
1 parent 8e11836 commit 6314093
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 2 deletions.
156 changes: 156 additions & 0 deletions libraries/ESP8266WiFi/examples/BearSSL_Sessions/BearSSL_Sessions.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Example of using SSL sessions to speed up SSL connection initiation
//
// September 2018 by Earle F. Philhower, III
// Released to the public domain

#include <ESP8266WiFi.h>
#include <time.h>

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);

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);

BearSSLSession session;

This comment has been minimized.

Copy link
@marcelstoer

marcelstoer Jan 1, 2019

Contributor

The documentation says

After the connection has had .close() called on it, serialize the BearSSL::Session object to stable storage (EEPROM, RTC RAM, etc.) and restore it before trying to reconnect. See the BearSSL_Sessions example for a detailed example.

Would you mind adding an example to show exactly that: how the session can survive deep-sleep if persisted to RTC RAM?

This comment has been minimized.

Copy link
@marcelstoer

marcelstoer Jan 1, 2019

Contributor

Sorry, I just realized it's nearly identical to #5160 (comment)

This comment has been minimized.

Copy link
@earlephilhower

earlephilhower Jan 1, 2019

Author Collaborator

Not sure if you have your answer or not, but I don't really have a setup to test deepSleep at the moment. Basically all you have to do is (in p-code) memcpy(rtc_ram, &session, sizeof(session)); before sleep, then the reverse memcpy(&session, rtc_ram, sizeof(session)); after you wake up. Assuming, of course, you have enough RTCRAM available to hold it. Could write it to a SPIFFS file, etc. as well.

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);

delay(10000); // Avoid DDOSing github
}

17 changes: 17 additions & 0 deletions libraries/ESP8266WiFi/src/BearSSLHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 11 additions & 2 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ void WiFiClientSecure::_clear() {
_recvapp_len = 0;
_oom_err = false;
_deleteChainKeyTA = false;
_session = nullptr;
_cipher_list = NULL;
_cipher_cnt = 0;
}
Expand Down Expand Up @@ -177,8 +178,11 @@ void WiFiClientSecure::setBufferSizes(int recv, int xmit) {

bool WiFiClientSecure::stop(unsigned int maxWaitMs) {
bool ret = WiFiClient::stop(maxWaitMs); // calls our virtual flush()
// Only if we've already connected, clear the connection options
// Only if we've already connected, store session params and clear the connection options
if (_handshake_done) {
if (_session) {
br_ssl_engine_get_session_parameters(_eng, _session->getSession());
}
_clearAuthenticationSettings();
}
_freeSSL();
Expand Down Expand Up @@ -865,7 +869,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;
}
Expand Down
7 changes: 7 additions & 0 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class WiFiClientSecure : public WiFiClient {
bool flush(unsigned int maxWaitMs = 0) override;
bool stop(unsigned int maxWaitMs = 0) 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();
Expand Down Expand Up @@ -170,6 +173,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];
Expand Down

0 comments on commit 6314093

Please sign in to comment.