From 081e7501650e425c2765e7b4e8988a59d9c9333a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Villac=C3=ADs=20Lasso?= Date: Mon, 11 Oct 2021 17:57:42 -0500 Subject: [PATCH] WIP: start of TLS negotiation on nonblocking socket --- src/AsyncTCP_TLS_Context.cpp | 263 +++++++++++++++++++++++++++++++++++ src/AsyncTCP_TLS_Context.h | 55 ++++++++ 2 files changed, 318 insertions(+) create mode 100644 src/AsyncTCP_TLS_Context.cpp diff --git a/src/AsyncTCP_TLS_Context.cpp b/src/AsyncTCP_TLS_Context.cpp new file mode 100644 index 0000000..64a4835 --- /dev/null +++ b/src/AsyncTCP_TLS_Context.cpp @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "AsyncTCP_TLS_Context.h" + +#if ASYNC_TCP_SSL_ENABLED +#ifndef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED +# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#else + +static const char *pers = "esp32-tls"; + +static int _handle_error(int err, const char * function, int line) +{ + if(err == -30848){ + return err; + } +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + mbedtls_strerror(err, error_buf, 100); + log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); +#else + log_e("[%s():%d]: code %d", function, line, err); +#endif + return err; +} + +#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + +AsyncTCP_TLS_Context::AsyncTCP_TLS_Context(void) +{ + mbedtls_ssl_init(&ssl_ctx); + mbedtls_ssl_config_init(&ssl_conf); + mbedtls_ctr_drbg_init(&drbg_ctx); + _socket = -1; + _have_ca_cert = false; + _have_client_cert = false; + _have_client_key = false; + handshake_timeout = 120000; +} + +int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip, const char *rootCABuff, + const char *cli_cert, const char *cli_key, const char *pskIdent, + const char *psKey, bool insecure) +{ + int ret; + int enable = 1; + + // The insecure flag will skip server certificate validation. Otherwise some + // certificate is required. + if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { + return -1; + } + +#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }} +// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO"); +// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO"); + + ROE(lwip_setsockopt(sck, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY"); + ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE"); + + log_v("Seeding the random number generator"); + mbedtls_entropy_init(&entropy_ctx); + + ret = mbedtls_ctr_drbg_seed(&drbg_ctx, mbedtls_entropy_func, + &entropy_ctx, (const unsigned char *) pers, strlen(pers)); + if (ret < 0) { + return handle_error(ret); + } + + log_v("Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + return handle_error(ret); + } + + if (insecure) { + mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE); + log_i("WARNING: Skipping SSL Verification. INSECURE!"); + } else if (rootCABuff != NULL) { + log_v("Loading CA cert"); + mbedtls_x509_crt_init(&ca_cert); + mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + ret = mbedtls_x509_crt_parse(&ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); + _have_ca_cert = true; + mbedtls_ssl_conf_ca_chain(&ssl_conf, &ca_cert, NULL); + if (ret < 0) { + // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash. + _deleteHandshakeCerts(); + return handle_error(ret); + } + } else if (pskIdent != NULL && psKey != NULL) { + log_v("Setting up PSK"); + // convert PSK from hex to binary + if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) { + log_e("pre-shared key not valid hex or too long"); + return -1; + } + unsigned char psk[MBEDTLS_PSK_MAX_LEN]; + size_t psk_len = strlen(psKey)/2; + for (int j=0; j= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] = c<<4; + c = psKey[j+1]; + if (c >= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] |= c; + } + // set mbedtls config + ret = mbedtls_ssl_conf_psk(&ssl_conf, psk, psk_len, + (const unsigned char *)pskIdent, strlen(pskIdent)); + if (ret != 0) { + log_e("mbedtls_ssl_conf_psk returned %d", ret); + return handle_error(ret); + } + } else { + return -1; + } + + if (!insecure && cli_cert != NULL && cli_key != NULL) { + mbedtls_x509_crt_init(&client_cert); + mbedtls_pk_init(&client_key); + + log_v("Loading CRT cert"); + + ret = mbedtls_x509_crt_parse(&client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); + _have_client_cert = true; + if (ret < 0) { + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + _deleteHandshakeCerts(); + return handle_error(ret); + } + + log_v("Loading private key"); + ret = mbedtls_pk_parse_key(&client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0); + _have_client_key = true; + + if (ret != 0) { + _deleteHandshakeCerts(); + return handle_error(ret); + } + + mbedtls_ssl_conf_own_cert(&ssl_conf, &client_cert, &client_key); + } + + log_v("Setting hostname for TLS session..."); + + // Hostname set here should match CN in server certificate + if((ret = mbedtls_ssl_set_hostname(&ssl_ctx, host_or_ip)) != 0){ + _deleteHandshakeCerts(); + return handle_error(ret); + } + + mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf)) != 0) { + _deleteHandshakeCerts(); + return handle_error(ret); + } + + _socket = sck; + mbedtls_ssl_set_bio(&ssl_ctx, &_socket, mbedtls_net_send, mbedtls_net_recv, NULL ); + handshake_start_time = 0; + + return 0; +} + +int AsyncTCP_TLS_Context::runSSLHandshake(void) +{ + if (_socket < 0) return -1; + + if (handshake_start_time == 0) handshake_start_time = millis(); + int ret = mbedtls_ssl_handshake(&ssl_ctx); + if (ret != 0) { + // Something happened before SSL handshake could be completed + + // Negotiation error, other than socket not readable/writable when required + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + return handle_error(ret); + } + + // Handshake is taking too long + if ((millis()-handshake_start_time) > ssl_client->handshake_timeout) + return -1; + + // Either MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE + return ret; + } + + // Handshake completed, validate remote side if required... + + if (_have_client_cert && _have_client_key) { + log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_ctx)); + if ((ret = mbedtls_ssl_get_record_expansion(&ssl_ctx)) >= 0) { + log_d("Record expansion is %d", ret); + } else { + log_w("Record expansion is unknown (compression)"); + } + } + + log_v("Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(&ssl_ctx)) != 0) { + memset(buf, 0, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + log_e("Failed to verify peer certificate! verification info: %s", buf); + _deleteHandshakeCerts(); + return handle_error(ret); + } else { + log_v("Certificate verified."); + } + + _deleteHandshakeCerts(); + + log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); + + return 0; +} + +void AsyncTCP_TLS_Context::_deleteHandshakeCerts(void) +{ + if (_have_ca_cert) { + mbedtls_x509_crt_free(&ca_cert); + _have_ca_cert = false; + } + if (_have_client_cert) { + mbedtls_x509_crt_free(&client_cert); + _have_client_cert = false; + } + if (_have_client_key) { + mbedtls_pk_free(&client_key); + _have_client_key = false; + } +} + +AsyncTCP_TLS_Context::~AsyncTCP_TLS_Context() +{ + _deleteHandshakeCerts(); + + mbedtls_ssl_free(&ssl_ctx); + mbedtls_ssl_config_free(&ssl_conf); + mbedtls_ctr_drbg_free(&drbg_ctx); + mbedtls_entropy_free(&entropy_ctx); // <-- Is this OK to free if mbedtls_entropy_init() has not been called on it? +} + +#endif +#endif // ASYNC_TCP_SSL_ENABLED \ No newline at end of file diff --git a/src/AsyncTCP_TLS_Context.h b/src/AsyncTCP_TLS_Context.h index e69de29..64513ed 100644 --- a/src/AsyncTCP_TLS_Context.h +++ b/src/AsyncTCP_TLS_Context.h @@ -0,0 +1,55 @@ +#pragma once + +// TODO: se debe quitar este #define para que pueda habilitarse a voluntad el SSL +// según el proyecto. Se coloca aquí para probar el desarrollo +#define ASYNC_TCP_SSL_ENABLED 1 + +#if ASYNC_TCP_SSL_ENABLED + +#include "mbedtls/platform.h" +#include "mbedtls/net.h" +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" + +class AsyncTCP_TLS_Context +{ +private: + // These fields must persist for the life of the encrypted connection, destroyed on + // object destructor. + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + + // These allocate memory during handshake but must be freed on either success or failure + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + bool _have_ca_cert; + bool _have_client_cert; + bool _have_client_key; + + unsigned long handshake_timeout; + unsigned long handshake_start_time; + + int _socket; + + + + // Delete certificates used in handshake + void _deleteHandshakeCerts(void); +public: + AsyncTCP_TLS_Context(void); + virtual ~AsyncTCP_TLS_Context(); + + int startSSLClient(int sck, const char * host_or_ip, const char *rootCABuff, + const char *cli_cert, const char *cli_key, const char *pskIdent, + const char *psKey, bool insecure); + + int runSSLHandshake(void); +}; + +#endif // ASYNC_TCP_SSL_ENABLED \ No newline at end of file