diff --git a/CMakeLists.txt b/CMakeLists.txt index f52e1c93..c7ad8b7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,3 +13,7 @@ set(COMPONENT_REQUIRES register_component() target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti) + +if(CONFIG_ASYNC_TCP_SSL_ENABLED) + target_compile_options(${COMPONENT_TARGET} PRIVATE -DASYNC_TCP_SSL_ENABLED) +endif() diff --git a/Kconfig.projbuild b/Kconfig.projbuild index 17749264..ab9ac57f 100644 --- a/Kconfig.projbuild +++ b/Kconfig.projbuild @@ -27,4 +27,10 @@ config ASYNC_TCP_USE_WDT help Enable WDT for the AsyncTCP task, so it will trigger if a handler is locking the thread. +config ASYNC_TCP_SSL_ENABLED + bool "Enable SSL for AsyncTCP client" + default "n" + help + Enables mbedTLS support for AsyncTCP clients. + endmenu diff --git a/README.md b/README.md index 983aabd9..25f68f9a 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,10 @@ This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ES ## AsyncClient and AsyncServer The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. + +## TLS support +Support for TLS is added using mbed TLS, for now only the client part is supported. You can enable this by adding the flag ASYNC_TCP_SSL_ENABLED to your build flags (-DASYNC_TCP_SSL_ENABLED). If you'd like to set a root certificate you can use the setRootCa function on AsyncClient. Feel free to add support for the server side as well :-) + +In addition to the regular certificate based cipher suites there is also support for Pre-Shared Key +cipher suites. Use `setPsk` to define the PSK identifier and PSK itself. The PSK needs to be +provided in the form of a hex string (and easy way to generate a PSK is to use md5sum). diff --git a/src/AsyncTCP.cpp b/src/AsyncTCP.cpp index 89ff6ee3..169c098a 100644 --- a/src/AsyncTCP.cpp +++ b/src/AsyncTCP.cpp @@ -33,6 +33,11 @@ extern "C"{ /* * TCP/IP Event Task + * + * This task processes events that correspond to the various callbacks made by LwIP. The callbacks + * are handled by _tcp_* functions, which package the info into events, which are processed by this + * task. The purpose of this scheme is ??? (to be able to block or spend arbitrary time in the event + * handlers without thereby blocking LwIP???). * */ typedef enum { @@ -228,6 +233,9 @@ static bool _start_async_task(){ /* * LwIP Callbacks + * + * The following "_tcp_*" functions are called by LwIP on its thread. They all do nothing but + * package the callback info into an event, which is queued for the async event task (see above). * */ static int8_t _tcp_clear_events(void * arg) { @@ -342,6 +350,13 @@ static int8_t _tcp_accept(void * arg, AsyncClient * client) { /* * TCP/IP API Calls + * + * The following functions provide stubs to call into LwIP's TCP api functions on the LwIP thread + * itself. This ensures there are no race conditions between the application and LwIP. + * The way it works is that the `_tcp_xxx` functions synchronously call the corresponding + * `_tcp_xxx_api` functions on the LwIP thread using a `tcp_api_call` mechanism provided by LwIP. + * The `_tcp_xxx_api` function then finally calls the actual `tcp_xxx` function in LwIP and returns + * the result. * */ #include "lwip/priv/tcpip_priv.h" @@ -534,7 +549,33 @@ static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { return msg.pcb; } +#if ASYNC_TCP_SSL_ENABLED +// Jump pads for _tcp_*4ssl function below to get access to _closed_slot. +// I'm sure there has to be a better way to do this... +esp_err_t AsyncClient::_tcp_output4ssl(tcp_pcb * pcb) { + return _tcp_output(pcb, _closed_slot); +} + +esp_err_t AsyncClient::_tcp_write4ssl(tcp_pcb * pcb, const char* data, size_t size, uint8_t apiflags) { + return _tcp_write(pcb, _closed_slot, data, size, apiflags); +} + +extern "C" { + // The following API stubs are callable from C for use in tcp_mbedtls.c + + esp_err_t _tcp_output4ssl(tcp_pcb * pcb, void* client) { + AsyncClient *cli = reinterpret_cast(client); + return cli->_tcp_output4ssl(pcb); + } + + esp_err_t _tcp_write4ssl(tcp_pcb * pcb, const char* data, size_t size, uint8_t apiflags, void* client) { + AsyncClient *cli = reinterpret_cast(client); + return cli->_tcp_write4ssl(pcb, data, size, apiflags); + } + +} +#endif /* Async TCP Client @@ -562,6 +603,18 @@ AsyncClient::AsyncClient(tcp_pcb* pcb) , _rx_since_timeout(0) , _ack_timeout(ASYNC_MAX_ACK_TIME) , _connect_port(0) +#if ASYNC_TCP_SSL_ENABLED +, _root_ca_len(0) +, _root_ca(NULL) +, _cli_cert_len(0) +, _cli_cert(NULL) +, _cli_key_len(0) +, _cli_key(NULL) +, _pcb_secure(false) +, _handshake_done(true) +, _psk_ident(0) +, _psk(0) +#endif // ASYNC_TCP_SSL_ENABLED , prev(NULL) , next(NULL) { @@ -603,6 +656,19 @@ AsyncClient& AsyncClient::operator=(const AsyncClient& other){ tcp_sent(_pcb, &_tcp_sent); tcp_err(_pcb, &_tcp_error); tcp_poll(_pcb, &_tcp_poll, 1); +#if ASYNC_TCP_SSL_ENABLED + if(tcp_ssl_has(_pcb)){ + _pcb_secure = true; + _handshake_done = false; + tcp_ssl_arg(_pcb, this); + tcp_ssl_data(_pcb, &_s_data); + tcp_ssl_handshake(_pcb, &_s_handshake); + tcp_ssl_err(_pcb, &_s_ssl_error); + } else { + _pcb_secure = false; + _handshake_done = true; + } +#endif // ASYNC_TCP_SSL_ENABLED } return *this; } @@ -674,7 +740,11 @@ void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ * Main Public Methods * */ +#if ASYNC_TCP_SSL_ENABLED +bool AsyncClient::connect(IPAddress ip, uint16_t port, bool secure){ +#else bool AsyncClient::connect(IPAddress ip, uint16_t port){ +#endif // ASYNC_TCP_SSL_ENABLED if (_pcb){ log_w("already connected, state %d", _pcb->state); return false; @@ -694,6 +764,11 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port){ return false; } +#if ASYNC_TCP_SSL_ENABLED + _pcb_secure = secure; + _handshake_done = !secure; +#endif // ASYNC_TCP_SSL_ENABLED + tcp_arg(pcb, this); tcp_err(pcb, &_tcp_error); tcp_recv(pcb, &_tcp_recv); @@ -704,19 +779,33 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port){ return true; } +#if ASYNC_TCP_SSL_ENABLED +bool AsyncClient::connect(const char* host, uint16_t port, bool secure){ +#else bool AsyncClient::connect(const char* host, uint16_t port){ +#endif // ASYNC_TCP_SSL_ENABLED ip_addr_t addr; - + if(!_start_async_task()){ log_e("failed to start task"); return false; } - + err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); if(err == ERR_OK) { +#if ASYNC_TCP_SSL_ENABLED + _hostname = host; + return connect(IPAddress(addr.u_addr.ip4.addr), port, secure); +#else return connect(IPAddress(addr.u_addr.ip4.addr), port); +#endif // ASYNC_TCP_SSL_ENABLED } else if(err == ERR_INPROGRESS) { _connect_port = port; +#if ASYNC_TCP_SSL_ENABLED + _hostname = host; + _pcb_secure = secure; + _handshake_done = !secure; +#endif // ASYNC_TCP_SSL_ENABLED return true; } log_e("error: %d", err); @@ -738,6 +827,28 @@ int8_t AsyncClient::abort(){ return ERR_ABRT; } +#if ASYNC_TCP_SSL_ENABLED +void AsyncClient::setRootCa(const char* rootca, const size_t len) { + _root_ca = (char*)rootca; + _root_ca_len = len; +} + +void AsyncClient::setClientCert(const char* cli_cert, const size_t len) { + _cli_cert = (char*)cli_cert; + _cli_cert_len = len; +} + +void AsyncClient::setClientKey(const char* cli_key, const size_t len) { + _cli_key = (char*)cli_key; + _cli_key_len = len; +} + +void AsyncClient::setPsk(const char* psk_ident, const char* psk) { + _psk_ident = psk_ident; + _psk = psk; +} +#endif // ASYNC_TCP_SSL_ENABLED + size_t AsyncClient::space(){ if((_pcb != NULL) && (_pcb->state == 4)){ return tcp_sndbuf(_pcb); @@ -753,6 +864,19 @@ size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { if(!room) { return 0; } +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + int sent = tcp_ssl_write(_pcb, (uint8_t*)data, size); + if(sent >= 0){ + // @ToDo: ??? + //_tx_unacked_len += sent; + return sent; + } + //log_i("add: tcp_ssl_write: %d", sent); + _close(); + return 0; + } +#endif // ASYNC_TCP_SSL_ENABLED size_t will_send = (room < size) ? room : size; int8_t err = ERR_OK; err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); @@ -763,6 +887,9 @@ size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { } bool AsyncClient::send(){ +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure) return true; +#endif // ASYNC_TCP_SSL_ENABLED int8_t err = ERR_OK; err = _tcp_output(_pcb, _closed_slot); if(err == ERR_OK){ @@ -800,6 +927,11 @@ int8_t AsyncClient::_close(){ int8_t err = ERR_OK; if(_pcb) { //log_i(""); +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + tcp_ssl_free(_pcb); + } +#endif // ASYNC_TCP_SSL_ENABLED tcp_arg(_pcb, NULL); tcp_sent(_pcb, NULL); tcp_recv(_pcb, NULL); @@ -853,15 +985,46 @@ int8_t AsyncClient::_connected(void* pcb, int8_t err){ // tcp_recv(_pcb, &_tcp_recv); // tcp_sent(_pcb, &_tcp_sent); // tcp_poll(_pcb, &_tcp_poll, 1); +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + bool err = false; + if (_psk_ident != NULL and _psk != NULL) { + err = tcp_ssl_new_psk_client(_pcb, this, _psk_ident, _psk) < 0; + } else { + err = tcp_ssl_new_client(_pcb, this, _hostname.empty() ? NULL : _hostname.c_str(), + _root_ca, _root_ca_len, _cli_cert, _cli_cert_len, _cli_key, _cli_key_len) < 0; + } + if (err) { + log_e("closing...."); + return _close(); + } + + tcp_ssl_data(_pcb, &_s_data); + tcp_ssl_handshake(_pcb, &_s_handshake); + tcp_ssl_err(_pcb, &_s_ssl_error); + } +#endif // ASYNC_TCP_SSL_ENABLED } +#if ASYNC_TCP_SSL_ENABLED + // _connect_cb happens after SSL handshake if this is a secure connection + if(_connect_cb && !_pcb_secure) { + _connect_cb(_connect_cb_arg, this); + } +#else if(_connect_cb) { _connect_cb(_connect_cb_arg, this); } +#endif // ASYNC_TCP_SSL_ENABLED return ERR_OK; } void AsyncClient::_error(int8_t err) { if(_pcb){ +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + tcp_ssl_free(_pcb); + } +#endif // ASYNC_TCP_SSL_ENABLED tcp_arg(_pcb, NULL); if(_pcb->state == LISTEN) { tcp_sent(_pcb, NULL); @@ -879,6 +1042,14 @@ void AsyncClient::_error(int8_t err) { } } +#if ASYNC_TCP_SSL_ENABLED +void AsyncClient::_ssl_error(int8_t err){ + if(_error_cb) { + _error_cb(_error_cb_arg, this, err+64); + } +} +#endif // ASYNC_TCP_SSL_ENABLED + //In LwIP Thread int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { if(!_pcb || pcb != _pcb){ @@ -922,24 +1093,46 @@ int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { while(pb != NULL) { _rx_last_packet = millis(); - //we should not ack before we assimilate the data - _ack_pcb = true; - pbuf *b = pb; - pb = b->next; - b->next = NULL; - if(_pb_cb){ - _pb_cb(_pb_cb_arg, this, b); - } else { - if(_recv_cb) { - _recv_cb(_recv_cb_arg, this, b->payload, b->len); + pbuf *nxt = pb->next; + pb->next = NULL; + +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure){ + // log_i("_recv: %d\n", pb->tot_len); + int err = tcp_ssl_read(pcb, pb); + // tcp_ssl_read always processes the full pbuf, so ack all of it + _tcp_recved(pcb, _closed_slot, pb->len); + pbuf_free(pb); + // handle errors + if(err < 0){ + if (err != MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + log_e("_recv err: %d\n", err); + _close(); + } + return ERR_BUF; // for lack of a better error value } - if(!_ack_pcb) { - _rx_ack_len += b->len; - } else if(_pcb) { - _tcp_recved(_pcb, _closed_slot, b->len); + + } else +#endif // ASYNC_TCP_SSL_ENABLED + { + //we should not ack before we assimilate the data + _ack_pcb = true; + if(_pb_cb){ + _pb_cb(_pb_cb_arg, this, pb); + } else { + if(_recv_cb) { + _recv_cb(_recv_cb_arg, this, pb->payload, pb->len); + } + if(!_ack_pcb) { + _rx_ack_len += pb->len; + } else if(_pcb) { + _tcp_recved(_pcb, _closed_slot, pb->len); + } + pbuf_free(pb); } - pbuf_free(b); } + + pb = nxt; } return ERR_OK; } @@ -970,6 +1163,13 @@ int8_t AsyncClient::_poll(tcp_pcb* pcb){ _close(); return ERR_OK; } +#if ASYNC_TCP_SSL_ENABLED + if(_pcb_secure && !_handshake_done && (now - _rx_last_packet) >= SSL_HANDSHAKE_TIMEOUT){ + log_w("ssl handshake timeout %d", pcb->state); + _close(); + return ERR_OK; + } +#endif // ASYNC_TCP_SSL_ENABLED // Everything is fine if(_poll_cb) { _poll_cb(_poll_cb_arg, this); @@ -979,7 +1179,11 @@ int8_t AsyncClient::_poll(tcp_pcb* pcb){ void AsyncClient::_dns_found(struct ip_addr *ipaddr){ if(ipaddr && ipaddr->u_addr.ip4.addr){ +#if ASYNC_TCP_SSL_ENABLED + connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port, _pcb_secure); +#else connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port); +#endif // ASYNC_TCP_SSL_ENABLED } else { if(_error_cb) { _error_cb(_error_cb_arg, this, -55); @@ -1230,6 +1434,23 @@ int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ return reinterpret_cast(arg)->_connected(pcb, err); } +#if ASYNC_TCP_SSL_ENABLED +void AsyncClient::_s_data(void *arg, struct tcp_pcb *tcp, uint8_t * data, size_t len){ + AsyncClient *c = reinterpret_cast(arg); + if(c->_recv_cb) c->_recv_cb(c->_recv_cb_arg, c, data, len); +} + +void AsyncClient::_s_handshake(void *arg, struct tcp_pcb *tcp, struct tcp_ssl_pcb* ssl){ + AsyncClient *c = reinterpret_cast(arg); + c->_handshake_done = true; + if(c->_connect_cb) c->_connect_cb(c->_connect_cb_arg, c); +} + +void AsyncClient::_s_ssl_error(void *arg, struct tcp_pcb *tcp, int8_t err){ + reinterpret_cast(arg)->_ssl_error(err); +} +#endif // ASYNC_TCP_SSL_ENABLED + /* Async TCP Server */ diff --git a/src/AsyncTCP.h b/src/AsyncTCP.h index ac87deda..48d9b68a 100644 --- a/src/AsyncTCP.h +++ b/src/AsyncTCP.h @@ -25,6 +25,11 @@ #include "IPAddress.h" #include "sdkconfig.h" #include +#include +#if ASYNC_TCP_SSL_ENABLED +#include +#include "tcp_mbedtls.h" +#endif extern "C" { #include "freertos/semphr.h" #include "lwip/pbuf.h" @@ -41,6 +46,7 @@ class AsyncClient; #define ASYNC_MAX_ACK_TIME 5000 #define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) #define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. +#define SSL_HANDSHAKE_TIMEOUT 5000 // timeout to complete SSL handshake typedef std::function AcConnectHandler; typedef std::function AcAckHandler; @@ -65,8 +71,18 @@ class AsyncClient { bool operator!=(const AsyncClient &other) { return !(*this == other); } + +#if ASYNC_TCP_SSL_ENABLED + bool connect(IPAddress ip, uint16_t port, bool secure = false); + bool connect(const char* host, uint16_t port, bool secure = false); + void setRootCa(const char* rootca, const size_t len); + void setClientCert(const char* cli_cert, const size_t len); + void setClientKey(const char* cli_key, const size_t len); + void setPsk(const char* psk_ident, const char* psk); +#else bool connect(IPAddress ip, uint16_t port); bool connect(const char* host, uint16_t port); +#endif // ASYNC_TCP_SSL_ENABLED void close(bool now = false); void stop(); int8_t abort(); @@ -135,12 +151,20 @@ class AsyncClient { static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); static int8_t _s_connected(void* arg, void* tpcb, int8_t err); static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); +#if ASYNC_TCP_SSL_ENABLED + static void _s_data(void *arg, struct tcp_pcb *tcp, uint8_t * data, size_t len); + static void _s_handshake(void *arg, struct tcp_pcb *tcp, struct tcp_ssl_pcb* ssl); + static void _s_ssl_error(void *arg, struct tcp_pcb *tcp, int8_t err); + esp_err_t _tcp_output4ssl(tcp_pcb * pcb); + esp_err_t _tcp_write4ssl(tcp_pcb * pcb, const char* data, size_t size, uint8_t apiflags); +#endif // ASYNC_TCP_SSL_ENABLED int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); tcp_pcb * pcb(){ return _pcb; } protected: tcp_pcb* _pcb; + std::string _hostname; int8_t _closed_slot; AcConnectHandler _connect_cb; @@ -169,6 +193,19 @@ class AsyncClient { uint32_t _ack_timeout; uint16_t _connect_port; +#if ASYNC_TCP_SSL_ENABLED + size_t _root_ca_len; + char* _root_ca; + size_t _cli_cert_len; + char* _cli_cert; + size_t _cli_key_len; + char* _cli_key; + bool _pcb_secure; + bool _handshake_done; + const char* _psk_ident; + const char* _psk; +#endif // ASYNC_TCP_SSL_ENABLED + int8_t _close(); void _free_closed_slot(); void _allocate_closed_slot(); @@ -179,18 +216,30 @@ class AsyncClient { int8_t _fin(tcp_pcb* pcb, int8_t err); int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); void _dns_found(struct ip_addr *ipaddr); +#if ASYNC_TCP_SSL_ENABLED + void _ssl_error(int8_t err); +#endif // ASYNC_TCP_SSL_ENABLED public: AsyncClient* prev; AsyncClient* next; }; +#if ASYNC_TCP_SSL_ENABLED +typedef std::function AcSSlFileHandler; +#endif + class AsyncServer { public: AsyncServer(IPAddress addr, uint16_t port); AsyncServer(uint16_t port); ~AsyncServer(); void onClient(AcConnectHandler cb, void* arg); +#if ASYNC_TCP_SSL_ENABLED + // Dummy, so it compiles with ESP Async WebServer library enabled. + void onSslFileRequest(AcSSlFileHandler cb, void* arg) {}; + void beginSecure(const char *cert, const char *private_key_file, const char *password) {}; +#endif void begin(); void end(); void setNoDelay(bool nodelay); diff --git a/src/tcp_mbedtls.c b/src/tcp_mbedtls.c new file mode 100644 index 00000000..c6d69bde --- /dev/null +++ b/src/tcp_mbedtls.c @@ -0,0 +1,610 @@ +#if ASYNC_TCP_SSL_ENABLED + +#include "tcp_mbedtls.h" +#include "lwip/tcp.h" +#include "mbedtls/debug.h" +#include "mbedtls/esp_debug.h" +#include + +// stubs to call LwIP's tcp functions on the LwIP thread itself, implemented in AsyncTCP.cpp +extern esp_err_t _tcp_output4ssl(struct tcp_pcb * pcb, void* client); +extern esp_err_t _tcp_write4ssl(struct tcp_pcb * pcb, const char* data, size_t size, uint8_t apiflags, void* client); + +#if 0 +#define TCP_SSL_DEBUG(...) do { ets_printf("T %s- ", pcTaskGetTaskName(xTaskGetCurrentTaskHandle())); ets_printf(__VA_ARGS__); } while(0) +#else +#define TCP_SSL_DEBUG(...) +#endif + +static const char pers[] = "esp32-tls"; + +static int handle_error(int err) { + if(err == -30848){ + return err; + } +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + mbedtls_strerror(err, error_buf, 100); + TCP_SSL_DEBUG("%s\n", error_buf); +#endif + TCP_SSL_DEBUG("MbedTLS message code: %d\n", err); + return err; +} + +/** + * Certificate verification callback for mbed TLS + * Here we only use it to display information on each cert in the chain + */ +// static int my_verify(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) { +// const uint32_t buf_size = 1024; +// char buf[buf_size]; +// (void) data; + +// mbedtls_printf("\nVerifying certificate at depth %d:\n", depth); +// mbedtls_x509_crt_info(buf, buf_size - 1, " ", crt); +// mbedtls_printf("%s", buf); + +// if (*flags == 0) +// mbedtls_printf("No verification issue for this certificate\n"); +// else +// { +// mbedtls_x509_crt_verify_info(buf, buf_size, " ! ", *flags); +// mbedtls_printf("%s\n", buf); +// } + +// return 0; +// } + +static uint8_t _tcp_ssl_has_client = 0; + +struct tcp_ssl_pcb { + struct tcp_pcb *tcp; + int fd; + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + mbedtls_x509_crt ca_cert; + bool has_ca_cert; + mbedtls_x509_crt client_cert; + bool has_client_cert; + mbedtls_pk_context client_key; + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + uint8_t type; + // int handshake; + void* arg; + tcp_ssl_data_cb_t on_data; + tcp_ssl_handshake_cb_t on_handshake; + tcp_ssl_error_cb_t on_error; + size_t last_wr; + struct pbuf *tcp_pbuf; + int pbuf_offset; + struct tcp_ssl_pcb* next; +}; + +typedef struct tcp_ssl_pcb tcp_ssl_t; + +static tcp_ssl_t * tcp_ssl_array = NULL; +static int tcp_ssl_next_fd = 0; + +// tcp_ssl_recv attempts to read up to len bytes into buf from data already received. +// It is called by mbedtls. +int tcp_ssl_recv(void *ctx, unsigned char *buf, size_t len) { + TCP_SSL_DEBUG("tcp_ssl_recv: ctx: 0x%X, buf: 0x%X, len: %d\n", ctx, buf, len); + tcp_ssl_t *tcp_ssl = (tcp_ssl_t*)ctx; + u16_t recv_len = 0; + + if(tcp_ssl->tcp_pbuf == NULL || tcp_ssl->tcp_pbuf->tot_len == 0) { + TCP_SSL_DEBUG("tcp_ssl_recv: not yet ready to read: tcp_pbuf: 0x%X.\n", tcp_ssl->tcp_pbuf); + return MBEDTLS_ERR_SSL_WANT_READ; + } + + recv_len = pbuf_copy_partial(tcp_ssl->tcp_pbuf, buf, len, tcp_ssl->pbuf_offset); + TCP_SSL_DEBUG("tcp_ssl_recv: len: %d, recv_len: %d, pbuf_offset: %d, tcp_pbuf len: %d.\n", + len, recv_len, tcp_ssl->pbuf_offset, tcp_ssl->tcp_pbuf->len); + tcp_ssl->pbuf_offset += recv_len; + + if(recv_len == 0) { + return MBEDTLS_ERR_SSL_WANT_READ; + } + return recv_len; +} + +// tcp_ssl_send attempts to send len bytes from buf. +// It is called by mbedtls. +int tcp_ssl_send(void *ctx, const unsigned char *buf, size_t len) { + TCP_SSL_DEBUG("tcp_ssl_send: ctx: 0x%X, buf: 0x%X, len: %d\n", ctx, buf, len); + + if(ctx == NULL) { + TCP_SSL_DEBUG("tcp_ssl_send: no context set\n"); + return -1; + } + + if(buf == NULL) { + TCP_SSL_DEBUG("tcp_ssl_send: buf not set\n"); + return -1; + } + + tcp_ssl_t *tcp_ssl = (tcp_ssl_t*)ctx; + size_t tcp_len = 0; + int err = ERR_OK; + + if (tcp_sndbuf(tcp_ssl->tcp) < len) { + tcp_len = tcp_sndbuf(tcp_ssl->tcp); + if(tcp_len == 0) { + TCP_SSL_DEBUG("tcp_ssl_send: tcp_sndbuf is zero: %d\n", len); + return ERR_MEM; + } + } else { + tcp_len = len; + } + + if (tcp_len > 2 * tcp_ssl->tcp->mss) { + tcp_len = 2 * tcp_ssl->tcp->mss; + } + + TCP_SSL_DEBUG("tcp_ssl_send: tcp_write(%x, %x, %d, %x)\n", tcp_ssl->tcp, (char *)buf, tcp_len, tcp_ssl->arg); + err = _tcp_write4ssl(tcp_ssl->tcp, (char *)buf, tcp_len, TCP_WRITE_FLAG_COPY, tcp_ssl->arg); + if(err < ERR_OK) { + if (err == ERR_MEM) { + TCP_SSL_DEBUG("tcp_ssl_send: No memory %d (%d)\n", tcp_len, len); + return err; + } + TCP_SSL_DEBUG("tcp_ssl_send: tcp_write error: %d\n", err); + return err; + } else if (err == ERR_OK) { + TCP_SSL_DEBUG("tcp_ssl_send: tcp_output: %d / %d\n", tcp_len, len); + err = _tcp_output4ssl(tcp_ssl->tcp, tcp_ssl->arg); + if(err != ERR_OK) { + TCP_SSL_DEBUG("tcp_ssl_send: tcp_output err: %d\n", err); + return err; + } + } + + tcp_ssl->last_wr += tcp_len; + + return tcp_len; +} + +uint8_t tcp_ssl_has_client() { + return _tcp_ssl_has_client; +} + +tcp_ssl_t * tcp_ssl_new(struct tcp_pcb *tcp, void* arg) { + + if(tcp_ssl_next_fd < 0){ + tcp_ssl_next_fd = 0;//overflow + } + + tcp_ssl_t * new_item = (tcp_ssl_t*)malloc(sizeof(tcp_ssl_t)); + if(!new_item){ + TCP_SSL_DEBUG("tcp_ssl_new: failed to allocate tcp_ssl\n"); + return NULL; + } + + new_item->tcp = tcp; + new_item->arg = arg; + new_item->on_data = NULL; + new_item->on_handshake = NULL; + new_item->on_error = NULL; + new_item->tcp_pbuf = NULL; + new_item->pbuf_offset = 0; + new_item->next = NULL; + new_item->has_ca_cert = false; + new_item->has_client_cert = false; + + if(tcp_ssl_array == NULL){ + tcp_ssl_array = new_item; + } else { + tcp_ssl_t * item = tcp_ssl_array; + while(item->next != NULL) + item = item->next; + item->next = new_item; + } + + return new_item; +} + +tcp_ssl_t* tcp_ssl_get(struct tcp_pcb *tcp) { + if(tcp == NULL) { + return NULL; + } + tcp_ssl_t * item = tcp_ssl_array; + while(item && item->tcp != tcp){ + item = item->next; + } + return item; +} + +int tcp_ssl_new_client(struct tcp_pcb *tcp, void *arg, const char* hostname, const char* root_ca, const size_t root_ca_len, + const char* cli_cert, const size_t cli_cert_len, const char* cli_key, const size_t cli_key_len) { + tcp_ssl_t* tcp_ssl; + + if(tcp == NULL) { + return -1; + } + + if(tcp_ssl_get(tcp) != NULL){ + return -1; + } + + tcp_ssl = tcp_ssl_new(tcp, arg); + if(tcp_ssl == NULL){ + return -1; + } + + mbedtls_entropy_init(&tcp_ssl->entropy_ctx); + mbedtls_ctr_drbg_init(&tcp_ssl->drbg_ctx); + mbedtls_ssl_init(&tcp_ssl->ssl_ctx); + mbedtls_ssl_config_init(&tcp_ssl->ssl_conf); + if(root_ca != NULL) { + mbedtls_x509_crt_init(&tcp_ssl->ca_cert); + tcp_ssl->has_ca_cert = true; + } + if (cli_cert != NULL && cli_key != NULL) { + mbedtls_x509_crt_init(&tcp_ssl->client_cert); + mbedtls_pk_init(&tcp_ssl->client_key); + tcp_ssl->has_client_cert = true; + } + + mbedtls_ctr_drbg_seed(&tcp_ssl->drbg_ctx, mbedtls_entropy_func, + &tcp_ssl->entropy_ctx, (const unsigned char*)pers, sizeof(pers)); + + if(mbedtls_ssl_config_defaults(&tcp_ssl->ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) { + TCP_SSL_DEBUG("error setting SSL config.\n"); + + tcp_ssl_free(tcp); + return -1; + } + + int ret = 0; + + if(tcp_ssl->has_ca_cert) { + TCP_SSL_DEBUG("setting the root ca.\n"); + + mbedtls_x509_crt_init(&tcp_ssl->ca_cert); + + mbedtls_ssl_conf_authmode(&tcp_ssl->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + + ret = mbedtls_x509_crt_parse(&tcp_ssl->ca_cert, (const unsigned char *)root_ca, root_ca_len); + if( ret < 0 ){ + TCP_SSL_DEBUG(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); + tcp_ssl_free(tcp); + return handle_error(ret); + } + + mbedtls_ssl_conf_ca_chain(&tcp_ssl->ssl_conf, &tcp_ssl->ca_cert, NULL); + } else { + mbedtls_ssl_conf_authmode(&tcp_ssl->ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + } + + if (tcp_ssl->has_client_cert) { + TCP_SSL_DEBUG("loading client cert"); + ret = mbedtls_x509_crt_parse(&tcp_ssl->client_cert, (const unsigned char *) cli_cert, cli_cert_len); + if (ret < 0) { + tcp_ssl_free(tcp); + return handle_error(ret); + } + TCP_SSL_DEBUG("loading private key"); + ret = mbedtls_pk_parse_key(&tcp_ssl->client_key, (const unsigned char *) cli_key, cli_key_len, NULL, 0); + if (ret != 0) { + tcp_ssl_free(tcp); + return handle_error(ret); + } + mbedtls_ssl_conf_own_cert(&tcp_ssl->ssl_conf, &tcp_ssl->client_cert, &tcp_ssl->client_key); + } + + if(hostname != NULL) { + TCP_SSL_DEBUG("setting the hostname: %s\n", hostname); + if((ret = mbedtls_ssl_set_hostname(&tcp_ssl->ssl_ctx, hostname)) != 0){ + tcp_ssl_free(tcp); + + return handle_error(ret); + } + } + + mbedtls_ssl_conf_rng(&tcp_ssl->ssl_conf, mbedtls_ctr_drbg_random, &tcp_ssl->drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&tcp_ssl->ssl_ctx, &tcp_ssl->ssl_conf)) != 0) { + tcp_ssl_free(tcp); + + return handle_error(ret); + } + + mbedtls_ssl_set_bio(&tcp_ssl->ssl_ctx, (void*)tcp_ssl, tcp_ssl_send, tcp_ssl_recv, NULL); + + // Start handshake. + ret = mbedtls_ssl_handshake(&tcp_ssl->ssl_ctx); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + TCP_SSL_DEBUG("handshake error!\n"); + tcp_ssl_free(tcp); + return handle_error(ret); + } + + return ERR_OK; +} + +// Open an SSL connection using a PSK (pre-shared-key) cipher suite. +int tcp_ssl_new_psk_client(struct tcp_pcb *tcp, void *arg, const char* psk_ident, const char* pskey) { + tcp_ssl_t* tcp_ssl; + + if (pskey == NULL || psk_ident == NULL) { + TCP_SSL_DEBUG(" failed\n ! pre-shared key or identity is NULL\n\n"); + return -1; + } + + if(tcp == NULL) return -1; + if(tcp_ssl_get(tcp) != NULL) return -1; + + int pskey_len = strnlen(pskey, 2*MBEDTLS_PSK_MAX_LEN+1); + if ((pskey_len > 2*MBEDTLS_PSK_MAX_LEN) || (pskey_len & 1) != 0) { + TCP_SSL_DEBUG(" failed\n ! pre-shared key not valid hex or too long\n\n"); + return -1; + } + + tcp_ssl = tcp_ssl_new(tcp, arg); + if(tcp_ssl == NULL) return -1; + + mbedtls_entropy_init(&tcp_ssl->entropy_ctx); + mbedtls_ctr_drbg_init(&tcp_ssl->drbg_ctx); + mbedtls_ssl_init(&tcp_ssl->ssl_ctx); + mbedtls_ssl_config_init(&tcp_ssl->ssl_conf); + + mbedtls_ctr_drbg_seed(&tcp_ssl->drbg_ctx, mbedtls_entropy_func, + &tcp_ssl->entropy_ctx, (const uint8_t*)pers, sizeof(pers)); + + if(mbedtls_ssl_config_defaults(&tcp_ssl->ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) { + TCP_SSL_DEBUG("error setting SSL config.\n"); + + tcp_ssl_free(tcp); + return -1; + } + + //mbedtls_esp_enable_debug_log(&tcp_ssl->ssl_conf, 4); // 4=verbose + + int ret = 0; + + TCP_SSL_DEBUG("setting the pre-shared key.\n"); + // convert PSK from hex string to binary + unsigned char psk[MBEDTLS_PSK_MAX_LEN]; + size_t psk_len = pskey_len/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(&tcp_ssl->ssl_conf, psk, psk_len, + (const unsigned char *)psk_ident, strnlen(psk_ident, 64)); + if (ret != 0) { + TCP_SSL_DEBUG(" failed\n ! mbedtls_ssl_conf_psk returned -0x%x\n\n", -ret); + return handle_error(ret); + } + + mbedtls_ssl_conf_rng(&tcp_ssl->ssl_conf, mbedtls_ctr_drbg_random, &tcp_ssl->drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&tcp_ssl->ssl_ctx, &tcp_ssl->ssl_conf)) != 0) { + tcp_ssl_free(tcp); + + return handle_error(ret); + } + + mbedtls_ssl_set_bio(&tcp_ssl->ssl_ctx, (void*)tcp_ssl, tcp_ssl_send, tcp_ssl_recv, NULL); + + // Start handshake. + ret = mbedtls_ssl_handshake(&tcp_ssl->ssl_ctx); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + TCP_SSL_DEBUG("handshake error!\n"); + return handle_error(ret); + } + + return ERR_OK; +} + +// tcp_ssl_write writes len bytes from data into the TLS connection. I.e., data is plaintext, gets +// encrypted, and then transmitted on the TCP connection. +int tcp_ssl_write(struct tcp_pcb *tcp, uint8_t *data, size_t len) { + TCP_SSL_DEBUG("tcp_ssl_write(%x, %x, len=%d)\n", tcp, data, len); + if(tcp == NULL) { + return -1; + } + + tcp_ssl_t * tcp_ssl = tcp_ssl_get(tcp); + + if(tcp_ssl == NULL){ + return 0; + } + + tcp_ssl->last_wr = 0; + + int rc = mbedtls_ssl_write(&tcp_ssl->ssl_ctx, data, len); + + if (rc < 0){ + if (rc != MBEDTLS_ERR_SSL_WANT_READ && rc != MBEDTLS_ERR_SSL_WANT_WRITE) { + TCP_SSL_DEBUG("about to call mbedtls_ssl_write\n"); + return handle_error(rc); + } + if(rc != MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + TCP_SSL_DEBUG("tcp_ssl_write error: %d\r\n", rc); + } + return rc; + } + + return tcp_ssl->last_wr; +} + +// tcp_ssl_read is a callback that reads from the TLS connection, i.e., it calls mbedtls, which then +// tries to read from the TCP connection and decrypts it, tcp_ssl_read then calls the application's +// onData callback with the decrypted data. +int tcp_ssl_read(struct tcp_pcb *tcp, struct pbuf *p) { + TCP_SSL_DEBUG("tcp_ssl_read(%x, %x)\n", tcp, p); + if(tcp == NULL) { + return -1; + } + + int read_bytes = 0; + int total_bytes = 0; + static const size_t read_buf_size = 1024; + uint8_t read_buf[read_buf_size]; + + tcp_ssl_t *tcp_ssl = tcp_ssl_get(tcp); + if(tcp_ssl == NULL) { + return ERR_TCP_SSL_INVALID_CLIENTFD_DATA; + } + + if(p == NULL) { + return ERR_TCP_SSL_INVALID_DATA; + } + + // TCP_SSL_DEBUG("READY TO READ SOME DATA\n"); + + tcp_ssl->tcp_pbuf = p; + tcp_ssl->pbuf_offset = 0; + + do { + if(tcp_ssl->ssl_ctx.state != MBEDTLS_SSL_HANDSHAKE_OVER) { + TCP_SSL_DEBUG("start handshake: %d\n", tcp_ssl->ssl_ctx.state); + int ret = mbedtls_ssl_handshake(&tcp_ssl->ssl_ctx); + //handle_error(ret); + if(ret == 0) { + TCP_SSL_DEBUG("Protocol is %s Ciphersuite is %s\n", mbedtls_ssl_get_version(&tcp_ssl->ssl_ctx), mbedtls_ssl_get_ciphersuite(&tcp_ssl->ssl_ctx)); + + TCP_SSL_DEBUG("Verifying peer X.509 certificate..."); + if ((mbedtls_ssl_get_verify_result(&tcp_ssl->ssl_ctx)) != 0) { + TCP_SSL_DEBUG("handshake error: %d\n", ret); + handle_error(ret); + if(tcp_ssl->on_error) + tcp_ssl->on_error(tcp_ssl->arg, tcp_ssl->tcp, ret); + } else { + TCP_SSL_DEBUG("Certificate verified."); + } + + if(tcp_ssl->on_handshake) + tcp_ssl->on_handshake(tcp_ssl->arg, tcp_ssl->tcp, tcp_ssl); + } else if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + TCP_SSL_DEBUG("handshake error: %d\n", ret); + handle_error(ret); + + if(tcp_ssl->on_error) + tcp_ssl->on_error(tcp_ssl->arg, tcp_ssl->tcp, ret); + + break; + } + } else { + read_bytes = mbedtls_ssl_read(&tcp_ssl->ssl_ctx, (unsigned char *)&read_buf, read_buf_size); + TCP_SSL_DEBUG("tcp_ssl_read: read_bytes: %d, total_bytes: %d, tot_len: %d, pbuf_offset: %d\r\n", read_bytes, total_bytes, p->tot_len, tcp_ssl->pbuf_offset); + if(read_bytes < 0) { // SSL_OK + if(read_bytes == MBEDTLS_ERR_SSL_WANT_READ) { + break; + } else if(read_bytes != MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + TCP_SSL_DEBUG("tcp_ssl_read: read error: %d\n", read_bytes); + } + total_bytes = read_bytes; + break; + } else if(read_bytes > 0){ + if(tcp_ssl->on_data){ + tcp_ssl->on_data(tcp_ssl->arg, tcp, read_buf, read_bytes); + } + total_bytes+= read_bytes; + } + } + } while (p->tot_len - tcp_ssl->pbuf_offset > 0 || read_bytes > 0); + + tcp_ssl->tcp_pbuf = NULL; + + return total_bytes >= 0 ? 0 : total_bytes; // return error code +} + +int tcp_ssl_free(struct tcp_pcb *tcp) { + TCP_SSL_DEBUG("tcp_ssl_free(%x)\n", tcp); + if(tcp == NULL) { + return -1; + } + tcp_ssl_t * item = tcp_ssl_array; + if(item->tcp == tcp){ + tcp_ssl_array = tcp_ssl_array->next; + if(item->tcp_pbuf != NULL) { + pbuf_free(item->tcp_pbuf); + } + mbedtls_ssl_free(&item->ssl_ctx); + mbedtls_ssl_config_free(&item->ssl_conf); + mbedtls_ctr_drbg_free(&item->drbg_ctx); + mbedtls_entropy_free(&item->entropy_ctx); + if(item->has_ca_cert) { + mbedtls_x509_crt_free(&item->ca_cert); + } + if (item->has_client_cert) { + mbedtls_x509_crt_free(&item->client_cert); + mbedtls_pk_free(&item->client_key); + } + free(item); + return 0; + } + + while(item->next && item->next->tcp != tcp) + item = item->next; + + if(item->next == NULL){ + return ERR_TCP_SSL_INVALID_CLIENTFD_DATA;//item not found + } + tcp_ssl_t * i = item->next; + item->next = i->next; + if(i->tcp_pbuf != NULL){ + pbuf_free(i->tcp_pbuf); + } + mbedtls_ssl_free(&i->ssl_ctx); + mbedtls_ssl_config_free(&i->ssl_conf); + mbedtls_ctr_drbg_free(&i->drbg_ctx); + mbedtls_entropy_free(&i->entropy_ctx); + free(i); + + return 0; +} + +bool tcp_ssl_has(struct tcp_pcb *tcp) { + return tcp_ssl_get(tcp) != NULL; +} + +void tcp_ssl_arg(struct tcp_pcb *tcp, void * arg) { + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->arg = arg; + } +} + +void tcp_ssl_data(struct tcp_pcb *tcp, tcp_ssl_data_cb_t arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->on_data = arg; + } +} + +void tcp_ssl_handshake(struct tcp_pcb *tcp, tcp_ssl_handshake_cb_t arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->on_handshake = arg; + } +} + +void tcp_ssl_err(struct tcp_pcb *tcp, tcp_ssl_error_cb_t arg){ + tcp_ssl_t * item = tcp_ssl_get(tcp); + if(item) { + item->on_error = arg; + } +} + +#endif // ASYNC_TCP_SSL_ENABLED diff --git a/src/tcp_mbedtls.h b/src/tcp_mbedtls.h new file mode 100644 index 00000000..7a741fdc --- /dev/null +++ b/src/tcp_mbedtls.h @@ -0,0 +1,52 @@ +#ifndef LWIPR_MBEDTLS_H +#define LWIPR_MBEDTLS_H + +#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" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ERR_TCP_SSL_INVALID_SSL -101 +#define ERR_TCP_SSL_INVALID_TCP -102 +#define ERR_TCP_SSL_INVALID_CLIENTFD -103 +#define ERR_TCP_SSL_INVALID_CLIENTFD_DATA -104 +#define ERR_TCP_SSL_INVALID_DATA -105 + +struct tcp_pcb; +struct pbuf; +struct tcp_ssl_pcb; + +typedef void (* tcp_ssl_data_cb_t)(void *arg, struct tcp_pcb *tcp, uint8_t * data, size_t len); +typedef void (* tcp_ssl_handshake_cb_t)(void *arg, struct tcp_pcb *tcp, struct tcp_ssl_pcb* ssl); +typedef void (* tcp_ssl_error_cb_t)(void *arg, struct tcp_pcb *tcp, int8_t error); + +uint8_t tcp_ssl_has_client(); +int tcp_ssl_new_client(struct tcp_pcb *tcp, void *arg, const char* hostname, const char* root_ca, const size_t root_ca_len, + const char* cli_cert, const size_t cli_cert_len, const char* cli_key, const size_t cli_key_len); +int tcp_ssl_new_psk_client(struct tcp_pcb *tcp, void *arg, const char* psk_ident, const char* psk); +int tcp_ssl_write(struct tcp_pcb *tcp, uint8_t *data, size_t len); +int tcp_ssl_read(struct tcp_pcb *tcp, struct pbuf *p); +int tcp_ssl_handshake_step(struct tcp_pcb *tcp); +int tcp_ssl_free(struct tcp_pcb *tcp); +bool tcp_ssl_has(struct tcp_pcb *tcp); +void tcp_ssl_arg(struct tcp_pcb *tcp, void * arg); +void tcp_ssl_data(struct tcp_pcb *tcp, tcp_ssl_data_cb_t arg); +void tcp_ssl_handshake(struct tcp_pcb *tcp, tcp_ssl_handshake_cb_t arg); +void tcp_ssl_err(struct tcp_pcb *tcp, tcp_ssl_error_cb_t arg); + +#ifdef __cplusplus +} +#endif + + +#endif // LWIPR_MBEDTLS_H +#endif // ASYNC_TCP_SSL_ENABLED