Skip to content

Commit 032db6f

Browse files
authored
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
1 parent 8add1fd commit 032db6f

File tree

10 files changed

+146
-19
lines changed

10 files changed

+146
-19
lines changed

doc/esp8266wifi/bearssl-server-secure-class.rst

+21-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Implements a TLS encrypted server with optional client certificate validation.
88
setBufferSizes(int recv, int xmit)
99
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1010

11-
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
11+
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()`
1212

1313
Setting Server Certificates
1414
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -33,6 +33,26 @@ setECCert(const BearSSL::X509List \*chain, unsigned cert_issuer_key_type, const
3333

3434
Sets an elliptic curve certificate and key for the server. Needs to be called before `begin()`.
3535

36+
Client sessions (Resuming connections fast)
37+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38+
39+
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.
40+
41+
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:
42+
43+
* With an EC key of 256 bits, a request taking ~360ms without caching takes ~60ms with caching.
44+
* With an RSA key of 2048 bits, a request taking ~1850ms without caching takes ~70ms with caching.
45+
46+
setCache(BearSSL::ServerSessions \*cache)
47+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48+
49+
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()`
50+
51+
When creating the cache, you can use any of the 2 available constructors:
52+
53+
* `BearSSL::ServerSessions(ServerSession *sessions, uint32_t size)`: Creates a cache with the given buffer and number of sessions.
54+
* `BearSSL::ServerSessions(uint32_t size)`: Dynamically allocates a cache for the given number of sessions.
55+
3656
Requiring Client Certificates
3757
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3858

libraries/ESP8266WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const char* ssid = STASSID;
2525
const char* password = STAPSK;
2626

2727
BearSSL::ESP8266WebServerSecure server(443);
28+
BearSSL::ServerSessions serverCache(5);
2829

2930
static const char serverCert[] PROGMEM = R"EOF(
3031
-----BEGIN CERTIFICATE-----
@@ -132,6 +133,9 @@ void setup(void){
132133

133134
server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
134135

136+
// Cache SSL sessions to accelerate the TLS handshake.
137+
server.getServer().setCache(&serverCache);
138+
135139
server.on("/", handleRoot);
136140

137141
server.on("/inline", [](){

libraries/ESP8266WiFi/examples/BearSSL_Server/BearSSL_Server.ino

+20
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ GBEnkz4KpKv7TkHoW+j7F5EMcLcSrUIpyw==
138138

139139
#endif
140140

141+
#define CACHE_SIZE 5 // Number of sessions to cache.
142+
#define USE_CACHE // Enable SSL session caching.
143+
// Caching SSL sessions shortens the length of the SSL handshake.
144+
// You can see the performance improvement by looking at the
145+
// Network tab of the developper tools of your browser.
146+
//#define DYNAMIC_CACHE // Whether to dynamically allocate the cache.
147+
148+
#if defined(USE_CACHE) && defined(DYNAMIC_CACHE)
149+
// Dynamically allocated cache.
150+
BearSSL::ServerSessions serverCache(CACHE_SIZE);
151+
#elif defined(USE_CACHE)
152+
// Statically allocated cache.
153+
ServerSession store[CACHE_SIZE];
154+
BearSSL::ServerSessions serverCache(store, CACHE_SIZE);
155+
#endif
141156

142157
void setup() {
143158
Serial.begin(115200);
@@ -169,6 +184,11 @@ void setup() {
169184
server.setECCert(serverCertList, BR_KEYTYPE_KEYX|BR_KEYTYPE_SIGN, serverPrivKey);
170185
#endif
171186

187+
// Set the server's cache
188+
#if defined(USE_CACHE)
189+
server.setCache(&serverCache);
190+
#endif
191+
172192
// Actually start accepting connections
173193
server.begin();
174194
}

libraries/ESP8266WiFi/keywords.txt

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ X509List KEYWORD1
2424
PrivateKey KEYWORD1
2525
PublicKey KEYWORD1
2626
Session KEYWORD1
27+
ServerSession KEYWORD1
28+
ServerSessions KEYWORD1
2729
ESP8266WiFiGratuitous KEYWORD1
2830

2931

@@ -191,10 +193,14 @@ getMFLNStatus KEYWORD2
191193
setRSACert KEYWORD2
192194
setECCert KEYWORD2
193195
setClientTrustAnchor KEYWORD2
196+
setCache KEYWORD2
194197

195198
#CertStoreBearSSL
196199
initCertStore KEYWORD2
197200

201+
#ServerSessions
202+
size KEYWORD2
203+
198204
#######################################
199205
# Constants (LITERAL1)
200206
#######################################

libraries/ESP8266WiFi/src/BearSSLHelpers.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,22 @@ bool X509List::append(const uint8_t *derCert, size_t derLen) {
872872
return true;
873873
}
874874

875+
ServerSessions::~ServerSessions() {
876+
if (_isDynamic && _store != nullptr)
877+
delete _store;
878+
}
879+
880+
ServerSessions::ServerSessions(ServerSession *sessions, uint32_t size, bool isDynamic) :
881+
_size(sessions != nullptr ? size : 0),
882+
_store(sessions), _isDynamic(isDynamic) {
883+
if (_size > 0)
884+
br_ssl_session_cache_lru_init(&_cache, (uint8_t*)_store, size * sizeof(ServerSession));
885+
}
886+
887+
const br_ssl_session_cache_class **ServerSessions::getCache() {
888+
return _size > 0 ? &_cache.vtable : nullptr;
889+
}
890+
875891
// SHA256 hash for updater
876892
void HashSHA256::begin() {
877893
br_sha256_init( &_cc );

libraries/ESP8266WiFi/src/BearSSLHelpers.h

+46-2
Original file line numberDiff line numberDiff line change
@@ -133,17 +133,61 @@ class X509List {
133133
// significantly faster. Completely optional.
134134
class WiFiClientSecure;
135135

136+
// Cache for a TLS session with a server
137+
// Use with BearSSL::WiFiClientSecure::setSession
138+
// to accelerate the TLS handshake
136139
class Session {
137140
friend class WiFiClientSecureCtx;
138141

139142
public:
140143
Session() { memset(&_session, 0, sizeof(_session)); }
141144
private:
142145
br_ssl_session_parameters *getSession() { return &_session; }
143-
// The actual BearSSL ession information
146+
// The actual BearSSL session information
144147
br_ssl_session_parameters _session;
145148
};
146149

150+
// Represents a single server session.
151+
// Use with BearSSL::ServerSessions.
152+
typedef uint8_t ServerSession[100];
153+
154+
// Cache for the TLS sessions of multiple clients.
155+
// Use with BearSSL::WiFiServerSecure::setCache
156+
class ServerSessions {
157+
friend class WiFiClientSecureCtx;
158+
159+
public:
160+
// Uses the given buffer to cache the given number of sessions and initializes it.
161+
ServerSessions(ServerSession *sessions, uint32_t size) : ServerSessions(sessions, size, false) {}
162+
163+
// Dynamically allocates a cache for the given number of sessions and initializes it.
164+
// If the allocation of the buffer wasn't successfull, the value
165+
// returned by size() will be 0.
166+
ServerSessions(uint32_t size) : ServerSessions(size > 0 ? new ServerSession[size] : nullptr, size, true) {}
167+
168+
~ServerSessions();
169+
170+
// Returns the number of sessions the cache can hold.
171+
uint32_t size() { return _size; }
172+
173+
private:
174+
ServerSessions(ServerSession *sessions, uint32_t size, bool isDynamic);
175+
176+
// Returns the cache's vtable or null if the cache has no capacity.
177+
const br_ssl_session_cache_class **getCache();
178+
179+
// Size of the store in sessions.
180+
uint32_t _size;
181+
// Store where the informations for the sessions are stored.
182+
ServerSession *_store;
183+
// Whether the store is dynamically allocated.
184+
// If this is true, the store needs to be freed in the destructor.
185+
bool _isDynamic;
186+
187+
// Cache of the server using the _store.
188+
br_ssl_session_cache_lru _cache;
189+
};
190+
147191
// Updater SHA256 hash and signature verification
148192
class HashSHA256 : public UpdaterHashClass {
149193
public:
@@ -170,7 +214,7 @@ class SigningVerifier : public UpdaterVerifyClass {
170214
private:
171215
PublicKey *_pubKey;
172216
};
173-
217+
174218
// Stack thunked versions of calls
175219
extern "C" {
176220
extern unsigned char *thunk_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len);

libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp

+12-6
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,16 @@ WiFiClientSecureCtx::~WiFiClientSecureCtx() {
124124

125125
WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
126126
const X509List *chain, const PrivateKey *sk,
127-
int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta) {
127+
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
128+
const X509List *client_CA_ta) {
128129
_clear();
129130
_clearAuthenticationSettings();
130131
stack_thunk_add_ref();
131132
_iobuf_in_size = iobuf_in_size;
132133
_iobuf_out_size = iobuf_out_size;
133134
_client = client;
134135
_client->ref();
135-
if (!_connectSSLServerRSA(chain, sk, client_CA_ta)) {
136+
if (!_connectSSLServerRSA(chain, sk, cache, client_CA_ta)) {
136137
_client->unref();
137138
_client = nullptr;
138139
_clear();
@@ -142,15 +143,16 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
142143
WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
143144
const X509List *chain,
144145
unsigned cert_issuer_key_type, const PrivateKey *sk,
145-
int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta) {
146+
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
147+
const X509List *client_CA_ta) {
146148
_clear();
147149
_clearAuthenticationSettings();
148150
stack_thunk_add_ref();
149151
_iobuf_in_size = iobuf_in_size;
150152
_iobuf_out_size = iobuf_out_size;
151153
_client = client;
152154
_client->ref();
153-
if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, client_CA_ta)) {
155+
if (!_connectSSLServerEC(chain, cert_issuer_key_type, sk, cache, client_CA_ta)) {
154156
_client->unref();
155157
_client = nullptr;
156158
_clear();
@@ -1178,7 +1180,7 @@ bool WiFiClientSecureCtx::_installServerX509Validator(const X509List *client_CA_
11781180

11791181
// Called by WiFiServerBearSSL when an RSA cert/key is specified.
11801182
bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain,
1181-
const PrivateKey *sk,
1183+
const PrivateKey *sk, ServerSessions *cache,
11821184
const X509List *client_CA_ta) {
11831185
_freeSSL();
11841186
_oom_err = false;
@@ -1206,6 +1208,8 @@ bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain,
12061208
sk ? sk->getRSA() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN,
12071209
br_rsa_private_get_default(), br_rsa_pkcs1_sign_get_default());
12081210
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
1211+
if (cache != nullptr)
1212+
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
12091213
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
12101214
DEBUG_BSSL("_connectSSLServerRSA: Can't install serverX509check\n");
12111215
return false;
@@ -1222,7 +1226,7 @@ bool WiFiClientSecureCtx::_connectSSLServerRSA(const X509List *chain,
12221226
// Called by WiFiServerBearSSL when an elliptic curve cert/key is specified.
12231227
bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain,
12241228
unsigned cert_issuer_key_type, const PrivateKey *sk,
1225-
const X509List *client_CA_ta) {
1229+
ServerSessions *cache, const X509List *client_CA_ta) {
12261230
#ifndef BEARSSL_SSL_BASIC
12271231
_freeSSL();
12281232
_oom_err = false;
@@ -1250,6 +1254,8 @@ bool WiFiClientSecureCtx::_connectSSLServerEC(const X509List *chain,
12501254
sk ? sk->getEC() : nullptr, BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN,
12511255
cert_issuer_key_type, br_ssl_engine_get_ec(_eng), br_ecdsa_i15_sign_asn1);
12521256
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
1257+
if (cache != nullptr)
1258+
br_ssl_server_set_cache(_sc_svr.get(), cache->getCache());
12531259
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
12541260
DEBUG_BSSL("_connectSSLServerEC: Can't install serverX509check\n");
12551261
return false;

libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h

+13-8
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,18 @@ class WiFiClientSecureCtx : public WiFiClient {
179179
// Methods for handling server.available() call which returns a client connection.
180180
friend class WiFiClientSecure; // access to private context constructors
181181
WiFiClientSecureCtx(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type,
182-
const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta);
182+
const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
183+
const X509List *client_CA_ta);
183184
WiFiClientSecureCtx(ClientContext* client, const X509List *chain, const PrivateKey *sk,
184-
int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta);
185+
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
186+
const X509List *client_CA_ta);
185187

186188
// RSA keyed server
187-
bool _connectSSLServerRSA(const X509List *chain, const PrivateKey *sk, const X509List *client_CA_ta);
189+
bool _connectSSLServerRSA(const X509List *chain, const PrivateKey *sk,
190+
ServerSessions *cache, const X509List *client_CA_ta);
188191
// EC keyed server
189192
bool _connectSSLServerEC(const X509List *chain, unsigned cert_issuer_key_type, const PrivateKey *sk,
190-
const X509List *client_CA_ta);
193+
ServerSessions *cache, const X509List *client_CA_ta);
191194

192195
// X.509 validators differ from server to client
193196
bool _installClientX509Validator(); // Set up X509 validator for a client conn.
@@ -290,13 +293,15 @@ class WiFiClientSecure : public WiFiClient {
290293
// Methods for handling server.available() call which returns a client connection.
291294
friend class WiFiServerSecure; // Server needs to access these constructors
292295
WiFiClientSecure(ClientContext *client, const X509List *chain, unsigned cert_issuer_key_type,
293-
const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta):
294-
_ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, client_CA_ta)) {
296+
const PrivateKey *sk, int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
297+
const X509List *client_CA_ta):
298+
_ctx(new WiFiClientSecureCtx(client, chain, cert_issuer_key_type, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) {
295299
}
296300

297301
WiFiClientSecure(ClientContext* client, const X509List *chain, const PrivateKey *sk,
298-
int iobuf_in_size, int iobuf_out_size, const X509List *client_CA_ta):
299-
_ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, client_CA_ta)) {
302+
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
303+
const X509List *client_CA_ta):
304+
_ctx(new WiFiClientSecureCtx(client, chain, sk, iobuf_in_size, iobuf_out_size, cache, client_CA_ta)) {
300305
}
301306

302307
}; // class WiFiClientSecure

libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ WiFiClientSecure WiFiServerSecure::available(uint8_t* status) {
7979
(void) status; // Unused
8080
if (_unclaimed) {
8181
if (_sk && _sk->isRSA()) {
82-
WiFiClientSecure result(_unclaimed, _chain, _sk, _iobuf_in_size, _iobuf_out_size, _client_CA_ta);
82+
WiFiClientSecure result(_unclaimed, _chain, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta);
8383
_unclaimed = _unclaimed->next();
8484
result.setNoDelay(_noDelay);
8585
DEBUGV("WS:av\r\n");
8686
return result;
8787
} else if (_sk && _sk->isEC()) {
88-
WiFiClientSecure result(_unclaimed, _chain, _cert_issuer_key_type, _sk, _iobuf_in_size, _iobuf_out_size, _client_CA_ta);
88+
WiFiClientSecure result(_unclaimed, _chain, _cert_issuer_key_type, _sk, _iobuf_in_size, _iobuf_out_size, _cache, _client_CA_ta);
8989
_unclaimed = _unclaimed->next();
9090
result.setNoDelay(_noDelay);
9191
DEBUGV("WS:av\r\n");

libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.h

+6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class WiFiServerSecure : public WiFiServer {
4242
_iobuf_out_size = xmit;
4343
}
4444

45+
// Sets the server's cache to the given one.
46+
void setCache(ServerSessions *cache) {
47+
_cache = cache;
48+
}
49+
4550
// Set the server's RSA key and x509 certificate (required, pick one).
4651
// Caller needs to preserve the chain and key throughout the life of the server.
4752
void setRSACert(const X509List *chain, const PrivateKey *sk);
@@ -69,6 +74,7 @@ class WiFiServerSecure : public WiFiServer {
6974
int _iobuf_in_size = BR_SSL_BUFSIZE_INPUT;
7075
int _iobuf_out_size = 837;
7176
const X509List *_client_CA_ta = nullptr;
77+
ServerSessions *_cache = nullptr;
7278

7379
};
7480

0 commit comments

Comments
 (0)