Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1514 lines (1366 sloc) 54 KB
/*
WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries
- Mostly compatible with Arduino WiFi shield library and standard
WiFiClient/ServerSecure (except for certificate handling).
Copyright (c) 2018 Earle F. Philhower, III
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define LWIP_INTERNAL
#include <list>
#include <errno.h>
#include <algorithm>
extern "C" {
#include "osapi.h"
#include "ets_sys.h"
}
#include "debug.h"
#include "ESP8266WiFi.h"
#include "WiFiClient.h"
#include "WiFiClientSecureBearSSL.h"
#include "StackThunk.h"
#include "lwip/opt.h"
#include "lwip/ip.h"
#include "lwip/tcp.h"
#include "lwip/inet.h"
#include "lwip/netif.h"
#include <include/ClientContext.h>
#include "c_types.h"
#include "coredecls.h"
#if !CORE_MOCK
// The BearSSL thunks in use for now
#define br_ssl_engine_recvapp_ack thunk_br_ssl_engine_recvapp_ack
#define br_ssl_engine_recvapp_buf thunk_br_ssl_engine_recvapp_buf
#define br_ssl_engine_recvrec_ack thunk_br_ssl_engine_recvrec_ack
#define br_ssl_engine_recvrec_buf thunk_br_ssl_engine_recvrec_buf
#define br_ssl_engine_sendapp_ack thunk_br_ssl_engine_sendapp_ack
#define br_ssl_engine_sendapp_buf thunk_br_ssl_engine_sendapp_buf
#define br_ssl_engine_sendrec_ack thunk_br_ssl_engine_sendrec_ack
#define br_ssl_engine_sendrec_buf thunk_br_ssl_engine_sendrec_buf
#endif
#ifdef DEBUG_ESP_SSL
#define DEBUG_BSSL(fmt, ...) DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__)
#else
#define DEBUG_BSSL(...)
#endif
namespace BearSSL {
void WiFiClientSecure::_clear() {
// TLS handshake may take more than the 5 second default timeout
_timeout = 15000;
_sc = nullptr;
_sc_svr = nullptr;
_eng = nullptr;
_x509_minimal = nullptr;
_x509_insecure = nullptr;
_x509_knownkey = nullptr;
_iobuf_in = nullptr;
_iobuf_out = nullptr;
_now = 0; // You can override or ensure time() is correct w/configTime
_ta = nullptr;
setBufferSizes(16384, 512); // Minimum safe
_handshake_done = false;
_recvapp_buf = nullptr;
_recvapp_len = 0;
_oom_err = false;
_session = nullptr;
_cipher_list = nullptr;
_cipher_cnt = 0;
}
void WiFiClientSecure::_clearAuthenticationSettings() {
_use_insecure = false;
_use_fingerprint = false;
_use_self_signed = false;
_knownkey = nullptr;
_sk = nullptr;
_ta = nullptr;
_axtls_ta = nullptr;
_axtls_chain = nullptr;
_axtls_sk = nullptr;
}
WiFiClientSecure::WiFiClientSecure() : WiFiClient() {
_clear();
_clearAuthenticationSettings();
_certStore = nullptr; // Don't want to remove cert store on a clear, should be long lived
stack_thunk_add_ref();
}
WiFiClientSecure::WiFiClientSecure(const WiFiClientSecure &rhs) : WiFiClient(rhs) {
*this = rhs;
stack_thunk_add_ref();
}
WiFiClientSecure::~WiFiClientSecure() {
if (_client) {
_client->unref();
_client = nullptr;
}
_cipher_list = nullptr; // std::shared will free if last reference
_freeSSL();
stack_thunk_del_ref();
// Clean up any dangling axtls compat structures, if needed
_axtls_ta = nullptr;
_axtls_chain = nullptr;
_axtls_sk = nullptr;
}
WiFiClientSecure::WiFiClientSecure(ClientContext* client,
const X509List *chain, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, 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)) {
_client->unref();
_client = nullptr;
_clear();
}
}
WiFiClientSecure::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) {
_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)) {
_client->unref();
_client = nullptr;
_clear();
}
}
void WiFiClientSecure::setClientRSACert(const X509List *chain, const PrivateKey *sk) {
_chain = chain;
_sk = sk;
}
void WiFiClientSecure::setClientECCert(const X509List *chain,
const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
_chain = chain;
_sk = sk;
_allowed_usages = allowed_usages;
_cert_issuer_key_type = cert_issuer_key_type;
}
void WiFiClientSecure::setBufferSizes(int recv, int xmit) {
// Following constants taken from bearssl/src/ssl/ssl_engine.c (not exported unfortunately)
const int MAX_OUT_OVERHEAD = 85;
const int MAX_IN_OVERHEAD = 325;
// The data buffers must be between 512B and 16KB
recv = std::max(512, std::min(16384, recv));
xmit = std::max(512, std::min(16384, xmit));
// Add in overhead for SSL protocol
recv += MAX_IN_OVERHEAD;
xmit += MAX_OUT_OVERHEAD;
_iobuf_in_size = recv;
_iobuf_out_size = xmit;
}
bool WiFiClientSecure::stop(unsigned int maxWaitMs) {
bool ret = WiFiClient::stop(maxWaitMs); // calls our virtual flush()
// 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());
}
}
_freeSSL();
return ret;
}
bool WiFiClientSecure::flush(unsigned int maxWaitMs) {
(void) _run_until(BR_SSL_SENDAPP);
return WiFiClient::flush(maxWaitMs);
}
int WiFiClientSecure::connect(CONST IPAddress& ip, uint16_t port) {
if (!WiFiClient::connect(ip, port)) {
return 0;
}
return _connectSSL(nullptr);
}
int WiFiClientSecure::connect(const char* name, uint16_t port) {
IPAddress remote_addr;
if (!WiFi.hostByName(name, remote_addr)) {
DEBUG_BSSL("connect: Name loopup failure\n");
return 0;
}
if (!WiFiClient::connect(remote_addr, port)) {
DEBUG_BSSL("connect: Unable to connect TCP socket\n");
return 0;
}
return _connectSSL(name);
}
int WiFiClientSecure::connect(const String& host, uint16_t port) {
return connect(host.c_str(), port);
}
void WiFiClientSecure::_freeSSL() {
// These are smart pointers and will free if refcnt==0
_sc = nullptr;
_sc_svr = nullptr;
_x509_minimal = nullptr;
_x509_insecure = nullptr;
_x509_knownkey = nullptr;
_iobuf_in = nullptr;
_iobuf_out = nullptr;
// Reset non-allocated ptrs (pointing to bits potentially free'd above)
_recvapp_buf = nullptr;
_recvapp_len = 0;
// This connection is toast
_handshake_done = false;
}
bool WiFiClientSecure::_clientConnected() {
return (_client && _client->state() == ESTABLISHED);
}
uint8_t WiFiClientSecure::connected() {
if (available() || (_clientConnected() && _handshake_done)) {
return true;
}
return false;
}
size_t WiFiClientSecure::_write(const uint8_t *buf, size_t size, bool pmem) {
size_t sent_bytes = 0;
if (!connected() || !size || !_handshake_done) {
return 0;
}
do {
// Ensure we yield if we need multiple fragments to avoid WDT
if (sent_bytes) {
optimistic_yield(1000);
}
// Get BearSSL to a state where we can send
if (_run_until(BR_SSL_SENDAPP) < 0) {
break;
}
if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) {
size_t sendapp_len;
unsigned char *sendapp_buf = br_ssl_engine_sendapp_buf(_eng, &sendapp_len);
int to_send = size > sendapp_len ? sendapp_len : size;
if (pmem) {
memcpy_P(sendapp_buf, buf, to_send);
} else {
memcpy(sendapp_buf, buf, to_send);
}
br_ssl_engine_sendapp_ack(_eng, to_send);
br_ssl_engine_flush(_eng, 0);
flush();
buf += to_send;
sent_bytes += to_send;
size -= to_send;
} else {
break;
}
} while (size);
return sent_bytes;
}
size_t WiFiClientSecure::write(const uint8_t *buf, size_t size) {
return _write(buf, size, false);
}
size_t WiFiClientSecure::write_P(PGM_P buf, size_t size) {
return _write((const uint8_t *)buf, size, true);
}
// We have to manually read and send individual chunks.
size_t WiFiClientSecure::write(Stream& stream) {
size_t totalSent = 0;
size_t countRead;
size_t countSent;
if (!connected() || !_handshake_done) {
DEBUG_BSSL("write: Connect/handshake not completed yet\n");
return 0;
}
do {
uint8_t temp[256]; // Temporary chunk size same as ClientContext
countSent = 0;
countRead = stream.readBytes(temp, sizeof(temp));
if (countRead) {
countSent = _write((const uint8_t*)temp, countRead, true);
totalSent += countSent;
}
yield(); // Feed the WDT
} while ((countSent == countRead) && (countSent > 0));
return totalSent;
}
int WiFiClientSecure::read(uint8_t *buf, size_t size) {
if (!ctx_present() || !_handshake_done) {
return -1;
}
int avail = available();
bool conn = connected();
if (!avail && conn) {
return 0; // We're still connected, but nothing to read
}
if (!avail && !conn) {
DEBUG_BSSL("read: Not connected, none left available\n");
return -1;
}
if (avail) {
// Take data from the recvapp buffer
int to_copy = _recvapp_len < size ? _recvapp_len : size;
memcpy(buf, _recvapp_buf, to_copy);
br_ssl_engine_recvapp_ack(_eng, to_copy);
_recvapp_buf = nullptr;
_recvapp_len = 0;
return to_copy;
}
if (!conn) {
DEBUG_BSSL("read: Not connected\n");
return -1;
}
return 0; // If we're connected, no error but no read.
}
int WiFiClientSecure::read() {
uint8_t c;
if (1 == read(&c, 1)) {
return c;
}
DEBUG_BSSL("read: failed\n");
return -1;
}
int WiFiClientSecure::available() {
if (_recvapp_buf) {
return _recvapp_len; // Anything from last call?
}
_recvapp_buf = nullptr;
_recvapp_len = 0;
if (!ctx_present() || _run_until(BR_SSL_RECVAPP, false) < 0) {
return 0;
}
int st = br_ssl_engine_current_state(_eng);
if (st == BR_SSL_CLOSED) {
return 0; // Nothing leftover, SSL is closed
}
if (st & BR_SSL_RECVAPP) {
_recvapp_buf = br_ssl_engine_recvapp_buf(_eng, &_recvapp_len);
return _recvapp_len;
}
return 0;
}
int WiFiClientSecure::peek() {
if (!ctx_present() || !available()) {
DEBUG_BSSL("peek: Not connected, none left available\n");
return -1;
}
if (_recvapp_buf && _recvapp_len) {
return _recvapp_buf[0];
}
DEBUG_BSSL("peek: No data left\n");
return -1;
}
size_t WiFiClientSecure::peekBytes(uint8_t *buffer, size_t length) {
size_t to_copy = 0;
if (!ctx_present()) {
DEBUG_BSSL("peekBytes: Not connected\n");
return 0;
}
_startMillis = millis();
while ((available() < (int) length) && ((millis() - _startMillis) < 5000)) {
yield();
}
to_copy = _recvapp_len < length ? _recvapp_len : length;
memcpy(buffer, _recvapp_buf, to_copy);
return to_copy;
}
/* --- Copied almost verbatim from BEARSSL SSL_IO.C ---
Run the engine, until the specified target state is achieved, or
an error occurs. The target state is SENDAPP, RECVAPP, or the
combination of both (the combination matches either). When a match is
achieved, this function returns 0. On error, it returns -1.
*/
int WiFiClientSecure::_run_until(unsigned target, bool blocking) {
if (!ctx_present()) {
DEBUG_BSSL("_run_until: Not connected\n");
return -1;
}
for (int no_work = 0; blocking || no_work < 2;) {
if (blocking) {
// Only for blocking operations can we afford to yield()
optimistic_yield(100);
}
int state;
state = br_ssl_engine_current_state(_eng);
if (state & BR_SSL_CLOSED) {
return -1;
}
if (!(_client->state() == ESTABLISHED) && !WiFiClient::available()) {
return (state & target) ? 0 : -1;
}
/*
If there is some record data to send, do it. This takes
precedence over everything else.
*/
if (state & BR_SSL_SENDREC) {
unsigned char *buf;
size_t len;
int wlen;
buf = br_ssl_engine_sendrec_buf(_eng, &len);
wlen = WiFiClient::write(buf, len);
if (wlen <= 0) {
/*
If we received a close_notify and we
still send something, then we have our
own response close_notify to send, and
the peer is allowed by RFC 5246 not to
wait for it.
*/
return -1;
}
if (wlen > 0) {
br_ssl_engine_sendrec_ack(_eng, wlen);
}
no_work = 0;
continue;
}
/*
If we reached our target, then we are finished.
*/
if (state & target) {
return 0;
}
/*
If some application data must be read, and we did not
exit, then this means that we are trying to write data,
and that's not possible until the application data is
read. This may happen if using a shared in/out buffer,
and the underlying protocol is not strictly half-duplex.
This is unrecoverable here, so we report an error.
*/
if (state & BR_SSL_RECVAPP) {
DEBUG_BSSL("_run_until: Fatal protocol state\n");
return -1;
}
/*
If we reached that point, then either we are trying
to read data and there is some, or the engine is stuck
until a new record is obtained.
*/
if (state & BR_SSL_RECVREC) {
if (WiFiClient::available()) {
unsigned char *buf;
size_t len;
int rlen;
buf = br_ssl_engine_recvrec_buf(_eng, &len);
rlen = WiFiClient::read(buf, len);
if (rlen < 0) {
return -1;
}
if (rlen > 0) {
br_ssl_engine_recvrec_ack(_eng, rlen);
}
no_work = 0;
continue;
}
}
/*
We can reach that point if the target RECVAPP, and
the state contains SENDAPP only. This may happen with
a shared in/out buffer. In that case, we must flush
the buffered data to "make room" for a new incoming
record.
*/
br_ssl_engine_flush(_eng, 0);
no_work++; // We didn't actually advance here
}
// We only get here if we ran through the loop without getting anything done
return -1;
}
bool WiFiClientSecure::_wait_for_handshake() {
_handshake_done = false;
while (!_handshake_done && _clientConnected()) {
int ret = _run_until(BR_SSL_SENDAPP);
if (ret < 0) {
DEBUG_BSSL("_wait_for_handshake: failed\n");
break;
}
if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) {
_handshake_done = true;
}
optimistic_yield(1000);
}
return _handshake_done;
}
static uint8_t htoi (unsigned char c)
{
if (c>='0' && c <='9') return c - '0';
else if (c>='A' && c<='F') return 10 + c - 'A';
else if (c>='a' && c<='f') return 10 + c - 'a';
else return 255;
}
// Set a fingerprint by parsing an ASCII string
bool WiFiClientSecure::setFingerprint(const char *fpStr) {
int idx = 0;
uint8_t c, d;
uint8_t fp[20];
while (idx < 20) {
c = pgm_read_byte(fpStr++);
if (!c) break; // String ended, done processing
d = pgm_read_byte(fpStr++);
if (!d) {
DEBUG_BSSL("setFingerprint: FP too short\n");
return false; // Only half of the last hex digit, error
}
c = htoi(c);
d = htoi(d);
if ((c>15) || (d>15)) {
DEBUG_BSSL("setFingerprint: Invalid char\n");
return false; // Error in one of the hex characters
}
fp[idx++] = (c<<4)|d;
// Skip 0 or more spaces or colons
while ( pgm_read_byte(fpStr) && (pgm_read_byte(fpStr)==' ' || pgm_read_byte(fpStr)==':') ) {
fpStr++;
}
}
if ((idx != 20) || pgm_read_byte(fpStr)) {
DEBUG_BSSL("setFingerprint: Garbage at end of fp\n");
return false; // Garbage at EOL or we didn't have enough hex digits
}
return setFingerprint(fp);
}
extern "C" {
// BearSSL doesn't define a true insecure decoder, so we make one ourselves
// from the simple parser. It generates the issuer and subject hashes and
// the SHA1 fingerprint, only one (or none!) of which will be used to
// "verify" the certificate.
// Private x509 decoder state
struct br_x509_insecure_context {
const br_x509_class *vtable;
bool done_cert;
const uint8_t *match_fingerprint;
br_sha1_context sha1_cert;
bool allow_self_signed;
br_sha256_context sha256_subject;
br_sha256_context sha256_issuer;
br_x509_decoder_context ctx;
};
// Callback for the x509_minimal subject DN
static void insecure_subject_dn_append(void *ctx, const void *buf, size_t len) {
br_x509_insecure_context *xc = (br_x509_insecure_context *)ctx;
br_sha256_update(&xc->sha256_subject, buf, len);
}
// Callback for the x509_minimal issuer DN
static void insecure_issuer_dn_append(void *ctx, const void *buf, size_t len) {
br_x509_insecure_context *xc = (br_x509_insecure_context *)ctx;
br_sha256_update(&xc->sha256_issuer, buf, len);
}
// Callback on the first byte of any certificate
static void insecure_start_chain(const br_x509_class **ctx, const char *server_name) {
br_x509_insecure_context *xc = (br_x509_insecure_context *)ctx;
br_x509_decoder_init(&xc->ctx, insecure_subject_dn_append, xc, insecure_issuer_dn_append, xc);
xc->done_cert = false;
br_sha1_init(&xc->sha1_cert);
br_sha256_init(&xc->sha256_subject);
br_sha256_init(&xc->sha256_issuer);
(void)server_name;
}
// Callback for each certificate present in the chain (but only operates
// on the first one by design).
static void insecure_start_cert(const br_x509_class **ctx, uint32_t length) {
(void) ctx;
(void) length;
}
// Callback for each byte stream in the chain. Only process first cert.
static void insecure_append(const br_x509_class **ctx, const unsigned char *buf, size_t len) {
br_x509_insecure_context *xc = (br_x509_insecure_context *)ctx;
// Don't process anything but the first certificate in the chain
if (!xc->done_cert) {
br_sha1_update(&xc->sha1_cert, buf, len);
br_x509_decoder_push(&xc->ctx, (const void*)buf, len);
}
}
// Callback on individual cert end.
static void insecure_end_cert(const br_x509_class **ctx) {
br_x509_insecure_context *xc = (br_x509_insecure_context *)ctx;
xc->done_cert = true;
}
// Callback when complete chain has been parsed.
// Return 0 on validation success, !0 on validation error
static unsigned insecure_end_chain(const br_x509_class **ctx) {
const br_x509_insecure_context *xc = (const br_x509_insecure_context *)ctx;
if (!xc->done_cert) {
DEBUG_BSSL("insecure_end_chain: No cert seen\n");
return 1; // error
}
// Handle SHA1 fingerprint matching
char res[20];
br_sha1_out(&xc->sha1_cert, res);
if (xc->match_fingerprint && memcmp(res, xc->match_fingerprint, sizeof(res))) {
DEBUG_BSSL("insecure_end_chain: Received cert FP doesn't match\n");
return BR_ERR_X509_NOT_TRUSTED;
}
// Handle self-signer certificate acceptance
char res_issuer[32];
char res_subject[32];
br_sha256_out(&xc->sha256_issuer, res_issuer);
br_sha256_out(&xc->sha256_subject, res_subject);
if (xc->allow_self_signed && memcmp(res_subject, res_issuer, sizeof(res_issuer))) {
DEBUG_BSSL("insecure_end_chain: Didn't get self-signed cert\n");
return BR_ERR_X509_NOT_TRUSTED;
}
// Default (no validation at all) or no errors in prior checks = success.
return 0;
}
// Return the public key from the validator (set by x509_minimal)
static const br_x509_pkey *insecure_get_pkey(const br_x509_class *const *ctx, unsigned *usages) {
const br_x509_insecure_context *xc = (const br_x509_insecure_context *)ctx;
if (usages != NULL) {
*usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; // I said we were insecure!
}
return &xc->ctx.pkey;
}
// Set up the x509 insecure data structures for BearSSL core to use.
void br_x509_insecure_init(br_x509_insecure_context *ctx, int _use_fingerprint, const uint8_t _fingerprint[20], int _allow_self_signed) {
static const br_x509_class br_x509_insecure_vtable PROGMEM = {
sizeof(br_x509_insecure_context),
insecure_start_chain,
insecure_start_cert,
insecure_append,
insecure_end_cert,
insecure_end_chain,
insecure_get_pkey
};
memset(ctx, 0, sizeof * ctx);
ctx->vtable = &br_x509_insecure_vtable;
ctx->done_cert = false;
ctx->match_fingerprint = _use_fingerprint ? _fingerprint : nullptr;
ctx->allow_self_signed = _allow_self_signed ? 1 : 0;
}
// Some constants uses to init the server/client contexts
// Note that suites_P needs to be copied to RAM before use w/BearSSL!
// List copied verbatim from BearSSL/ssl_client_full.c
/*
* The "full" profile supports all implemented cipher suites.
*
* Rationale for suite order, from most important to least
* important rule:
*
* -- Don't use 3DES if AES or ChaCha20 is available.
* -- Try to have Forward Secrecy (ECDHE suite) if possible.
* -- When not using Forward Secrecy, ECDH key exchange is
* better than RSA key exchange (slightly more expensive on the
* client, but much cheaper on the server, and it implies smaller
* messages).
* -- ChaCha20+Poly1305 is better than AES/GCM (faster, smaller code).
* -- GCM is better than CCM and CBC. CCM is better than CBC.
* -- CCM is preferable over CCM_8 (with CCM_8, forgeries may succeed
* with probability 2^(-64)).
* -- AES-128 is preferred over AES-256 (AES-128 is already
* strong enough, and AES-256 is 40% more expensive).
*/
static const uint16_t suites_P[] PROGMEM = {
#ifndef BEARSSL_SSL_BASIC
BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
BR_TLS_RSA_WITH_AES_128_GCM_SHA256,
BR_TLS_RSA_WITH_AES_256_GCM_SHA384,
BR_TLS_RSA_WITH_AES_128_CCM,
BR_TLS_RSA_WITH_AES_256_CCM,
BR_TLS_RSA_WITH_AES_128_CCM_8,
BR_TLS_RSA_WITH_AES_256_CCM_8,
#endif
BR_TLS_RSA_WITH_AES_128_CBC_SHA256,
BR_TLS_RSA_WITH_AES_256_CBC_SHA256,
BR_TLS_RSA_WITH_AES_128_CBC_SHA,
BR_TLS_RSA_WITH_AES_256_CBC_SHA,
#ifndef BEARSSL_SSL_BASIC
BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA
#endif
};
// For apps which want to use less secure but faster ciphers, only
static const uint16_t faster_suites_P[] PROGMEM = {
BR_TLS_RSA_WITH_AES_256_CBC_SHA256,
BR_TLS_RSA_WITH_AES_128_CBC_SHA256,
BR_TLS_RSA_WITH_AES_256_CBC_SHA,
BR_TLS_RSA_WITH_AES_128_CBC_SHA };
// Install hashes into the SSL engine
static void br_ssl_client_install_hashes(br_ssl_engine_context *eng) {
br_ssl_engine_set_hash(eng, br_md5_ID, &br_md5_vtable);
br_ssl_engine_set_hash(eng, br_sha1_ID, &br_sha1_vtable);
br_ssl_engine_set_hash(eng, br_sha224_ID, &br_sha224_vtable);
br_ssl_engine_set_hash(eng, br_sha256_ID, &br_sha256_vtable);
br_ssl_engine_set_hash(eng, br_sha384_ID, &br_sha384_vtable);
br_ssl_engine_set_hash(eng, br_sha512_ID, &br_sha512_vtable);
}
static void br_x509_minimal_install_hashes(br_x509_minimal_context *x509) {
br_x509_minimal_set_hash(x509, br_md5_ID, &br_md5_vtable);
br_x509_minimal_set_hash(x509, br_sha1_ID, &br_sha1_vtable);
br_x509_minimal_set_hash(x509, br_sha224_ID, &br_sha224_vtable);
br_x509_minimal_set_hash(x509, br_sha256_ID, &br_sha256_vtable);
br_x509_minimal_set_hash(x509, br_sha384_ID, &br_sha384_vtable);
br_x509_minimal_set_hash(x509, br_sha512_ID, &br_sha512_vtable);
}
// Default initializion for our SSL clients
static void br_ssl_client_base_init(br_ssl_client_context *cc, const uint16_t *cipher_list, int cipher_cnt) {
uint16_t suites[cipher_cnt];
memcpy_P(suites, cipher_list, cipher_cnt * sizeof(cipher_list[0]));
br_ssl_client_zero(cc);
br_ssl_engine_set_versions(&cc->eng, BR_TLS10, BR_TLS12);
br_ssl_engine_set_suites(&cc->eng, suites, (sizeof suites) / (sizeof suites[0]));
br_ssl_client_set_default_rsapub(cc);
br_ssl_engine_set_default_rsavrfy(&cc->eng);
#ifndef BEARSSL_SSL_BASIC
br_ssl_engine_set_default_ecdsa(&cc->eng);
#endif
br_ssl_client_install_hashes(&cc->eng);
br_ssl_engine_set_prf10(&cc->eng, &br_tls10_prf);
br_ssl_engine_set_prf_sha256(&cc->eng, &br_tls12_sha256_prf);
br_ssl_engine_set_prf_sha384(&cc->eng, &br_tls12_sha384_prf);
br_ssl_engine_set_default_aes_cbc(&cc->eng);
#ifndef BEARSSL_SSL_BASIC
br_ssl_engine_set_default_aes_gcm(&cc->eng);
br_ssl_engine_set_default_aes_ccm(&cc->eng);
br_ssl_engine_set_default_des_cbc(&cc->eng);
br_ssl_engine_set_default_chapol(&cc->eng);
#endif
}
}
// Set custom list of ciphers
bool WiFiClientSecure::setCiphers(const uint16_t *cipherAry, int cipherCount) {
_cipher_list = nullptr;
_cipher_list = std::shared_ptr<uint16_t>(new uint16_t[cipherCount], std::default_delete<uint16_t[]>());
if (!_cipher_list.get()) {
DEBUG_BSSL("setCiphers: list empty\n");
return false;
}
memcpy_P(_cipher_list.get(), cipherAry, cipherCount * sizeof(uint16_t));
_cipher_cnt = cipherCount;
return true;
}
bool WiFiClientSecure::setCiphersLessSecure() {
return setCiphers(faster_suites_P, sizeof(faster_suites_P)/sizeof(faster_suites_P[0]));
}
bool WiFiClientSecure::setCiphers(std::vector<uint16_t> list) {
return setCiphers(&list[0], list.size());
}
// Installs the appropriate X509 cert validation method for a client connection
bool WiFiClientSecure::_installClientX509Validator() {
if (_use_insecure || _use_fingerprint || _use_self_signed) {
// Use common insecure x509 authenticator
_x509_insecure = std::make_shared<struct br_x509_insecure_context>();
if (!_x509_insecure) {
DEBUG_BSSL("_installClientX509Validator: OOM for _x509_insecure\n");
return false;
}
br_x509_insecure_init(_x509_insecure.get(), _use_fingerprint, _fingerprint, _use_self_signed);
br_ssl_engine_set_x509(_eng, &_x509_insecure->vtable);
} else if (_knownkey) {
// Simple, pre-known public key authenticator, ignores cert completely.
_x509_knownkey = std::make_shared<br_x509_knownkey_context>();
if (!_x509_knownkey) {
DEBUG_BSSL("_installClientX509Validator: OOM for _x509_knownkey\n");
return false;
}
if (_knownkey->isRSA()) {
br_x509_knownkey_init_rsa(_x509_knownkey.get(), _knownkey->getRSA(), _knownkey_usages);
} else if (_knownkey->isEC()) {
#ifndef BEARSSL_SSL_BASIC
br_x509_knownkey_init_ec(_x509_knownkey.get(), _knownkey->getEC(), _knownkey_usages);
#else
(void) _knownkey;
(void) _knownkey_usages;
DEBUG_BSSL("_installClientX509Validator: Attempting to use EC keys in minimal cipher mode (no EC)\n");
return false;
#endif
}
br_ssl_engine_set_x509(_eng, &_x509_knownkey->vtable);
} else {
// X509 minimal validator. Checks dates, cert chain for trusted CA, etc.
_x509_minimal = std::make_shared<br_x509_minimal_context>();
if (!_x509_minimal) {
DEBUG_BSSL("_installClientX509Validator: OOM for _x509_minimal\n");
return false;
}
br_x509_minimal_init(_x509_minimal.get(), &br_sha256_vtable, _ta ? _ta->getTrustAnchors() : nullptr, _ta ? _ta->getCount() : 0);
br_x509_minimal_set_rsa(_x509_minimal.get(), br_ssl_engine_get_rsavrfy(_eng));
#ifndef BEARSSL_SSL_BASIC
br_x509_minimal_set_ecdsa(_x509_minimal.get(), br_ssl_engine_get_ec(_eng), br_ssl_engine_get_ecdsa(_eng));
#endif
br_x509_minimal_install_hashes(_x509_minimal.get());
if (_now) {
// Magic constants convert to x509 times
br_x509_minimal_set_time(_x509_minimal.get(), ((uint32_t)_now) / 86400 + 719528, ((uint32_t)_now) % 86400);
}
if (_certStore) {
_certStore->installCertStore(_x509_minimal.get());
}
br_ssl_engine_set_x509(_eng, &_x509_minimal->vtable);
}
return true;
}
// Called by connect() to do the actual SSL setup and handshake.
// Returns if the SSL handshake succeeded.
bool WiFiClientSecure::_connectSSL(const char* hostName) {
DEBUG_BSSL("_connectSSL: start connection\n");
_freeSSL();
_oom_err = false;
#ifdef DEBUG_ESP_SSL
// BearSSL will reject all connections unless an authentication option is set, warn in DEBUG builds
if (!_use_insecure && !_use_fingerprint && !_use_self_signed && !_knownkey && !_certStore && !_ta) {
DEBUG_BSSL("Connection *will* fail, no authentication method is setup\n");
}
#endif
_sc = std::make_shared<br_ssl_client_context>();
_eng = &_sc->eng; // Allocation/deallocation taken care of by the _sc shared_ptr
_iobuf_in = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_in_size], std::default_delete<unsigned char[]>());
_iobuf_out = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_out_size], std::default_delete<unsigned char[]>());
if (!_sc || !_iobuf_in || !_iobuf_out) {
_freeSSL(); // Frees _sc, _iobuf*
_oom_err = true;
DEBUG_BSSL("_connectSSL: OOM error\n");
return false;
}
// If no cipher list yet set, use defaults
if (_cipher_list.get() == nullptr) {
br_ssl_client_base_init(_sc.get(), suites_P, sizeof(suites_P) / sizeof(suites_P[0]));
} else {
br_ssl_client_base_init(_sc.get(), _cipher_list.get(), _cipher_cnt);
}
// Only failure possible in the installation is OOM
if (!_installClientX509Validator()) {
_freeSSL();
_oom_err = true;
DEBUG_BSSL("_connectSSL: Can't install x509 validator\n");
return false;
}
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
// Apply any client certificates, if supplied.
if (_sk && _sk->isRSA()) {
br_ssl_client_set_single_rsa(_sc.get(), _chain ? _chain->getX509Certs() : nullptr, _chain ? _chain->getCount() : 0,
_sk->getRSA(), br_rsa_pkcs1_sign_get_default());
} else if (_sk && _sk->isEC()) {
#ifndef BEARSSL_SSL_BASIC
br_ssl_client_set_single_ec(_sc.get(), _chain ? _chain->getX509Certs() : nullptr, _chain ? _chain->getCount() : 0,
_sk->getEC(), _allowed_usages,
_cert_issuer_key_type, br_ec_get_default(), br_ecdsa_sign_asn1_get_default());
#else
_freeSSL();
DEBUG_BSSL("_connectSSL: Attempting to use EC cert in minimal cipher mode (no EC)\n");
return false;
#endif
}
// 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();
DEBUG_BSSL("_connectSSL: Can't reset client\n");
return false;
}
auto ret = _wait_for_handshake();
#ifdef DEBUG_ESP_SSL
if (!ret) {
char err[256];
getLastSSLError(err, sizeof(err));
DEBUG_BSSL("Couldn't connect. Error = '%s'\n", err);
} else {
DEBUG_BSSL("Connected!\n");
}
#endif
return ret;
}
// Slightly different X509 setup for servers who want to validate client
// certificates, so factor it out as it's used in RSA and EC servers.
bool WiFiClientSecure::_installServerX509Validator(const X509List *client_CA_ta) {
if (client_CA_ta) {
_ta = client_CA_ta;
// X509 minimal validator. Checks dates, cert chain for trusted CA, etc.
_x509_minimal = std::make_shared<br_x509_minimal_context>();
if (!_x509_minimal) {
_freeSSL();
_oom_err = true;
DEBUG_BSSL("_installServerX509Validator: OOM for _x509_minimal\n");
return false;
}
br_x509_minimal_init(_x509_minimal.get(), &br_sha256_vtable, _ta->getTrustAnchors(), _ta->getCount());
br_ssl_engine_set_default_rsavrfy(_eng);
#ifndef BEARSSL_SSL_BASIC
br_ssl_engine_set_default_ecdsa(_eng);
#endif
br_x509_minimal_set_rsa(_x509_minimal.get(), br_ssl_engine_get_rsavrfy(_eng));
#ifndef BEARSSL_SSL_BASIC
br_x509_minimal_set_ecdsa(_x509_minimal.get(), br_ssl_engine_get_ec(_eng), br_ssl_engine_get_ecdsa(_eng));
#endif
br_x509_minimal_install_hashes(_x509_minimal.get());
if (_now) {
// Magic constants convert to x509 times
br_x509_minimal_set_time(_x509_minimal.get(), ((uint32_t)_now) / 86400 + 719528, ((uint32_t)_now) % 86400);
}
br_ssl_engine_set_x509(_eng, &_x509_minimal->vtable);
br_ssl_server_set_trust_anchor_names_alt(_sc_svr.get(), _ta->getTrustAnchors(), _ta->getCount());
}
return true;
}
// Called by WiFiServerBearSSL when an RSA cert/key is specified.
bool WiFiClientSecure::_connectSSLServerRSA(const X509List *chain,
const PrivateKey *sk,
const X509List *client_CA_ta) {
_freeSSL();
_oom_err = false;
_sc_svr = std::make_shared<br_ssl_server_context>();
_eng = &_sc_svr->eng; // Allocation/deallocation taken care of by the _sc shared_ptr
_iobuf_in = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_in_size], std::default_delete<unsigned char[]>());
_iobuf_out = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_out_size], std::default_delete<unsigned char[]>());
if (!_sc_svr || !_iobuf_in || !_iobuf_out) {
_freeSSL();
_oom_err = true;
DEBUG_BSSL("_connectSSLServerRSA: OOM error\n");
return false;
}
br_ssl_server_init_full_rsa(_sc_svr.get(), chain ? chain->getX509Certs() : nullptr, chain ? chain->getCount() : 0, sk ? sk->getRSA() : nullptr);
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
DEBUG_BSSL("_connectSSLServerRSA: Can't install serverX509check\n");
return false;
}
if (!br_ssl_server_reset(_sc_svr.get())) {
_freeSSL();
DEBUG_BSSL("_connectSSLServerRSA: Can't reset server ctx\n");
return false;
}
return _wait_for_handshake();
}
// Called by WiFiServerBearSSL when an elliptic curve cert/key is specified.
bool WiFiClientSecure::_connectSSLServerEC(const X509List *chain,
unsigned cert_issuer_key_type, const PrivateKey *sk,
const X509List *client_CA_ta) {
#ifndef BEARSSL_SSL_BASIC
_freeSSL();
_oom_err = false;
_sc_svr = std::make_shared<br_ssl_server_context>();
_eng = &_sc_svr->eng; // Allocation/deallocation taken care of by the _sc shared_ptr
_iobuf_in = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_in_size], std::default_delete<unsigned char[]>());
_iobuf_out = std::shared_ptr<unsigned char>(new unsigned char[_iobuf_out_size], std::default_delete<unsigned char[]>());
if (!_sc_svr || !_iobuf_in || !_iobuf_out) {
_freeSSL();
_oom_err = true;
DEBUG_BSSL("_connectSSLServerEC: OOM error\n");
return false;
}
br_ssl_server_init_full_ec(_sc_svr.get(), chain ? chain->getX509Certs() : nullptr, chain ? chain->getCount() : 0,
cert_issuer_key_type, sk ? sk->getEC() : nullptr);
br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size);
if (client_CA_ta && !_installServerX509Validator(client_CA_ta)) {
DEBUG_BSSL("_connectSSLServerEC: Can't install serverX509check\n");
return false;
}
if (!br_ssl_server_reset(_sc_svr.get())) {
_freeSSL();
DEBUG_BSSL("_connectSSLServerEC: Can't reset server ctx\n");
return false;
}
return _wait_for_handshake();
#else
(void) chain;
(void) cert_issuer_key_type;
(void) sk;
(void) client_CA_ta;
DEBUG_BSSL("_connectSSLServerEC: Attempting to use EC cert in minimal cipher mode (no EC)\n");
return false;
#endif
}
// Returns an error ID and possibly a string (if dest != null) of the last
// BearSSL reported error.
int WiFiClientSecure::getLastSSLError(char *dest, size_t len) {
int err = 0;
const char *t = PSTR("OK");
if (_sc || _sc_svr) {
err = br_ssl_engine_last_error(_eng);
}
if (_oom_err) {
err = -1000;
}
switch (err) {
case -1000: t = PSTR("Unable to allocate memory for SSL structures and buffers."); break;
case BR_ERR_BAD_PARAM: t = PSTR("Caller-provided parameter is incorrect."); break;
case BR_ERR_BAD_STATE: t = PSTR("Operation requested by the caller cannot be applied with the current context state (e.g. reading data while outgoing data is waiting to be sent)."); break;
case BR_ERR_UNSUPPORTED_VERSION: t = PSTR("Incoming protocol or record version is unsupported."); break;
case BR_ERR_BAD_VERSION: t = PSTR("Incoming record version does not match the expected version."); break;
case BR_ERR_BAD_LENGTH: t = PSTR("Incoming record length is invalid."); break;
case BR_ERR_TOO_LARGE: t = PSTR("Incoming record is too large to be processed, or buffer is too small for the handshake message to send."); break;
case BR_ERR_BAD_MAC: t = PSTR("Decryption found an invalid padding, or the record MAC is not correct."); break;
case BR_ERR_NO_RANDOM: t = PSTR("No initial entropy was provided, and none can be obtained from the OS."); break;
case BR_ERR_UNKNOWN_TYPE: t = PSTR("Incoming record type is unknown."); break;
case BR_ERR_UNEXPECTED: t = PSTR("Incoming record or message has wrong type with regards to the current engine state."); break;
case BR_ERR_BAD_CCS: t = PSTR("ChangeCipherSpec message from the peer has invalid contents."); break;
case BR_ERR_BAD_ALERT: t = PSTR("Alert message from the peer has invalid contents (odd length)."); break;
case BR_ERR_BAD_HANDSHAKE: t = PSTR("Incoming handshake message decoding failed."); break;
case BR_ERR_OVERSIZED_ID: t = PSTR("ServerHello contains a session ID which is larger than 32 bytes."); break;
case BR_ERR_BAD_CIPHER_SUITE: t = PSTR("Server wants to use a cipher suite that we did not claim to support. This is also reported if we tried to advertise a cipher suite that we do not support."); break;
case BR_ERR_BAD_COMPRESSION: t = PSTR("Server wants to use a compression that we did not claim to support."); break;
case BR_ERR_BAD_FRAGLEN: t = PSTR("Server's max fragment length does not match client's."); break;
case BR_ERR_BAD_SECRENEG: t = PSTR("Secure renegotiation failed."); break;
case BR_ERR_EXTRA_EXTENSION: t = PSTR("Server sent an extension type that we did not announce, or used the same extension type several times in a single ServerHello."); break;
case BR_ERR_BAD_SNI: t = PSTR("Invalid Server Name Indication contents (when used by the server, this extension shall be empty)."); break;
case BR_ERR_BAD_HELLO_DONE: t = PSTR("Invalid ServerHelloDone from the server (length is not 0)."); break;
case BR_ERR_LIMIT_EXCEEDED: t = PSTR("Internal limit exceeded (e.g. server's public key is too large)."); break;
case BR_ERR_BAD_FINISHED: t = PSTR("Finished message from peer does not match the expected value."); break;
case BR_ERR_RESUME_MISMATCH: t = PSTR("Session resumption attempt with distinct version or cipher suite."); break;
case BR_ERR_INVALID_ALGORITHM: t = PSTR("Unsupported or invalid algorithm (ECDHE curve, signature algorithm, hash function)."); break;
case BR_ERR_BAD_SIGNATURE: t = PSTR("Invalid signature in ServerKeyExchange or CertificateVerify message."); break;
case BR_ERR_WRONG_KEY_USAGE: t = PSTR("Peer's public key does not have the proper type or is not allowed for the requested operation."); break;
case BR_ERR_NO_CLIENT_AUTH: t = PSTR("Client did not send a certificate upon request, or the client certificate could not be validated."); break;
case BR_ERR_IO: t = PSTR("I/O error or premature close on transport stream."); break;
case BR_ERR_X509_INVALID_VALUE: t = PSTR("Invalid value in an ASN.1 structure."); break;
case BR_ERR_X509_TRUNCATED: t = PSTR("Truncated certificate or other ASN.1 object."); break;
case BR_ERR_X509_EMPTY_CHAIN: t = PSTR("Empty certificate chain (no certificate at all)."); break;
case BR_ERR_X509_INNER_TRUNC: t = PSTR("Decoding error: inner element extends beyond outer element size."); break;
case BR_ERR_X509_BAD_TAG_CLASS: t = PSTR("Decoding error: unsupported tag class (application or private)."); break;
case BR_ERR_X509_BAD_TAG_VALUE: t = PSTR("Decoding error: unsupported tag value."); break;
case BR_ERR_X509_INDEFINITE_LENGTH: t = PSTR("Decoding error: indefinite length."); break;
case BR_ERR_X509_EXTRA_ELEMENT: t = PSTR("Decoding error: extraneous element."); break;
case BR_ERR_X509_UNEXPECTED: t = PSTR("Decoding error: unexpected element."); break;
case BR_ERR_X509_NOT_CONSTRUCTED: t = PSTR("Decoding error: expected constructed element, but is primitive."); break;
case BR_ERR_X509_NOT_PRIMITIVE: t = PSTR("Decoding error: expected primitive element, but is constructed."); break;
case BR_ERR_X509_PARTIAL_BYTE: t = PSTR("Decoding error: BIT STRING length is not multiple of 8."); break;
case BR_ERR_X509_BAD_BOOLEAN: t = PSTR("Decoding error: BOOLEAN value has invalid length."); break;
case BR_ERR_X509_OVERFLOW: t = PSTR("Decoding error: value is off-limits."); break;
case BR_ERR_X509_BAD_DN: t = PSTR("Invalid distinguished name."); break;
case BR_ERR_X509_BAD_TIME: t = PSTR("Invalid date/time representation."); break;
case BR_ERR_X509_UNSUPPORTED: t = PSTR("Certificate contains unsupported features that cannot be ignored."); break;
case BR_ERR_X509_LIMIT_EXCEEDED: t = PSTR("Key or signature size exceeds internal limits."); break;
case BR_ERR_X509_WRONG_KEY_TYPE: t = PSTR("Key type does not match that which was expected."); break;
case BR_ERR_X509_BAD_SIGNATURE: t = PSTR("Signature is invalid."); break;
case BR_ERR_X509_TIME_UNKNOWN: t = PSTR("Validation time is unknown."); break;
case BR_ERR_X509_EXPIRED: t = PSTR("Certificate is expired or not yet valid."); break;
case BR_ERR_X509_DN_MISMATCH: t = PSTR("Issuer/Subject DN mismatch in the chain."); break;
case BR_ERR_X509_BAD_SERVER_NAME: t = PSTR("Expected server name was not found in the chain."); break;
case BR_ERR_X509_CRITICAL_EXTENSION: t = PSTR("Unknown critical extension in certificate."); break;
case BR_ERR_X509_NOT_CA: t = PSTR("Not a CA, or path length constraint violation."); break;
case BR_ERR_X509_FORBIDDEN_KEY_USAGE: t = PSTR("Key Usage extension prohibits intended usage."); break;
case BR_ERR_X509_WEAK_PUBLIC_KEY: t = PSTR("Public key found in certificate is too small."); break;
case BR_ERR_X509_NOT_TRUSTED: t = PSTR("Chain could not be linked to a trust anchor."); break;
default: t = PSTR("Unknown error code."); break;
}
if (dest) {
strncpy_P(dest, t, len);
dest[len - 1] = 0;
}
return err;
}
bool WiFiClientSecure::probeMaxFragmentLength(const char* name, uint16_t port, uint16_t len) {
IPAddress remote_addr;
if (!WiFi.hostByName(name, remote_addr)) {
DEBUG_BSSL("probeMaxFragmentLength: Can't resolve host\n");
return false;
}
return WiFiClientSecure::probeMaxFragmentLength(remote_addr, port, len);
}
bool WiFiClientSecure::probeMaxFragmentLength(const String& host, uint16_t port, uint16_t len) {
return WiFiClientSecure::probeMaxFragmentLength(host.c_str(), port, len);
}
// Helper function which aborts a TLS handshake by sending TLS
// ClientAbort and ClientClose messages.
static bool _SendAbort(WiFiClient& probe, bool supportsLen) {
// If we're still connected, send the appropriate notice that
// we're aborting the handshake per RFCs.
static const uint8_t clientAbort_P[] PROGMEM = {
0x15 /*alert*/, 0x03, 0x03 /*TLS 1.2*/, 0x00, 0x02,
1, 90 /* warning: user_cancelled */
};
static const uint8_t clientClose_P[] PROGMEM = {
0x15 /*alert*/, 0x03, 0x03 /*TLS 1.2*/, 0x00, 0x02,
1, 0 /* warning: close_notify */
};
if (probe.connected()) {
uint8_t msg[sizeof(clientAbort_P)];
memcpy_P(msg, clientAbort_P, sizeof(clientAbort_P));
probe.write(msg, sizeof(clientAbort_P));
memcpy_P(msg, clientClose_P, sizeof(clientClose_P));
probe.write(msg, sizeof(clientClose_P));
}
return supportsLen;
}
// Checks for support of Maximum Frame Length Negotiation at the given
// blocksize. Note that, per spec, only 512, 1024, 2048, and 4096 are
// supported. Many servers today do not support this negotiation.
// TODO - Allow for fragmentation...but not very critical as the ServerHello
// we use comes to < 80 bytes which has no reason to ever be fragmented.
// TODO - Check the type of returned extensions and that the MFL is the exact
// same one we sent. Not critical as only horribly broken servers would
// return changed or add their own extensions.
bool WiFiClientSecure::probeMaxFragmentLength(IPAddress ip, uint16_t port, uint16_t len) {
// Hardcoded TLS 1.2 packets used throughout
static const uint8_t clientHelloHead_P[] PROGMEM = {
0x16, 0x03, 0x03, 0x00, 0, // TLS header, change last 2 bytes to len
0x01, 0x00, 0x00, 0, // Last 3 bytes == length
0x03, 0x03, // Proto version TLS 1.2
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Random (gmtime + rand[28])
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x00, // Session ID
};
// Followed by our cipher-suite, generated on-the-fly
// 0x00, 0x02, // cipher suite len
// 0xc0, 0x13, // BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
static const uint8_t clientHelloTail_P[] PROGMEM = {
0x01, 0x00, // No compression
0x00, 26 + 14 + 6 + 5, // Extension length
0x00, 0x0d, 0x00, 0x16, 0x00, 0x14, 0x04, 0x03, 0x03, 0x03, 0x05, 0x03,
0x06, 0x03, 0x02, 0x03, 0x04, 0x01, 0x03, 0x01, 0x05, 0x01, 0x06,
0x01, 0x02, 0x01, // Supported signature algorithms
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19,
0x00, 0x1d, // Supported groups
0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, // Supported EC formats
0x00, 0x01, // Max Frag Len
0x00, 0x01, // len of MaxFragLen
};
// Followed by a 1-byte MFLN size requesst
// 0x04 // 2^12 = 4K
uint8_t mfl;
switch (len) {
case 512: mfl = 1; break;
case 1024: mfl = 2; break;
case 2048: mfl = 3; break;
case 4096: mfl = 4; break;
default: return false; // Invalid size
}
int ttlLen = sizeof(clientHelloHead_P) + (2 + sizeof(suites_P)) + (sizeof(clientHelloTail_P) + 1);
uint8_t *clientHello = new uint8_t[ttlLen];
if (!clientHello) {
DEBUG_BSSL("probeMaxFragmentLength: OOM\n");
return false;
}
memcpy_P(clientHello, clientHelloHead_P, sizeof(clientHelloHead_P));
clientHello[sizeof(clientHelloHead_P) + 0] = sizeof(suites_P) >> 8; // MSB byte len
clientHello[sizeof(clientHelloHead_P) + 1] = sizeof(suites_P) & 0xff; // LSB byte len
for (size_t i = 0; i < sizeof(suites_P) / sizeof(suites_P[0]); i++) {
uint16_t flip = pgm_read_word(&suites_P[i]);
// Swap to network byte order
flip = ((flip >> 8) & 0xff) | ((flip & 0xff) << 8);
memcpy(clientHello + sizeof(clientHelloHead_P) + 2 + 2 * i, &flip, 2);
}
memcpy_P(clientHello + sizeof(clientHelloHead_P) + 2 + sizeof(suites_P), clientHelloTail_P, sizeof(clientHelloTail_P));
clientHello[sizeof(clientHelloHead_P) + 2 + sizeof(suites_P) + sizeof(clientHelloTail_P)] = mfl;
// Fix up TLS fragment length
clientHello[3] = (ttlLen - 5) >> 8;
clientHello[4] = (ttlLen - 5) & 0xff;
// Fix up ClientHello message length
clientHello[7] = (ttlLen - 5 - 4) >> 8;
clientHello[8] = (ttlLen - 5 - 4) & 0xff;
WiFiClient probe;
probe.connect(ip, port);
if (!probe.connected()) {
delete[] clientHello;
DEBUG_BSSL("probeMaxFragmentLength: Can't connect\n");
return false;
}
int ret = probe.write(clientHello, ttlLen);
delete[] clientHello; // We're done w/the hello message
if (!probe.connected() || (ret != ttlLen)) {
DEBUG_BSSL("probeMaxFragmentLength: Protocol error\n");
return false;
}
bool supportsLen = false;
uint8_t fragResp[5];
int fragLen;
uint8_t hand[4];
int handLen;
uint8_t protoVer[2];
uint8_t rand[32];
uint8_t sessionLen;
uint8_t cipher[2];
uint8_t comp;
uint8_t extBytes[2];
uint16_t extLen;
ret = probe.readBytes(fragResp, 5);
if (!probe.connected() || (ret != 5) || (fragResp[0] != 0x16) || (fragResp[1] != 0x03) || (fragResp[2] != 0x03)) {
// Short read, not a HANDSHAKE or not TLS 1.2, so it's not supported
return _SendAbort(probe, supportsLen);
}
fragLen = (fragResp[3] << 8) | fragResp[4];
if (fragLen < 4 + 2 + 32 + 1 + 2 + 1) {
// Too short to have an extension
return _SendAbort(probe, supportsLen);
}
ret = probe.readBytes(hand, 4);
fragLen -= ret;
if ((ret != 4) || (hand[0] != 2)) {
// Short read or not server_hello
return _SendAbort(probe, supportsLen);
}
handLen = (hand[1] << 16) | (hand[2] << 8) | hand[3];
if (handLen != fragLen) {
// Got some weird mismatch, this is invalid
return _SendAbort(probe, supportsLen);
}
ret = probe.readBytes(protoVer, 2);
handLen -= ret;
if ((ret != 2) || (protoVer[0] != 0x03) || (protoVer[1] != 0x03)) {
// Short read or not tls 1.2, so can't do MFLN
return _SendAbort(probe, supportsLen);
}
ret = probe.readBytes(rand, 32);
handLen -= ret;
if (ret != 32) {
// short read of random data
return _SendAbort(probe, supportsLen);
}
ret = probe.readBytes(&sessionLen, 1);
handLen -= ret;
if ((ret != 1) || (sessionLen > 32)) {
// short read of session len or invalid size
return _SendAbort(probe, supportsLen);
}
if (sessionLen) {
ret = probe.readBytes(rand, sessionLen);
handLen -= ret;
if (ret != sessionLen) {
// short session id read
return _SendAbort(probe, supportsLen);
}
}
ret = probe.readBytes(cipher, 2);
handLen -= ret;
if (ret != 2) {
// Short read...we don't check the cipher here
return _SendAbort(probe, supportsLen);
}
ret = probe.readBytes(&comp, 1);
handLen -= ret;
if ((ret != 1) || comp != 0) {
// short read or invalid compression
return _SendAbort(probe, supportsLen);
}
ret = probe.readBytes(extBytes, 2);
handLen -= ret;
extLen = extBytes[1] || (extBytes[0]<<8);
if ((extLen == 0) || (ret != 2)) {
return _SendAbort(probe, supportsLen);
}
while (handLen > 0) {
// Parse each extension and look for MFLN
uint8_t typeBytes[2];
ret = probe.readBytes(typeBytes, 2);
handLen -= 2;
if ((ret != 2) || (handLen <= 0) ) {
return _SendAbort(probe, supportsLen);
}
uint8_t lenBytes[2];
ret = probe.readBytes(lenBytes, 2);
handLen -= 2;
uint16_t extLen = lenBytes[1] | (lenBytes[0]<<8);
if ((ret != 2) || (handLen <= 0) || (extLen > 32) || (extLen > handLen) ) {
return _SendAbort(probe, supportsLen);
}
if ((typeBytes[0]==0x00) && (typeBytes[1]==0x01)) { // MFLN extension!
// If present and 1-byte in length, it's supported
return _SendAbort(probe, extLen==1 ? true : false);
}
// Skip the extension, move to next one
uint8_t junk[32];
ret = probe.readBytes(junk, extLen);
handLen -= extLen;
if (ret != extLen) {
return _SendAbort(probe, supportsLen);
}
}
return _SendAbort(probe, supportsLen);
}
// AXTLS compatibility interfaces
bool WiFiClientSecure::setCACert(const uint8_t* pk, size_t size) {
_axtls_ta = nullptr;
_axtls_ta = std::shared_ptr<X509List>(new X509List(pk, size));
_ta = _axtls_ta.get();
return _ta ? true : false;
}
bool WiFiClientSecure::setCertificate(const uint8_t* pk, size_t size) {
_axtls_chain = nullptr;
_axtls_chain = std::shared_ptr<X509List>(new X509List(pk, size));
_chain = _axtls_chain.get();
return _chain ? true : false;
}
bool WiFiClientSecure::setPrivateKey(const uint8_t* pk, size_t size) {
_axtls_sk = nullptr;
_axtls_sk = std::shared_ptr<PrivateKey>(new PrivateKey(pk, size));
_sk = _axtls_sk.get();
return _sk ? true : false;
}
uint8_t *WiFiClientSecure::_streamLoad(Stream& stream, size_t size) {
uint8_t *dest = (uint8_t*)malloc(size);
if (!dest) {
return nullptr;
}
if (size != stream.readBytes(dest, size)) {
free(dest);
return nullptr;
}
return dest;
}
bool WiFiClientSecure::loadCACert(Stream& stream, size_t size) {
uint8_t *dest = _streamLoad(stream, size);
bool ret = false;
if (dest) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
ret = setCACert(dest, size);
#pragma GCC diagnostic pop
}
free(dest);
return ret;
}
bool WiFiClientSecure::loadCertificate(Stream& stream, size_t size) {
uint8_t *dest = _streamLoad(stream, size);
bool ret = false;
if (dest) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
ret = setCertificate(dest, size);
#pragma GCC diagnostic pop
}
free(dest);
return ret;
}
bool WiFiClientSecure::loadPrivateKey(Stream& stream, size_t size) {
uint8_t *dest = _streamLoad(stream, size);
bool ret = false;
if (dest) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
ret = setPrivateKey(dest, size);
#pragma GCC diagnostic pop
}
free(dest);
return ret;
}
};
You can’t perform that action at this time.