Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiFiServerSecure: Cache SSL sessions #7774

Merged
merged 9 commits into from
Dec 22, 2020
22 changes: 21 additions & 1 deletion doc/esp8266wifi/bearssl-server-secure-class.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -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 <bearssl-client-secure-class.rst#sessions-resuming-connections-fast>`__ 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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-----
Expand Down Expand Up @@ -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", [](){
Expand Down
20 changes: 20 additions & 0 deletions libraries/ESP8266WiFi/examples/BearSSL_Server/BearSSL_Server.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
Expand Down
6 changes: 6 additions & 0 deletions libraries/ESP8266WiFi/keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ X509List KEYWORD1
PrivateKey KEYWORD1
PublicKey KEYWORD1
Session KEYWORD1
ServerSession KEYWORD1
ServerSessions KEYWORD1
ESP8266WiFiGratuitous KEYWORD1


Expand Down Expand Up @@ -191,10 +193,14 @@ getMFLNStatus KEYWORD2
setRSACert KEYWORD2
setECCert KEYWORD2
setClientTrustAnchor KEYWORD2
setCache KEYWORD2

#CertStoreBearSSL
initCertStore KEYWORD2

#ServerSessions
size KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################
Expand Down
16 changes: 16 additions & 0 deletions libraries/ESP8266WiFi/src/BearSSLHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
48 changes: 46 additions & 2 deletions libraries/ESP8266WiFi/src/BearSSLHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,61 @@ 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;

public:
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:
Expand All @@ -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);
Expand Down
18 changes: 12 additions & 6 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,16 @@ 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();
_iobuf_in_size = iobuf_in_size;
_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();
Expand All @@ -142,15 +143,16 @@ 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();
_iobuf_in_size = iobuf_in_size;
_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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 13 additions & 8 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
6 changes: 6 additions & 0 deletions libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

};

Expand Down