From 032db6fc81371df529798c460693e351f0503fc5 Mon Sep 17 00:00:00 2001 From: Zakary Kamal Ismail Date: Tue, 22 Dec 2020 00:13:43 -0500 Subject: [PATCH] WiFiServerSecure: Cache SSL sessions (#7774) * WiFiServerSecure: Cache the SSL sessions * Add SSL session caching to HTTPS server examples * Document server SSL session caching * Fix an incomplete sentence in the documentation * Document BearSSL::Session * Use the number of sessions instead of the buffer size in ServerSessions' constructors --- .../bearssl-server-secure-class.rst | 22 ++++++++- .../HelloServerBearSSL/HelloServerBearSSL.ino | 4 ++ .../BearSSL_Server/BearSSL_Server.ino | 20 ++++++++ libraries/ESP8266WiFi/keywords.txt | 6 +++ libraries/ESP8266WiFi/src/BearSSLHelpers.cpp | 16 +++++++ libraries/ESP8266WiFi/src/BearSSLHelpers.h | 48 ++++++++++++++++++- .../src/WiFiClientSecureBearSSL.cpp | 18 ++++--- .../ESP8266WiFi/src/WiFiClientSecureBearSSL.h | 21 ++++---- .../src/WiFiServerSecureBearSSL.cpp | 4 +- .../ESP8266WiFi/src/WiFiServerSecureBearSSL.h | 6 +++ 10 files changed, 146 insertions(+), 19 deletions(-) diff --git a/doc/esp8266wifi/bearssl-server-secure-class.rst b/doc/esp8266wifi/bearssl-server-secure-class.rst index dffefc0de..f25ce384e 100644 --- a/doc/esp8266wifi/bearssl-server-secure-class.rst +++ b/doc/esp8266wifi/bearssl-server-secure-class.rst @@ -8,7 +8,7 @@ Implements a TLS encrypted server with optional client certificate validation. setBufferSizes(int recv, int xmit) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Similar to the `BearSSL::WiFiClientSecure` method, sets the receive and transmit buffer sizes. Note that servers cannot request a buffer size from the client, so if these are shrunk and the client tries to send a chunk larger than the receive buffer, it will always fail. This must be called before the server is +Similar to the `BearSSL::WiFiClientSecure` method, sets the receive and transmit buffer sizes. Note that servers cannot request a buffer size from the client, so if these are shrunk and the client tries to send a chunk larger than the receive buffer, it will always fail. Needs to be called before `begin()` Setting Server Certificates ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -33,6 +33,26 @@ setECCert(const BearSSL::X509List \*chain, unsigned cert_issuer_key_type, const Sets an elliptic curve certificate and key for the server. Needs to be called before `begin()`. +Client sessions (Resuming connections fast) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The TLS handshake process takes a long time because of all the back and forth between the client and the server. You can shorten it by caching the clients' sessions which will skip a few steps in the TLS handshake. In order for this to work, your client also needs to cache the session. `BearSSL::WiFiClientSecure `__ can do that as well as modern web browers. + +Here are the kind of performance improvements that you'll be able to see for TLS handshakes with an ESP8266 with it's clock set at 160MHz on a network with fairly low latency: + +* With an EC key of 256 bits, a request taking ~360ms without caching takes ~60ms with caching. +* With an RSA key of 2048 bits, a request taking ~1850ms without caching takes ~70ms with caching. + +setCache(BearSSL::ServerSessions \*cache) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sets the cache for the server's sessions. When choosing the size of the cache, remember that each client session takes 100 bytes. If you setup a cache for 10 sessions, it will take 1000 bytes. Needs to be called before `begin()` + +When creating the cache, you can use any of the 2 available constructors: + +* `BearSSL::ServerSessions(ServerSession *sessions, uint32_t size)`: Creates a cache with the given buffer and number of sessions. +* `BearSSL::ServerSessions(uint32_t size)`: Dynamically allocates a cache for the given number of sessions. + Requiring Client Certificates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino b/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino index ab36e5480..8e9f4cd5b 100644 --- a/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino +++ b/libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino @@ -25,6 +25,7 @@ const char* ssid = STASSID; const char* password = STAPSK; BearSSL::ESP8266WebServerSecure server(443); +BearSSL::ServerSessions serverCache(5); static const char serverCert[] PROGMEM = R"EOF( -----BEGIN CERTIFICATE----- @@ -132,6 +133,9 @@ void setup(void){ server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey)); + // Cache SSL sessions to accelerate the TLS handshake. + server.getServer().setCache(&serverCache); + server.on("/", handleRoot); server.on("/inline", [](){ diff --git a/libraries/ESP8266WiFi/examples/BearSSL_Server/BearSSL_Server.ino b/libraries/ESP8266WiFi/examples/BearSSL_Server/BearSSL_Server.ino index d27382284..972f0ebab 100644 --- a/libraries/ESP8266WiFi/examples/BearSSL_Server/BearSSL_Server.ino +++ b/libraries/ESP8266WiFi/examples/BearSSL_Server/BearSSL_Server.ino @@ -138,6 +138,21 @@ GBEnkz4KpKv7TkHoW+j7F5EMcLcSrUIpyw== #endif +#define CACHE_SIZE 5 // Number of sessions to cache. +#define USE_CACHE // Enable SSL session caching. + // Caching SSL sessions shortens the length of the SSL handshake. + // You can see the performance improvement by looking at the + // Network tab of the developper tools of your browser. +//#define DYNAMIC_CACHE // Whether to dynamically allocate the cache. + +#if defined(USE_CACHE) && defined(DYNAMIC_CACHE) +// Dynamically allocated cache. +BearSSL::ServerSessions serverCache(CACHE_SIZE); +#elif defined(USE_CACHE) +// Statically allocated cache. +ServerSession store[CACHE_SIZE]; +BearSSL::ServerSessions serverCache(store, CACHE_SIZE); +#endif void setup() { Serial.begin(115200); @@ -169,6 +184,11 @@ void setup() { server.setECCert(serverCertList, BR_KEYTYPE_KEYX|BR_KEYTYPE_SIGN, serverPrivKey); #endif + // Set the server's cache +#if defined(USE_CACHE) + server.setCache(&serverCache); +#endif + // Actually start accepting connections server.begin(); } diff --git a/libraries/ESP8266WiFi/keywords.txt b/libraries/ESP8266WiFi/keywords.txt index ec3c0228b..d3c1bfa38 100644 --- a/libraries/ESP8266WiFi/keywords.txt +++ b/libraries/ESP8266WiFi/keywords.txt @@ -24,6 +24,8 @@ X509List KEYWORD1 PrivateKey KEYWORD1 PublicKey KEYWORD1 Session KEYWORD1 +ServerSession KEYWORD1 +ServerSessions KEYWORD1 ESP8266WiFiGratuitous KEYWORD1 @@ -191,10 +193,14 @@ getMFLNStatus KEYWORD2 setRSACert KEYWORD2 setECCert KEYWORD2 setClientTrustAnchor KEYWORD2 +setCache KEYWORD2 #CertStoreBearSSL initCertStore KEYWORD2 +#ServerSessions +size KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp index ea1546965..15a09f209 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp @@ -872,6 +872,22 @@ bool X509List::append(const uint8_t *derCert, size_t derLen) { return true; } +ServerSessions::~ServerSessions() { + if (_isDynamic && _store != nullptr) + delete _store; +} + +ServerSessions::ServerSessions(ServerSession *sessions, uint32_t size, bool isDynamic) : + _size(sessions != nullptr ? size : 0), + _store(sessions), _isDynamic(isDynamic) { + if (_size > 0) + br_ssl_session_cache_lru_init(&_cache, (uint8_t*)_store, size * sizeof(ServerSession)); +} + +const br_ssl_session_cache_class **ServerSessions::getCache() { + return _size > 0 ? &_cache.vtable : nullptr; +} + // SHA256 hash for updater void HashSHA256::begin() { br_sha256_init( &_cc ); diff --git a/libraries/ESP8266WiFi/src/BearSSLHelpers.h b/libraries/ESP8266WiFi/src/BearSSLHelpers.h index 2e2251d8c..d91c8211d 100644 --- a/libraries/ESP8266WiFi/src/BearSSLHelpers.h +++ b/libraries/ESP8266WiFi/src/BearSSLHelpers.h @@ -133,6 +133,9 @@ class X509List { // significantly faster. Completely optional. class WiFiClientSecure; +// Cache for a TLS session with a server +// Use with BearSSL::WiFiClientSecure::setSession +// to accelerate the TLS handshake class Session { friend class WiFiClientSecureCtx; @@ -140,10 +143,51 @@ class Session { Session() { memset(&_session, 0, sizeof(_session)); } private: br_ssl_session_parameters *getSession() { return &_session; } - // The actual BearSSL ession information + // The actual BearSSL session information br_ssl_session_parameters _session; }; +// Represents a single server session. +// Use with BearSSL::ServerSessions. +typedef uint8_t ServerSession[100]; + +// Cache for the TLS sessions of multiple clients. +// Use with BearSSL::WiFiServerSecure::setCache +class ServerSessions { + friend class WiFiClientSecureCtx; + + public: + // Uses the given buffer to cache the given number of sessions and initializes it. + ServerSessions(ServerSession *sessions, uint32_t size) : ServerSessions(sessions, size, false) {} + + // Dynamically allocates a cache for the given number of sessions and initializes it. + // If the allocation of the buffer wasn't successfull, the value + // returned by size() will be 0. + ServerSessions(uint32_t size) : ServerSessions(size > 0 ? new ServerSession[size] : nullptr, size, true) {} + + ~ServerSessions(); + + // Returns the number of sessions the cache can hold. + uint32_t size() { return _size; } + + private: + ServerSessions(ServerSession *sessions, uint32_t size, bool isDynamic); + + // Returns the cache's vtable or null if the cache has no capacity. + const br_ssl_session_cache_class **getCache(); + + // Size of the store in sessions. + uint32_t _size; + // Store where the informations for the sessions are stored. + ServerSession *_store; + // Whether the store is dynamically allocated. + // If this is true, the store needs to be freed in the destructor. + bool _isDynamic; + + // Cache of the server using the _store. + br_ssl_session_cache_lru _cache; +}; + // Updater SHA256 hash and signature verification class HashSHA256 : public UpdaterHashClass { public: @@ -170,7 +214,7 @@ class SigningVerifier : public UpdaterVerifyClass { private: PublicKey *_pubKey; }; - + // Stack thunked versions of calls extern "C" { extern unsigned char *thunk_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len); diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp index 4a0d63af6..48dc531a5 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp @@ -124,7 +124,8 @@ WiFiClientSecureCtx::~WiFiClientSecureCtx() { WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client, const X509List *chain, const PrivateKey *sk, - int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta) { + int iobuf_in_size, int iobuf_out_size, ServerSessions *cache, + const X509List *client_CA_ta) { _clear(); _clearAuthenticationSettings(); stack_thunk_add_ref(); @@ -132,7 +133,7 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client, _iobuf_out_size = iobuf_out_size; _client = client; _client->ref(); - if (!_connectSSLServerRSA(chain, sk, client_CA_ta)) { + if (!_connectSSLServerRSA(chain, sk, cache, client_CA_ta)) { _client->unref(); _client = nullptr; _clear(); @@ -142,7 +143,8 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client, WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type, const PrivateKey *sk, - int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta) { + int iobuf_in_size, int iobuf_out_size, ServerSessions *cache, + const X509List *client_CA_ta) { _clear(); _clearAuthenticationSettings(); stack_thunk_add_ref(); @@ -150,7 +152,7 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client, _iobuf_out_size = iobuf_out_size; _client = client; _client->ref(); - if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, client_CA_ta)) { + if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, cache, client_CA_ta)) { _client->unref(); _client = nullptr; _clear(); @@ -1178,7 +1180,7 @@ bool WiFiClientSecureCtx::_installServerX509Validator(const X509List *client_CA_ // Called by WiFiServerBearSSL when an RSA cert/key is specified. bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain, - const PrivateKey *sk, + const PrivateKey *sk, ServerSessions *cache, const X509List *client_CA_ta) { _freeSSL(); _oom_err = false; @@ -1206,6 +1208,8 @@ bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain, sk ? sk->getRSA() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN, br_rsa_private_get_default(), br_rsa_pkcs1_sign_get_default()); br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size); + if (cache != nullptr) + br_ssl_server_set_cache(_sc_svr.get(), cache->getCache()); if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) { DEBUG_BSSL("_connectSSLServerRSA: Can't install serverX509check\n"); return false; @@ -1222,7 +1226,7 @@ bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain, // Called by WiFiServerBearSSL when an elliptic curve cert/key is specified. bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain, unsigned cert_issuer_key_type, const PrivateKey *sk, - const X509List *client_CA_ta) { + ServerSessions *cache, const X509List *client_CA_ta) { #ifndef BEARSSL_SSL_BASIC _freeSSL(); _oom_err = false; @@ -1250,6 +1254,8 @@ bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain, sk ? sk->getEC() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN, cert_issuer_key_type, br_ssl_engine_get_ec(_eng), br_ecdsa_i15_sign_asn1); br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size); + if (cache != nullptr) + br_ssl_server_set_cache(_sc_svr.get(), cache->getCache()); if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) { DEBUG_BSSL("_connectSSLServerEC: Can't install serverX509check\n"); return false; diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h index 9d140b97a..858f573e2 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h +++ b/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h @@ -179,15 +179,18 @@ class WiFiClientSecureCtx : public WiFiClient { // Methods for handling server.available() call which returns a client connection. friend class WiFiClientSecure; // access to private context constructors WiFiClientSecureCtx(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type, - const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta); + const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, ServerSessions *cache, + const X509List *client_CA_ta); WiFiClientSecureCtx(ClientContext* client, const X509List *chain, const PrivateKey *sk, - int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta); + int iobuf_in_size, int iobuf_out_size, ServerSessions *cache, + const X509List *client_CA_ta); // RSA keyed server - bool _connectSSLServerRSA(const X509List *chain, const PrivateKey *sk, const X509List *client_CA_ta); + bool _connectSSLServerRSA(const X509List *chain, const PrivateKey *sk, + ServerSessions *cache, const X509List *client_CA_ta); // EC keyed server bool _connectSSLServerEC(const X509List *chain, unsigned cert_issuer_key_type, const PrivateKey *sk, - const X509List *client_CA_ta); + ServerSessions *cache, const X509List *client_CA_ta); // X.509 validators differ from server to client bool _installClientX509Validator(); // Set up X509 validator for a client conn. @@ -290,13 +293,15 @@ class WiFiClientSecure : public WiFiClient { // Methods for handling server.available() call which returns a client connection. friend class WiFiServerSecure; // Server needs to access these constructors WiFiClientSecure(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type, - const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta): - _ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, client_CA_ta)) { + const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, ServerSessions *cache, + const X509List *client_CA_ta): + _ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) { } WiFiClientSecure(ClientContext* client, const X509List *chain, const PrivateKey *sk, - int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta): - _ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, client_CA_ta)) { + int iobuf_in_size, int iobuf_out_size, ServerSessions *cache, + const X509List *client_CA_ta): + _ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) { } }; // class WiFiClientSecure diff --git a/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp b/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp index 6478c8df9..5f7bcac07 100644 --- a/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp +++ b/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp @@ -79,13 +79,13 @@ WiFiClientSecure WiFiServerSecure::available(uint8_t* status) { (void) status; // Unused if (_unclaimed) { if (_sk && _sk->isRSA()) { - WiFiClientSecure result(_unclaimed, _chain, _sk, _iobuf_in_size, _iobuf_out_size, _client_CA_ta); + WiFiClientSecure result(_unclaimed, _chain, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta); _unclaimed = _unclaimed->next(); result.setNoDelay(_noDelay); DEBUGV("WS:av\r\n"); return result; } else if (_sk && _sk->isEC()) { - WiFiClientSecure result(_unclaimed, _chain, _cert_issuer_key_type, _sk, _iobuf_in_size, _iobuf_out_size, _client_CA_ta); + WiFiClientSecure result(_unclaimed, _chain, _cert_issuer_key_type, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta); _unclaimed = _unclaimed->next(); result.setNoDelay(_noDelay); DEBUGV("WS:av\r\n"); diff --git a/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.h b/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.h index 21916e3cb..716f00eaa 100644 --- a/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.h +++ b/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.h @@ -42,6 +42,11 @@ class WiFiServerSecure : public WiFiServer { _iobuf_out_size = xmit; } + // Sets the server's cache to the given one. + void setCache(ServerSessions *cache) { + _cache = cache; + } + // Set the server's RSA key and x509 certificate (required, pick one). // Caller needs to preserve the chain and key throughout the life of the server. void setRSACert(const X509List *chain, const PrivateKey *sk); @@ -69,6 +74,7 @@ class WiFiServerSecure : public WiFiServer { int _iobuf_in_size = BR_SSL_BUFSIZE_INPUT; int _iobuf_out_size = 837; const X509List *_client_CA_ta = nullptr; + ServerSessions *_cache = nullptr; };