From b319be14e85903e1d581121b35162d19f916fd11 Mon Sep 17 00:00:00 2001 From: Kimmo Vaisanen Date: Mon, 26 Aug 2019 12:54:20 +0300 Subject: [PATCH 1/3] Offloaded TLSSocket for BG96 Some external modems have an internal TLSSocket implementation which can be used instead of mbedtls based TLSSocket. Using offloaded TLSSocket can result in significantly reduced ROM usage. Offloaded TLSSocket can be enabled by enabling "nsapi.offload-tlssocket" and the used network stack (e.g. cellular modem's CellularStack class) must support the setsockopt's defined in nsapi_types.h. Compared to original mbedtls based TLSSocket, offloaded TLSSocket brings in one significant API limitation. Offloaded TLSSocket requires setting of certificates and keys after open() and before connect() calls, where mbedtls based TLSSocket allows setting these before open() call. --- .../cellular/framework/AT/AT_CellularStack.h | 2 + .../BG96/QUECTEL_BG96_CellularStack.cpp | 274 ++++++++++++++++-- .../QUECTEL/BG96/QUECTEL_BG96_CellularStack.h | 10 +- features/netsocket/TLSSocket.cpp | 70 ++++- features/netsocket/TLSSocket.h | 76 ++++- features/netsocket/mbed_lib.json | 4 + features/netsocket/nsapi_types.h | 12 + 7 files changed, 410 insertions(+), 38 deletions(-) diff --git a/features/cellular/framework/AT/AT_CellularStack.h b/features/cellular/framework/AT/AT_CellularStack.h index 5015aeee48b..f5825202bd5 100644 --- a/features/cellular/framework/AT/AT_CellularStack.h +++ b/features/cellular/framework/AT/AT_CellularStack.h @@ -102,6 +102,7 @@ class AT_CellularStack : public NetworkStack, public AT_CellularBase { started(false), tx_ready(false), rx_avail(false), + tls_socket(false), pending_bytes(0) { } @@ -119,6 +120,7 @@ class AT_CellularStack : public NetworkStack, public AT_CellularBase { bool started; // socket has been opened on modem stack bool tx_ready; // socket is ready for sending on modem stack bool rx_avail; // socket has data for reading on modem stack + bool tls_socket; // socket uses modem's internal TLS socket functionality nsapi_size_t pending_bytes; // The number of received bytes pending }; diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp index ee9652e4f64..b5a452e613a 100644 --- a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp @@ -18,6 +18,15 @@ #include #include "QUECTEL/BG96/QUECTEL_BG96_CellularStack.h" #include "CellularLog.h" +#include "netsocket/TLSSocket.h" + +// Ref: Quectel_BG96_SSL_AT_Commands_Manual, ch 2.1.1 AT+QSSLCFG +static const int BG96_SUPPORTED_SSL_VERSION = 4; // All +static const char BG96_SUPPORTED_CIPHER_SUITE[] = "0xFFFF"; // Support all + +// TODO: At the moment we support only one active SSL context +// Later can be expanded to support multiple contexts. Modem supports IDs 0-5. +static const int sslctxID = 0; using namespace mbed; @@ -25,12 +34,27 @@ QUECTEL_BG96_CellularStack::QUECTEL_BG96_CellularStack(ATHandler &atHandler, int #ifdef MBED_CONF_CELLULAR_OFFLOAD_DNS_QUERIES , _dns_callback(NULL), _dns_version(NSAPI_UNSPEC) #endif + , _tls_sec_level(0) { _at.set_urc_handler("+QIURC: \"recv", mbed::Callback(this, &QUECTEL_BG96_CellularStack::urc_qiurc_recv)); _at.set_urc_handler("+QIURC: \"close", mbed::Callback(this, &QUECTEL_BG96_CellularStack::urc_qiurc_closed)); #ifdef MBED_CONF_CELLULAR_OFFLOAD_DNS_QUERIES _at.set_urc_handler("+QIURC: \"dnsgip\",", mbed::Callback(this, &QUECTEL_BG96_CellularStack::urc_qiurc_dnsgip)); #endif + + _at.set_urc_handler("+QSSLURC: \"recv", mbed::Callback(this, &QUECTEL_BG96_CellularStack::urc_qiurc_recv)); + _at.set_urc_handler("+QSSLURC: \"close", mbed::Callback(this, &QUECTEL_BG96_CellularStack::urc_qiurc_closed)); + + // TODO: this needs to be handled properly, but now making just a quick hack + // Close all SSL sockets if open. This can happen for example if application processor + // was reset but modem not. Old sockets are still up and running and it prevents + // new SSL configurations and creating new sockets. + for (int i = 0; i < 12; i++) { + _at.clear_error(); + tr_debug("Closing SSL socket %d...", i); + _at.at_cmd_discard("+QSSLCLOSE", "=", "%d", i); + } + _at.clear_error(); } QUECTEL_BG96_CellularStack::~QUECTEL_BG96_CellularStack() @@ -52,7 +76,7 @@ nsapi_error_t QUECTEL_BG96_CellularStack::socket_connect(nsapi_socket_t handle, CellularSocket *socket = (CellularSocket *)handle; int modem_connect_id = -1; - int err = -1; + int err = NSAPI_ERROR_NO_CONNECTION; int request_connect_id = find_socket_index(socket); // assert here as its a programming error if the socket container doesn't contain @@ -61,25 +85,49 @@ nsapi_error_t QUECTEL_BG96_CellularStack::socket_connect(nsapi_socket_t handle, _at.lock(); if (socket->proto == NSAPI_TCP) { - char ipdot[NSAPI_IP_SIZE]; - ip2dot(address, ipdot); - _at.at_cmd_discard("+QIOPEN", "=", "%d%d%s%s%d%d%d", _cid, request_connect_id, "TCP", - ipdot, address.get_port(), socket->localAddress.get_port(), 0); - - handle_open_socket_response(modem_connect_id, err); - - if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { - if (err == BG96_SOCKET_BIND_FAIL) { - socket->id = -1; + if (socket->tls_socket) { + if (_tls_sec_level == 0) { _at.unlock(); - return NSAPI_ERROR_PARAMETER; + return NSAPI_ERROR_AUTH_FAILURE; } - _at.at_cmd_discard("+QICLOSE", "=", "%d", modem_connect_id); + _at.at_cmd_discard("+QSSLOPEN", "=", "%d%d%d%s%d%d", _cid, sslctxID, request_connect_id, + address.get_ip_address(), address.get_port(), 0); + handle_open_socket_response(modem_connect_id, err, true); + + if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { + if (err == BG96_SOCKET_BIND_FAIL) { + socket->id = -1; + _at.unlock(); + return NSAPI_ERROR_PARAMETER; + } + socket_close_impl(modem_connect_id); + + _at.at_cmd_discard("+QSSLOPEN", "=", "%d%d%d%s%d%d", _cid, sslctxID, request_connect_id, + address.get_ip_address(), address.get_port(), 0); + handle_open_socket_response(modem_connect_id, err, true); + } + } else { + char ipdot[NSAPI_IP_SIZE]; + ip2dot(address, ipdot); _at.at_cmd_discard("+QIOPEN", "=", "%d%d%s%s%d%d%d", _cid, request_connect_id, "TCP", ipdot, address.get_port(), socket->localAddress.get_port(), 0); - handle_open_socket_response(modem_connect_id, err); + handle_open_socket_response(modem_connect_id, err, false); + + if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { + if (err == BG96_SOCKET_BIND_FAIL) { + socket->id = -1; + _at.unlock(); + return NSAPI_ERROR_PARAMETER; + } + socket_close_impl(modem_connect_id); + + _at.at_cmd_discard("+QIOPEN", "=", "%d%d%s%s%d%d%d", _cid, request_connect_id, "TCP", + ipdot, address.get_port(), socket->localAddress.get_port(), 0); + + handle_open_socket_response(modem_connect_id, err, false); + } } } @@ -91,14 +139,14 @@ nsapi_error_t QUECTEL_BG96_CellularStack::socket_connect(nsapi_socket_t handle, nsapi_error_t ret_val = _at.get_last_error(); _at.unlock(); - if ((ret_val == NSAPI_ERROR_OK) && (modem_connect_id == request_connect_id)) { + if ((!err) && (ret_val == NSAPI_ERROR_OK) && (modem_connect_id == request_connect_id)) { socket->id = request_connect_id; socket->remoteAddress = address; socket->connected = true; return NSAPI_ERROR_OK; } - return NSAPI_ERROR_NO_CONNECTION; + return err; } void QUECTEL_BG96_CellularStack::urc_qiurc_recv() @@ -182,21 +230,41 @@ bool QUECTEL_BG96_CellularStack::is_protocol_supported(nsapi_protocol_t protocol nsapi_error_t QUECTEL_BG96_CellularStack::socket_close_impl(int sock_id) { _at.set_at_timeout(BG96_CLOSE_SOCKET_TIMEOUT); - nsapi_error_t err = _at.at_cmd_discard("+QICLOSE", "=", "%d", sock_id); + nsapi_error_t err; + CellularSocket *socket = find_socket(sock_id); + if (socket && socket->tls_socket) { + err = _at.at_cmd_discard("+QSSLCLOSE", "=", "%d", sock_id); + if (err == NSAPI_ERROR_OK) { + // Disable TLSSocket settings to prevent reuse on next socket without setting the values + _tls_sec_level = 0; + err = _at.at_cmd_discard("+QSSLCFG", "=\"seclevel\",", "%d%d", sslctxID, _tls_sec_level); + } + } else { + err = _at.at_cmd_discard("+QICLOSE", "=", "%d", sock_id); + } _at.restore_at_timeout(); return err; } -void QUECTEL_BG96_CellularStack::handle_open_socket_response(int &modem_connect_id, int &err) +void QUECTEL_BG96_CellularStack::handle_open_socket_response(int &modem_connect_id, int &err, bool tlssocket) { // OK // QIOPEN -> should be handled as URC? _at.set_at_timeout(BG96_CREATE_SOCKET_TIMEOUT); - _at.resp_start("+QIOPEN:"); + + if (tlssocket) { + _at.resp_start("+QSSLOPEN:"); + } else { + _at.resp_start("+QIOPEN:"); + } + _at.restore_at_timeout(); modem_connect_id = _at.read_int(); err = _at.read_int(); + if (tlssocket && err != 0) { + err = NSAPI_ERROR_AUTH_FAILURE; + } } nsapi_error_t QUECTEL_BG96_CellularStack::create_socket_impl(CellularSocket *socket) @@ -215,7 +283,7 @@ nsapi_error_t QUECTEL_BG96_CellularStack::create_socket_impl(CellularSocket *soc (_ip_ver_sendto == NSAPI_IPv4) ? "127.0.0.1" : "0:0:0:0:0:0:0:1", remote_port, socket->localAddress.get_port(), 0); - handle_open_socket_response(modem_connect_id, err); + handle_open_socket_response(modem_connect_id, err, false); if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { if (err == BG96_SOCKET_BIND_FAIL) { @@ -228,7 +296,7 @@ nsapi_error_t QUECTEL_BG96_CellularStack::create_socket_impl(CellularSocket *soc (_ip_ver_sendto == NSAPI_IPv4) ? "127.0.0.1" : "0:0:0:0:0:0:0:1", remote_port, socket->localAddress.get_port(), 0); - handle_open_socket_response(modem_connect_id, err); + handle_open_socket_response(modem_connect_id, err, false); } } else if (socket->proto == NSAPI_UDP && socket->connected) { char ipdot[NSAPI_IP_SIZE]; @@ -236,7 +304,7 @@ nsapi_error_t QUECTEL_BG96_CellularStack::create_socket_impl(CellularSocket *soc _at.at_cmd_discard("+QIOPEN", "=", "%d%d%s%s%d", _cid, request_connect_id, "UDP", ipdot, socket->remoteAddress.get_port()); - handle_open_socket_response(modem_connect_id, err); + handle_open_socket_response(modem_connect_id, err, false); if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { if (err == BG96_SOCKET_BIND_FAIL) { @@ -248,7 +316,7 @@ nsapi_error_t QUECTEL_BG96_CellularStack::create_socket_impl(CellularSocket *soc _at.at_cmd_discard("+QIOPEN", "=", "%d%d%s%s%d", _cid, request_connect_id, "UDP", ipdot, socket->remoteAddress.get_port()); - handle_open_socket_response(modem_connect_id, err); + handle_open_socket_response(modem_connect_id, err, false); } } @@ -283,8 +351,12 @@ nsapi_size_or_error_t QUECTEL_BG96_CellularStack::socket_sendto_impl(CellularSoc int sent_len_before = 0; int sent_len_after = 0; - // Get the sent count before sending - _at.at_cmd_int("+QISEND", "=", sent_len_before, "%d%d", socket->id, 0); + if (socket->tls_socket) { + sent_len_after = size; + } else { + // Get the sent count before sending + _at.at_cmd_int("+QISEND", "=", sent_len_before, "%d%d", socket->id, 0); + } // Send if (socket->proto == NSAPI_UDP) { @@ -293,7 +365,11 @@ nsapi_size_or_error_t QUECTEL_BG96_CellularStack::socket_sendto_impl(CellularSoc _at.cmd_start_stop("+QISEND", "=", "%d%d%s%d", socket->id, size, ipdot, address.get_port()); } else { - _at.cmd_start_stop("+QISEND", "=", "%d%d", socket->id, size); + if (socket->tls_socket) { + _at.cmd_start_stop("+QSSLSEND", "=", "%d%d", socket->id, size); + } else { + _at.cmd_start_stop("+QISEND", "=", "%d%d", socket->id, size); + } } _at.resp_start(">"); @@ -310,7 +386,12 @@ nsapi_size_or_error_t QUECTEL_BG96_CellularStack::socket_sendto_impl(CellularSoc } // Get the sent count after sending - nsapi_size_or_error_t err = _at.at_cmd_int("+QISEND", "=", sent_len_after, "%d%d", socket->id, 0); + nsapi_size_or_error_t err = NSAPI_ERROR_OK; + + if (!socket->tls_socket) { + err = _at.at_cmd_int("+QISEND", "=", sent_len_after, "%d%d", socket->id, 0); + } + if (err == NSAPI_ERROR_OK) { sent_len = sent_len_after - sent_len_before; return sent_len; @@ -323,22 +404,34 @@ nsapi_size_or_error_t QUECTEL_BG96_CellularStack::socket_recvfrom_impl(CellularS void *buffer, nsapi_size_t size) { nsapi_size_or_error_t recv_len = 0; - int port; + int port = -1; char ip_address[NSAPI_IP_SIZE + 1]; if (socket->proto == NSAPI_TCP) { // do not read more than max size size = size > BG96_MAX_RECV_SIZE ? BG96_MAX_RECV_SIZE : size; - _at.cmd_start_stop("+QIRD", "=", "%d%d", socket->id, size); + if (socket->tls_socket) { + _at.cmd_start_stop("+QSSLRECV", "=", "%d%d", socket->id, size); + } else { + _at.cmd_start_stop("+QIRD", "=", "%d%d", socket->id, size); + } } else { _at.cmd_start_stop("+QIRD", "=", "%d", socket->id); } - _at.resp_start("+QIRD:"); + if (socket->tls_socket) { + _at.resp_start("+QSSLRECV:"); + } else { + _at.resp_start("+QIRD:"); + } + recv_len = _at.read_int(); - _at.read_string(ip_address, sizeof(ip_address)); - port = _at.read_int(); if (recv_len > 0) { + // UDP has remote_IP and remote_port parameters + if (socket->proto == NSAPI_UDP) { + _at.read_string(ip_address, sizeof(ip_address)); + port = _at.read_int(); + } // do not read more than buffer size recv_len = recv_len > (nsapi_size_or_error_t)size ? size : recv_len; _at.read_bytes((uint8_t *)buffer, recv_len); @@ -436,3 +529,120 @@ void QUECTEL_BG96_CellularStack::ip2dot(const SocketAddress &ip, char *dot) *dot = '\0'; } } + +nsapi_error_t QUECTEL_BG96_CellularStack::set_to_modem_impl(const char *filename, const char *config, const char *data, size_t size) +{ + // Delete old file from the modem. + _at.at_cmd_discard("+QFDEL", "=", "%s", filename); + _at.clear_error(); // Ignore error if file didn't exist + + // Upload new file to modem + _at.cmd_start_stop("+QFUPL", "=", "%s%d", filename, size); + _at.resp_start("CONNECT"); + _at.write_bytes((uint8_t *)data, size); + _at.resp_start("+QFUPL:"); + size_t upload_size = _at.read_int(); + _at.resp_stop(); + if (upload_size != size) { + tr_error("Upload error! orig = %d, uploaded = %d", size, upload_size); + return NSAPI_ERROR_DEVICE_ERROR; + } + + // Configure into use + _at.at_cmd_discard("+QSSLCFG", "=", "%s%d%s", config, sslctxID, filename); + + return _at.get_last_error(); +} + + +nsapi_error_t QUECTEL_BG96_CellularStack::setsockopt(nsapi_socket_t handle, int level, + int optname, const void *optval, unsigned optlen) +{ + CellularSocket *socket = (CellularSocket *)handle; + nsapi_error_t ret = NSAPI_ERROR_OK; + + if (level == NSAPI_TLSSOCKET_LEVEL) { + if (optval) { + _at.lock(); + switch (optname) { + case NSAPI_TLSSOCKET_ENABLE: { + MBED_ASSERT(optlen == sizeof(bool)); + bool *enabled = (bool *)optval; + if (socket->proto == NSAPI_TCP) { + socket->tls_socket = enabled; + + if (enabled) { + _at.at_cmd_discard("+QSSLCFG", "=\"seclevel\",", "%d%d", sslctxID, _tls_sec_level); + + _at.at_cmd_discard("+QSSLCFG", "=\"sslversion\",", "%d%d", sslctxID, BG96_SUPPORTED_SSL_VERSION); + + _at.cmd_start("AT+QSSLCFG=\"ciphersuite\","); + _at.write_int(sslctxID); + _at.write_string(BG96_SUPPORTED_CIPHER_SUITE, false); + _at.cmd_stop_read_resp(); + + ret = _at.get_last_error(); + } + } else { + tr_error("Trying to set non-TCPSocket as TLSSocket"); + ret = NSAPI_ERROR_PARAMETER; + } + } + break; + + case NSAPI_TLSSOCKET_SET_HOSTNAME: { + const char *hostname = (const char *)optval; + _at.at_cmd_discard("+QSSLCFG", "=\"checkhost\",", "%d%s", sslctxID, hostname); + ret = _at.get_last_error(); + } + break; + + case NSAPI_TLSSOCKET_SET_CACERT: { + const char *cacert = (const char *)optval; + ret = set_to_modem_impl("cacert.pem", "cacert", cacert, optlen); + + // Set sec level to "Manage server authentication" if only cacert is in use + if (ret == NSAPI_ERROR_OK && _tls_sec_level == 0) { + _tls_sec_level = 1; + } + } + break; + + case NSAPI_TLSSOCKET_SET_CLCERT: { + const char *clcert = (const char *)optval; + ret = set_to_modem_impl("clcert.pem", "clientcert", clcert, optlen); + + // Set sec level to "Manage server and client authentication if requested by the remote server" + if (ret == NSAPI_ERROR_OK) { + _tls_sec_level = 2; + } + } + break; + + case NSAPI_TLSSOCKET_SET_CLKEY: { + const char *clkey = (const char *)optval; + ret = set_to_modem_impl("client.key", "clientkey", clkey, optlen); + + // Set sec level to "Manage server and client authentication if requested by the remote server" + if (ret == NSAPI_ERROR_OK) { + _tls_sec_level = 2; + } + } + break; + + default: + tr_error("Unsupported sockopt (%d)", optname); + ret = NSAPI_ERROR_UNSUPPORTED; + } + _at.unlock(); + } else { + tr_error("No optval!"); + ret = NSAPI_ERROR_PARAMETER; + } + } else { + tr_warning("Unsupported level (%d)", level); + ret = NSAPI_ERROR_UNSUPPORTED; + } + + return ret; +} diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h index 0552f811f5e..e056344b73b 100644 --- a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h @@ -55,6 +55,9 @@ class QUECTEL_BG96_CellularStack : public AT_CellularStack { virtual nsapi_error_t gethostbyname_async_cancel(int id); #endif + virtual nsapi_error_t setsockopt(nsapi_socket_t handle, int level, + int optname, const void *optval, unsigned optlen); + protected: // AT_CellularStack virtual int get_max_socket_count(); @@ -79,7 +82,9 @@ class QUECTEL_BG96_CellularStack : public AT_CellularStack { // URC handler for socket being closed void urc_qiurc_closed(); - void handle_open_socket_response(int &modem_connect_id, int &err); + void handle_open_socket_response(int &modem_connect_id, int &err, bool tlssocket); + + nsapi_error_t set_to_modem_impl(const char *filename, const char *config, const char *data, size_t size); #ifdef MBED_CONF_CELLULAR_OFFLOAD_DNS_QUERIES // URC handler for DNS query @@ -89,6 +94,9 @@ class QUECTEL_BG96_CellularStack : public AT_CellularStack { hostbyname_cb_t _dns_callback; nsapi_version_t _dns_version; #endif + + uint8_t _tls_sec_level; + /** Convert IP address to dotted string representation * * BG96 requires consecutive zeros so can't use get_ip_address or ip6tos directly. diff --git a/features/netsocket/TLSSocket.cpp b/features/netsocket/TLSSocket.cpp index bb0c7eb9e4e..359613a98b3 100644 --- a/features/netsocket/TLSSocket.cpp +++ b/features/netsocket/TLSSocket.cpp @@ -20,6 +20,8 @@ #define TRACE_GROUP "TLSS" #include "mbed-trace/mbed_trace.h" +#if !defined(MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET) || !(MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET) + // This class requires Mbed TLS SSL/TLS client code #if defined(MBEDTLS_SSL_CLI_C) @@ -46,5 +48,71 @@ TLSSocket::~TLSSocket() */ close(); } - #endif // MBEDTLS_SSL_CLI_C + +#else // MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET + +TLSSocket::TLSSocket() +{ +} + +TLSSocket::~TLSSocket() +{ +} + + +nsapi_error_t TLSSocket::set_hostname(const char *hostname) +{ + return setsockopt(NSAPI_TLSSOCKET_LEVEL, NSAPI_TLSSOCKET_SET_HOSTNAME, hostname, strlen(hostname)); +} + +nsapi_error_t TLSSocket::set_root_ca_cert(const void *root_ca, size_t len) +{ + return setsockopt(NSAPI_TLSSOCKET_LEVEL, NSAPI_TLSSOCKET_SET_CACERT, root_ca, len); +} + +nsapi_error_t TLSSocket::set_root_ca_cert(const char *root_ca_pem) +{ + return set_root_ca_cert(root_ca_pem, strlen(root_ca_pem)); +} + +nsapi_error_t TLSSocket::set_client_cert_key(const void *client_cert, size_t client_cert_len, + const void *client_private_key_pem, size_t client_private_key_len) +{ + nsapi_error_t ret = setsockopt(NSAPI_TLSSOCKET_LEVEL, NSAPI_TLSSOCKET_SET_CLCERT, client_cert, client_cert_len); + if (ret == NSAPI_ERROR_OK) { + ret = setsockopt(NSAPI_TLSSOCKET_LEVEL, NSAPI_TLSSOCKET_SET_CLKEY, client_private_key_pem, client_private_key_len); + } + return ret; +} + +nsapi_error_t TLSSocket::set_client_cert_key(const char *client_cert_pem, const char *client_private_key_pem) +{ + return set_client_cert_key(client_cert_pem, strlen(client_cert_pem), client_private_key_pem, strlen(client_private_key_pem)); +} + +nsapi_error_t TLSSocket::connect(const char *host, uint16_t port) +{ + nsapi_error_t ret = enable_tlssocket(); + if (ret == NSAPI_ERROR_OK) { + ret = TCPSocket::connect(host, port); + } + return ret; +} + +nsapi_error_t TLSSocket::connect(const SocketAddress &address) +{ + nsapi_error_t ret = enable_tlssocket(); + if (ret == NSAPI_ERROR_OK) { + ret = TCPSocket::connect(address); + } + return ret; +} + +nsapi_error_t TLSSocket::enable_tlssocket() +{ + bool enabled = true; + return setsockopt(NSAPI_TLSSOCKET_LEVEL, NSAPI_TLSSOCKET_ENABLE, &enabled, sizeof(enabled)); +} + +#endif // MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET diff --git a/features/netsocket/TLSSocket.h b/features/netsocket/TLSSocket.h index 1ce1e7d3d38..98b4347f70f 100644 --- a/features/netsocket/TLSSocket.h +++ b/features/netsocket/TLSSocket.h @@ -23,7 +23,6 @@ #define _MBED_HTTPS_TLS_TCP_SOCKET_H_ #include "netsocket/TCPSocket.h" -#include "TLSSocketWrapper.h" #include "mbedtls/platform.h" #include "mbedtls/ssl.h" @@ -31,9 +30,13 @@ #include "mbedtls/ctr_drbg.h" #include "mbedtls/error.h" +#if !defined(MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET) || !(MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET) + // This class requires Mbed TLS SSL/TLS client code #if defined(MBEDTLS_SSL_CLI_C) || defined(DOXYGEN_ONLY) +#include "TLSSocketWrapper.h" + /** * \brief TLSSocket is a wrapper around TCPSocket for interacting with TLS servers. * @@ -62,7 +65,7 @@ class TLSSocket : public TLSSocketWrapper { * clear internal TLS memory structures. * * @param stack Network stack as target for socket. - * @return 0 on success, negative error code on failure. + * @return NSAPI_ERROR_OK on success, negative error code on failure. */ virtual nsapi_error_t open(NetworkStack *stack) { @@ -87,15 +90,80 @@ class TLSSocket : public TLSSocketWrapper { * * @param host Hostname of the remote host. * @param port Port of the remote host. - * @return 0 on success, negative error code on failure. + * @return NSAPI_ERROR_OK on success, negative error code on failure. */ nsapi_error_t connect(const char *host, uint16_t port); private: TCPSocket tcp_socket; }; - #endif // MBEDTLS_SSL_CLI_C + +#else // MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET + +class TLSSocket : public TCPSocket { +public: + TLSSocket(); + virtual ~TLSSocket(); + + /** Set hostname. + * + * TLSSocket requires hostname used to verify the certificate. + * If hostname is not given in constructor, this function must be used before + * starting the TLS handshake. + * + * @param hostname Hostname of the remote host, used for certificate checking. + */ + nsapi_error_t set_hostname(const char *hostname); + + /** Sets the certification of Root CA. + * + * @note Must be called after open() before calling connect() + * + * @param root_ca Root CA Certificate in any Mbed TLS-supported format. + * @param len Length of certificate (including terminating 0 for PEM). + * @return NSAPI_ERROR_OK on success, negative error code on failure. + */ + virtual nsapi_error_t set_root_ca_cert(const void *root_ca, size_t len); + + /** Sets the certification of Root CA. + * + * @note Must be called after open() before calling connect() + * + * @param root_ca_pem Root CA Certificate in PEM format. + */ + virtual nsapi_error_t set_root_ca_cert(const char *root_ca_pem); + + + /** Sets client certificate, and client private key. + * + * @param client_cert Client certification in PEM or DER format. + * @param client_cert_len Certificate size including the terminating null byte for PEM data. + * @param client_private_key_pem Client private key in PEM or DER format. + * @param client_private_key_len Key size including the terminating null byte for PEM data + * @return NSAPI_ERROR_OK on success, negative error code on failure. + */ + virtual nsapi_error_t set_client_cert_key(const void *client_cert, size_t client_cert_len, + const void *client_private_key_pem, size_t client_private_key_len); + + /** Sets client certificate, and client private key. + * + * @param client_cert_pem Client certification in PEM format. + * @param client_private_key_pem Client private key in PEM format. + * @return NSAPI_ERROR_OK on success, negative error code on failure. + */ + virtual nsapi_error_t set_client_cert_key(const char *client_cert_pem, const char *client_private_key_pem); + + // From TCPSocket + virtual nsapi_error_t connect(const char *host, uint16_t port); + virtual nsapi_error_t connect(const SocketAddress &address); + +protected: + virtual nsapi_error_t enable_tlssocket(); +}; + +#endif // MBED_CONF_NSAPI_OFFLOAD_TLSSOCKET + #endif // _MBED_HTTPS_TLS_TCP_SOCKET_H_ /** @} */ diff --git a/features/netsocket/mbed_lib.json b/features/netsocket/mbed_lib.json index 002c5badaa3..352fa88a74d 100644 --- a/features/netsocket/mbed_lib.json +++ b/features/netsocket/mbed_lib.json @@ -65,6 +65,10 @@ "socket-stats-max-count": { "help": "Maximum number of socket statistics cached", "value": 10 + }, + "offload-tlssocket" : { + "help": "Use external TLSSocket implementation. Used network stack must support external TLSSocket setsockopt values (see nsapi_types.h)", + "value": null } }, "target_overrides": { diff --git a/features/netsocket/nsapi_types.h b/features/netsocket/nsapi_types.h index 00e07588d05..c640aa7dcd4 100644 --- a/features/netsocket/nsapi_types.h +++ b/features/netsocket/nsapi_types.h @@ -269,6 +269,18 @@ typedef enum nsapi_socket_option { NSAPI_BIND_TO_DEVICE, /*!< Bind socket network interface name*/ } nsapi_socket_option_t; +typedef enum nsapi_tlssocket_level { + NSAPI_TLSSOCKET_LEVEL = 7099, /*!< TLSSocket option level - see nsapi_tlssocket_option_t for options*/ +} nsapi_tlssocket_level_t; + +typedef enum nsapi_tlssocket_option { + NSAPI_TLSSOCKET_SET_HOSTNAME, /*!< Set host name */ + NSAPI_TLSSOCKET_SET_CACERT, /*!< Set server CA certificate */ + NSAPI_TLSSOCKET_SET_CLCERT, /*!< Set client certificate */ + NSAPI_TLSSOCKET_SET_CLKEY, /*!< Set client key */ + NSAPI_TLSSOCKET_ENABLE /*!< Enable TLSSocket */ +} nsapi_tlssocket_option_t; + /** Supported IP protocol versions of IP stack * * @enum nsapi_ip_stack From 6ba0efc969a9f9c82fb004066d10440254d42f6c Mon Sep 17 00:00:00 2001 From: Kimmo Vaisanen Date: Mon, 26 Aug 2019 12:54:33 +0300 Subject: [PATCH 2/3] Cellular: Use more specific error codes for socket open and connect - When calling socket APIs when socket is not open, NSAPI_ERROR_NO_SOCKET will be returned instead of generic NSAPI_ERROR_DEVICE_ERROR - If socket_send() is called when connection is not open, NSAPI_ERROR_NO_CONNECTION will be returned instead of generic NSAPI_ERROR_DEVICE_ERROR --- .../at_cellularstack/at_cellularstacktest.cpp | 14 ++++++------- .../framework/AT/AT_CellularStack.cpp | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/UNITTESTS/features/cellular/framework/AT/at_cellularstack/at_cellularstacktest.cpp b/UNITTESTS/features/cellular/framework/AT/at_cellularstack/at_cellularstacktest.cpp index 57ad8519f52..3615cb2c850 100644 --- a/UNITTESTS/features/cellular/framework/AT/at_cellularstack/at_cellularstacktest.cpp +++ b/UNITTESTS/features/cellular/framework/AT/at_cellularstack/at_cellularstacktest.cpp @@ -244,7 +244,7 @@ TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_bind) MyStack st(at, 0, IPV6_STACK); SocketAddress addr; ATHandler_stub::nsapi_error_value = NSAPI_ERROR_ALREADY; - EXPECT_EQ(st.socket_bind(NULL, addr), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_bind(NULL, addr), NSAPI_ERROR_NO_SOCKET); EXPECT_EQ(st.socket_bind(&st.socket, addr), NSAPI_ERROR_ALREADY); } @@ -267,7 +267,7 @@ TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_connect) MyStack st(at, 0, IPV6_STACK); SocketAddress addr; - EXPECT_EQ(st.socket_connect(NULL, addr), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_connect(NULL, addr), NSAPI_ERROR_NO_SOCKET); EXPECT_EQ(st.socket_connect(&st.socket, addr), NSAPI_ERROR_OK); } @@ -290,9 +290,9 @@ TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_send) ATHandler at(&fh1, que, 0, ","); MyStack st(at, 0, IPV6_STACK); - EXPECT_EQ(st.socket_send(NULL, "addr", 4), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_send(NULL, "addr", 4), NSAPI_ERROR_NO_SOCKET); - EXPECT_EQ(st.socket_send(&st.socket, "addr", 4), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_send(&st.socket, "addr", 4), NSAPI_ERROR_NO_CONNECTION); SocketAddress addr; st.max_sock_value = 1; @@ -312,7 +312,7 @@ TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_sendto) MyStack st(at, 0, IPV6_STACK); SocketAddress addr; - EXPECT_EQ(st.socket_sendto(NULL, addr, "addr", 4), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_sendto(NULL, addr, "addr", 4), NSAPI_ERROR_NO_SOCKET); st.max_sock_value = 1; st.bool_value = true; @@ -334,7 +334,7 @@ TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_recv) MyStack st(at, 0, IPV6_STACK); char table[4]; - EXPECT_EQ(st.socket_recv(NULL, table, 4), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_recv(NULL, table, 4), NSAPI_ERROR_NO_SOCKET); } TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_recvfrom) @@ -345,7 +345,7 @@ TEST_F(TestAT_CellularStack, test_AT_CellularStack_socket_recvfrom) MyStack st(at, 0, IPV6_STACK); char table[4]; - EXPECT_EQ(st.socket_recvfrom(NULL, NULL, table, 4), NSAPI_ERROR_DEVICE_ERROR); + EXPECT_EQ(st.socket_recvfrom(NULL, NULL, table, 4), NSAPI_ERROR_NO_SOCKET); SocketAddress addr; st.max_sock_value = 1; diff --git a/features/cellular/framework/AT/AT_CellularStack.cpp b/features/cellular/framework/AT/AT_CellularStack.cpp index 621431eabe0..c90411cd8f9 100644 --- a/features/cellular/framework/AT/AT_CellularStack.cpp +++ b/features/cellular/framework/AT/AT_CellularStack.cpp @@ -156,7 +156,7 @@ nsapi_error_t AT_CellularStack::socket_close(nsapi_socket_t handle) struct CellularSocket *socket = (struct CellularSocket *)handle; if (!socket) { - return err; + return NSAPI_ERROR_NO_SOCKET; } int sock_id = socket->id; @@ -192,7 +192,7 @@ nsapi_error_t AT_CellularStack::socket_bind(nsapi_socket_t handle, const SocketA { struct CellularSocket *socket = (CellularSocket *)handle; if (!socket) { - return NSAPI_ERROR_DEVICE_ERROR; + return NSAPI_ERROR_NO_SOCKET; } if (addr) { @@ -220,14 +220,14 @@ nsapi_error_t AT_CellularStack::socket_bind(nsapi_socket_t handle, const SocketA nsapi_error_t AT_CellularStack::socket_listen(nsapi_socket_t handle, int backlog) { - return NSAPI_ERROR_UNSUPPORTED;; + return NSAPI_ERROR_UNSUPPORTED; } nsapi_error_t AT_CellularStack::socket_connect(nsapi_socket_t handle, const SocketAddress &addr) { CellularSocket *socket = (CellularSocket *)handle; if (!socket) { - return NSAPI_ERROR_DEVICE_ERROR; + return NSAPI_ERROR_NO_SOCKET; } socket->remoteAddress = addr; socket->connected = true; @@ -237,14 +237,17 @@ nsapi_error_t AT_CellularStack::socket_connect(nsapi_socket_t handle, const Sock nsapi_error_t AT_CellularStack::socket_accept(void *server, void **socket, SocketAddress *addr) { - return NSAPI_ERROR_UNSUPPORTED;; + return NSAPI_ERROR_UNSUPPORTED; } nsapi_size_or_error_t AT_CellularStack::socket_send(nsapi_socket_t handle, const void *data, unsigned size) { CellularSocket *socket = (CellularSocket *)handle; - if (!socket || !socket->connected) { - return NSAPI_ERROR_DEVICE_ERROR; + if (!socket) { + return NSAPI_ERROR_NO_SOCKET; + } + if (!socket->connected) { + return NSAPI_ERROR_NO_CONNECTION; } return socket_sendto(handle, socket->remoteAddress, data, size); } @@ -253,7 +256,7 @@ nsapi_size_or_error_t AT_CellularStack::socket_sendto(nsapi_socket_t handle, con { CellularSocket *socket = (CellularSocket *)handle; if (!socket) { - return NSAPI_ERROR_DEVICE_ERROR; + return NSAPI_ERROR_NO_SOCKET; } if (socket->closed && !socket->rx_avail) { @@ -319,7 +322,7 @@ nsapi_size_or_error_t AT_CellularStack::socket_recvfrom(nsapi_socket_t handle, S { CellularSocket *socket = (CellularSocket *)handle; if (!socket) { - return NSAPI_ERROR_DEVICE_ERROR; + return NSAPI_ERROR_NO_SOCKET; } if (socket->closed) { From 986204f2699ecdb0d422bd119f5c8c5bd406ce2f Mon Sep 17 00:00:00 2001 From: Kimmo Vaisanen Date: Mon, 26 Aug 2019 10:38:28 +0300 Subject: [PATCH 3/3] Fix TLSSocket tests - set certs and keys after socket open() as required by offloaded TLSSocket - Added more checks for invalid handshake test and removed google.com test as as some modems (e.g. BG96) might contains root CA for google.com --- TESTS/netsocket/tls/main.cpp | 8 ++++---- TESTS/netsocket/tls/tlssocket_endpoint_close.cpp | 5 ++++- TESTS/netsocket/tls/tlssocket_handshake_invalid.cpp | 10 ++++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/TESTS/netsocket/tls/main.cpp b/TESTS/netsocket/tls/main.cpp index bd1fff47755..e17eb53bfcb 100644 --- a/TESTS/netsocket/tls/main.cpp +++ b/TESTS/netsocket/tls/main.cpp @@ -84,15 +84,15 @@ nsapi_error_t tlssocket_connect_to_srv(TLSSocket &sock, uint16_t port) printf("MBED: Server '%s', port %d\n", tls_addr.get_ip_address(), tls_addr.get_port()); - nsapi_error_t err = sock.set_root_ca_cert(tls_global::cert); + nsapi_error_t err = sock.open(NetworkInterface::get_default_instance()); if (err != NSAPI_ERROR_OK) { - printf("Error from sock.set_root_ca_cert: %d\n", err); + printf("Error from sock.open: %d\n", err); return err; } - err = sock.open(NetworkInterface::get_default_instance()); + err = sock.set_root_ca_cert(tls_global::cert); if (err != NSAPI_ERROR_OK) { - printf("Error from sock.open: %d\n", err); + printf("Error from sock.set_root_ca_cert: %d\n", err); return err; } diff --git a/TESTS/netsocket/tls/tlssocket_endpoint_close.cpp b/TESTS/netsocket/tls/tlssocket_endpoint_close.cpp index 186f03d5cf7..93abc1368db 100644 --- a/TESTS/netsocket/tls/tlssocket_endpoint_close.cpp +++ b/TESTS/netsocket/tls/tlssocket_endpoint_close.cpp @@ -48,6 +48,10 @@ static nsapi_error_t _tlssocket_connect_to_daytime_srv(TLSSocket &sock) return err; } + TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.set_root_ca_cert(tls_global::cert)); + + sock.set_timeout(10000); // Set timeout for case TLSSocket does not support peer closed indication + return sock.connect(tls_addr); } @@ -62,7 +66,6 @@ void TLSSOCKET_ENDPOINT_CLOSE() tc_exec_time.start(); TLSSocket sock; - TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.set_root_ca_cert(tls_global::cert)); if (_tlssocket_connect_to_daytime_srv(sock) != NSAPI_ERROR_OK) { TEST_FAIL(); return; diff --git a/TESTS/netsocket/tls/tlssocket_handshake_invalid.cpp b/TESTS/netsocket/tls/tlssocket_handshake_invalid.cpp index 15f0fa1bbb6..0c3b00ed75c 100644 --- a/TESTS/netsocket/tls/tlssocket_handshake_invalid.cpp +++ b/TESTS/netsocket/tls/tlssocket_handshake_invalid.cpp @@ -28,12 +28,18 @@ using namespace utest::v1; void TLSSOCKET_HANDSHAKE_INVALID() { + const int https_port = 443; SKIP_IF_TCP_UNSUPPORTED(); TLSSocket sock; TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.open(NetworkInterface::get_default_instance())); TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.set_root_ca_cert(tls_global::cert)); - TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, - sock.connect("google.com", 443)); // 443 is https port. + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("expired.badssl.com", https_port)); + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("wrong.host.badssl.com", https_port)); + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("self-signed.badssl.com", https_port)); + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("untrusted-root.badssl.com", https_port)); + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("revoked.badssl.com", https_port)); + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("pinning-test.badssl.com", https_port)); + TEST_ASSERT_EQUAL(NSAPI_ERROR_AUTH_FAILURE, sock.connect("sha1-intermediate.badssl.com", https_port)); TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.close()); }