diff --git a/Makefile b/Makefile index 9746d31..5b5c43a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,15 @@ PROJECT_NAME := nina-fw +# Passthrough Board Port +M4_PORT := /dev/cu.usbmodem14121301 +UPLOAD_BAUD = 115200 +# ESP32 USB Serial Port +ESP_PORT := /dev/cu.usbserial-AH03B302 + +# Directories and Files +BOOT_VOLUME := /Volumes/FEATHERBOOT/. +CIRCUITPYTHON_UF2 := circuitpython.uf2 + EXTRA_COMPONENT_DIRS := $(PWD)/arduino ifeq ($(RELEASE),1) @@ -14,7 +24,27 @@ endif include $(IDF_PATH)/make/project.mk +load-passthrough: + cp passthrough.UF2 $(BOOT_VOLUME) + +load-nina: + esptool.py --port $(M4_PORT) --before no_reset --baud $(UPLOAD_BAUD) write_flash 0 NINA_W102-1.3.1.bin + +load-circuitpython: + cp $(CIRCUITPYTHON_UF2) $(BOOT_VOLUME) + +serial: + miniterm.py $(ESP_PORT) $(UPLOAD_BAUD) + firmware: all python combine.py .PHONY: firmware + +.PHONY: load-passthrough + +.PHONY: load-nina + +.PHONY: load-circuitpython + +.PHONY: serial \ No newline at end of file diff --git a/arduino/libraries/WiFi/src/WiFiSSLClient.cpp b/arduino/libraries/WiFi/src/WiFiSSLClient.cpp index 6d65633..ca1f87a 100644 --- a/arduino/libraries/WiFi/src/WiFiSSLClient.cpp +++ b/arduino/libraries/WiFi/src/WiFiSSLClient.cpp @@ -17,6 +17,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "Arduino.h" +#include #include #include #include "esp_partition.h" @@ -52,128 +54,203 @@ WiFiSSLClient::WiFiSSLClient() : int WiFiSSLClient::connect(const char* host, uint16_t port) { + ets_printf("** Connect(host/port) Called\n"); + return connect(host, port, _cert, _private_key); +} + +int WiFiSSLClient::connect(const char* host, uint16_t port, const char* client_cert, const char* client_key) +{ + ets_printf("** Connect(host/port/cert/key) called\n"); + int ret, flags; synchronized { _netContext.fd = -1; _connected = false; + ets_printf("Free internal heap before TLS %u", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + ets_printf("*** connect init\n"); + // SSL Client Initialization mbedtls_ssl_init(&_sslContext); mbedtls_ctr_drbg_init(&_ctrDrbgContext); mbedtls_ssl_config_init(&_sslConfig); - mbedtls_entropy_init(&_entropyContext); - mbedtls_x509_crt_init(&_caCrt); + mbedtls_net_init(&_netContext); ets_printf("*** connect inited\n"); ets_printf("*** connect drbgseed\n"); - - if (mbedtls_ctr_drbg_seed(&_ctrDrbgContext, mbedtls_entropy_func, &_entropyContext, NULL, 0) != 0) { + mbedtls_entropy_init(&_entropyContext); + // Seeds and sets up CTR_DRBG for future reseeds, pers is device personalization (esp) + ret = mbedtls_ctr_drbg_seed(&_ctrDrbgContext, mbedtls_entropy_func, + &_entropyContext, (const unsigned char *) pers, strlen(pers)); + if (ret < 0) { + ets_printf("Unable to set up mbedtls_entropy.\n"); stop(); return 0; } ets_printf("*** connect ssl hostname\n"); /* Hostname set here should match CN in server certificate */ - if(mbedtls_ssl_set_hostname(&_sslContext, host) != 0) - { + if(mbedtls_ssl_set_hostname(&_sslContext, host) != 0) { stop(); return 0; } ets_printf("*** connect ssl config\n"); - - if (mbedtls_ssl_config_defaults(&_sslConfig, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) { + ret= mbedtls_ssl_config_defaults(&_sslConfig, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { stop(); + ets_printf("Error Setting up SSL Config: %d\n", ret); return 0; - } + } ets_printf("*** connect authmode\n"); - + // we're always using the root CA cert from partition, so MBEDTLS_SSL_VERIFY_REQUIRED + ets_printf("*** Loading CA Cert...\n"); + mbedtls_x509_crt_init(&_caCrt); mbedtls_ssl_conf_authmode(&_sslConfig, MBEDTLS_SSL_VERIFY_REQUIRED); + // setting up CA certificates from partition spi_flash_mmap_handle_t handle; const unsigned char* certs_data = {}; - ets_printf("*** connect part findfirst\n"); - const esp_partition_t* part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "certs"); - if (part == NULL) - { + if (part == NULL) { + stop(); return 0; } ets_printf("*** connect part mmap\n"); - int ret = esp_partition_mmap(part, 0, part->size, SPI_FLASH_MMAP_DATA, (const void**)&certs_data, &handle); - if (ret != ESP_OK) - { - return 0; - } - - ets_printf("*** connect crt parse\n"); - - ret = mbedtls_x509_crt_parse(&_caCrt, certs_data, strlen((char*)certs_data) + 1); - if (ret < 0) { + if (ret != ESP_OK) { + ets_printf("*** Error partition mmap %d\n", ret); stop(); return 0; } + ets_printf("Length of certs_data: %d", strlen((char*)certs_data)+1); + ets_printf("*** connect crt parse\n"); + ret = mbedtls_x509_crt_parse(&_caCrt, certs_data, strlen((char*)certs_data) + 1); ets_printf("*** connect conf ca chain\n"); - mbedtls_ssl_conf_ca_chain(&_sslConfig, &_caCrt, NULL); + if (ret < 0) { + ets_printf("*** Error parsing CA chain.\n"); + stop(); + return 0; + } + ets_printf("*** check for client_cert and client_key\n"); + if (client_cert != NULL && client_key != NULL) { + mbedtls_x509_crt_init(&_clientCrt); + mbedtls_pk_init(&_clientKey); + + ets_printf("*** Loading client certificate.\n"); + // note: +1 added for line ending + ret = mbedtls_x509_crt_parse(&_clientCrt, (const unsigned char *)client_cert, strlen(client_cert) + 1); + if (ret != 0) { + ets_printf("ERROR: Client cert not parsed properly(%d)\n", ret); + stop(); + return 0; + } + + ets_printf("*** Loading private key.\n"); + ret = mbedtls_pk_parse_key(&_clientKey, (const unsigned char *)client_key, strlen(client_key)+1, + NULL, 0); + if (ret != 0) { + ets_printf("ERROR: Private key not parsed properly:(%d)\n", ret); + stop(); + return 0; + } + // set own certificate chain and key + ret = mbedtls_ssl_conf_own_cert(&_sslConfig, &_clientCrt, &_clientKey); + if (ret != 0) { + if (ret == -0x7f00) { + ets_printf("ERROR: Memory allocation failed, MBEDTLS_ERR_SSL_ALLOC_FAILED"); + } + ets_printf("Private key not parsed properly(%d)\n", ret); + stop(); + return 0; + } + } + else { + ets_printf("Client certificate and key not provided.\n"); + } ets_printf("*** connect conf RNG\n"); - mbedtls_ssl_conf_rng(&_sslConfig, mbedtls_ctr_drbg_random, &_ctrDrbgContext); ets_printf("*** connect ssl setup\n"); - - if (mbedtls_ssl_setup(&_sslContext, &_sslConfig) != 0) { + if ((ret = mbedtls_ssl_setup(&_sslContext, &_sslConfig)) != 0) { + if (ret == -0x7f00){ + ets_printf("%s", &_clientCrt); + ets_printf("Memory allocation failed (MBEDTLS_ERR_SSL_ALLOC_FAILED)\n"); + ets_printf("Free internal heap: %u\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + } + ets_printf("Unable to connect ssl setup %d\n", ret); stop(); return 0; } char portStr[6]; itoa(port, portStr, 10); - ets_printf("*** connect netconnect\n"); - if (mbedtls_net_connect(&_netContext, host, portStr, MBEDTLS_NET_PROTO_TCP) != 0) { stop(); return 0; } ets_printf("*** connect set bio\n"); - mbedtls_ssl_set_bio(&_sslContext, &_netContext, mbedtls_net_send, mbedtls_net_recv, NULL); - int result = -1; - - do { - ets_printf("*** connect ssl handshake\n"); - result = mbedtls_ssl_handshake(&_sslContext); - } while (result == MBEDTLS_ERR_SSL_WANT_READ || result == MBEDTLS_ERR_SSL_WANT_WRITE); - - if (result != 0) { - uint8_t module_id = (result >> 12) & 0x7; - uint8_t module_dep = (result >> 7) & 0x1F; - uint8_t lowlevel = result & 0x7F; - ets_printf("*** ssl fail! result %x\t module id: %x module dependant: %x lowlevel: %x\n", result, module_id, module_dep, lowlevel); - - char str[100]; - mbedtls_strerror(result, str, 100); - ets_printf("strerror: %s\n", str); - - stop(); - return 0; + ets_printf("*** start SSL/TLS handshake...\n"); + unsigned long start_handshake = millis(); + // ref: https://tls.mbed.org/api/ssl_8h.html#a4a37e497cd08c896870a42b1b618186e + while ((ret = mbedtls_ssl_handshake(&_sslContext)) !=0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ets_printf("Error performing SSL handshake"); + } + if((millis() - start_handshake) > _handshake_timeout){ + ets_printf("SSL Handshake Timeout\n"); + stop(); + return -1; + } + vTaskDelay(10 / portTICK_PERIOD_MS); } - + + if (client_cert != NULL && client_key != NULL) + { + ets_printf("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&_sslContext), mbedtls_ssl_get_ciphersuite(&_sslContext)); + } + ets_printf("Verifying peer X.509 certificate\n"); + char buf[512]; + if ((flags = mbedtls_ssl_get_verify_result(&_sslContext)) != 0) { + bzero(buf, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + ets_printf("Failed to verify peer certificate! verification info: %s\n", buf); + stop(); // invalid certificate, stop + return -1; + } else { + ets_printf("Certificate chain verified.\n"); + } + ets_printf("*** ssl set nonblock\n"); mbedtls_net_set_nonblock(&_netContext); - _connected = true; + ets_printf("Free internal heap before cleanup: %u\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + // free the heap + if (certs_data != NULL) { + mbedtls_x509_crt_free(&_caCrt); + } + if (client_cert != NULL) { + mbedtls_x509_crt_free(&_clientCrt); + } + if (client_key !=NULL) { + mbedtls_pk_free(&_clientKey); + } + ets_printf("Free internal heap after cleanup: %u\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + _connected = true; return 1; } } @@ -264,6 +341,26 @@ int WiFiSSLClient::peek() return _peek; } +void WiFiSSLClient::setCertificate(const char *client_ca) +{ + ets_printf("\n*** Setting client certificate...\n"); + _cert = client_ca; + ets_printf("%s", _cert); + ets_printf("\n*** Set client certificate\n"); +} + +void WiFiSSLClient::setPrivateKey(const char *private_key) +{ + ets_printf("Setting client private key...\n"); + _private_key = private_key; + ets_printf("Set client private key\n"); +} + +void WiFiSSLClient::setHandshakeTimeout(unsigned long timeout) +{ + _handshake_timeout = timeout * 1000; +} + void WiFiSSLClient::flush() { } @@ -272,10 +369,12 @@ void WiFiSSLClient::stop() { synchronized { if (_netContext.fd > 0) { - mbedtls_ssl_session_reset(&_sslContext); + mbedtls_ssl_session_reset(&_sslContext); mbedtls_net_free(&_netContext); mbedtls_x509_crt_free(&_caCrt); + mbedtls_x509_crt_free(&_clientCrt); + mbedtls_pk_free(&_clientKey); mbedtls_entropy_free(&_entropyContext); mbedtls_ssl_config_free(&_sslConfig); mbedtls_ctr_drbg_free(&_ctrDrbgContext); diff --git a/arduino/libraries/WiFi/src/WiFiSSLClient.h b/arduino/libraries/WiFi/src/WiFiSSLClient.h index 965245b..b0d2954 100644 --- a/arduino/libraries/WiFi/src/WiFiSSLClient.h +++ b/arduino/libraries/WiFi/src/WiFiSSLClient.h @@ -22,13 +22,14 @@ #include #include +#include #include #include #include +#include #include -// #include -// #include + class WiFiSSLClient /*: public Client*/ { @@ -39,6 +40,7 @@ public: virtual int connect(/*IPAddress*/uint32_t ip, uint16_t port); virtual int connect(const char* host, uint16_t port); + virtual int connect(const char* host, uint16_t port, const char* client_cert, const char* client_key); virtual size_t write(uint8_t); virtual size_t write(const uint8_t *buf, size_t size); virtual int available(); @@ -49,14 +51,20 @@ public: virtual void stop(); virtual uint8_t connected(); virtual operator bool(); + virtual void setCertificate(const char *client_ca); + virtual void setPrivateKey (const char *private_key); + virtual void setHandshakeTimeout(unsigned long timeout); // using Print::write; virtual /*IPAddress*/uint32_t remoteIP(); virtual uint16_t remotePort(); + const char *pers = "esp32-tls"; private: static const char* ROOT_CAs; + const char *_cert; // user-provided certificate + const char *_private_key; // user-provided private mbedtls_entropy_context _entropyContext; mbedtls_ctr_drbg_context _ctrDrbgContext; @@ -64,8 +72,11 @@ private: mbedtls_ssl_config _sslConfig; mbedtls_net_context _netContext; mbedtls_x509_crt _caCrt; + mbedtls_x509_crt _clientCrt; + mbedtls_pk_context _clientKey; bool _connected; int _peek; + unsigned long _handshake_timeout = 120000; SemaphoreHandle_t _mbedMutex; }; diff --git a/combine.py b/combine.py index 3d18300..5ce43e9 100644 --- a/combine.py +++ b/combine.py @@ -31,7 +31,7 @@ for i in range(0, len(certsData)): # zero terminate the pem file outputData[0x10000 + len(certsData)] = 0 -outputFilename = "NINA_W102-1.3.1.bin" +outputFilename = "NINA_W102-1.4.0.bin" if (len(sys.argv) > 1): outputFilename = sys.argv[1] diff --git a/main/CommandHandler.cpp b/main/CommandHandler.cpp index 8cc0a96..9190e28 100644 --- a/main/CommandHandler.cpp +++ b/main/CommandHandler.cpp @@ -26,7 +26,17 @@ #include "CommandHandler.h" -const char FIRMWARE_VERSION[6] = "1.3.1"; +#include "Arduino.h" + +const char FIRMWARE_VERSION[6] = "1.4.0"; + +// Optional, user-defined X.509 certificate +char CERT_BUF[1300]; +bool setCert = 0; + +// Optional, user-defined RSA private key +char PK_BUFF[1700]; +bool setPSK = 0; /*IPAddress*/uint32_t resolvedHostname; @@ -589,10 +599,17 @@ int startClientTcp(const uint8_t command[], uint8_t response[]) } } else if (type == 0x02) { int result; - if (host[0] != '\0') { + if (setCert && setPSK) { + tlsClients[socket].setCertificate(CERT_BUF); + tlsClients[socket].setPrivateKey(PK_BUFF); + } result = tlsClients[socket].connect(host, port); } else { + if (setCert && setPSK) { + tlsClients[socket].setCertificate(CERT_BUF); + tlsClients[socket].setPrivateKey(PK_BUFF); + } result = tlsClients[socket].connect(ip, port); } @@ -1046,6 +1063,36 @@ int wpa2EntEnable(const uint8_t command[], uint8_t response[]) { return 6; } +int setClientCert(const uint8_t command[], uint8_t response[]){ + ets_printf("*** Called setClientCert\n"); + + memset(CERT_BUF, 0x00, sizeof(CERT_BUF)); + memcpy(CERT_BUF, &command[4], sizeof(CERT_BUF)); + + response[2] = 1; // number of parameters + response[3] = 1; // parameter 1 length + response[4] = 1; + + setCert = 1; + + return 6; +} + +int setCertKey(const uint8_t command[], uint8_t response[]){ + ets_printf("*** Called setCertKey\n"); + + memset(PK_BUFF, 0x00, sizeof(PK_BUFF)); + memcpy(PK_BUFF, &command[4], sizeof(PK_BUFF)); + + response[2] = 1; // number of parameters + response[3] = 1; // parameter 1 length + response[4] = 1; + + setPSK = 1; + + return 6; +} + typedef int (*CommandHandlerType)(const uint8_t command[], uint8_t response[]); const CommandHandlerType commandHandlers[] = { @@ -1062,7 +1109,7 @@ const CommandHandlerType commandHandlers[] = { disconnect, NULL, getIdxRSSI, getIdxEnct, reqHostByName, getHostByName, startScanNetworks, getFwVersion, NULL, sendUDPdata, getRemoteData, getTime, getIdxBSSID, getIdxChannel, ping, getSocket, // 0x40 -> 0x4f - NULL, NULL, NULL, NULL, sendDataTcp, getDataBufTcp, insertDataBuf, NULL, NULL, NULL, wpa2EntSetIdentity, wpa2EntSetUsername, wpa2EntSetPassword, wpa2EntSetCACert, wpa2EntSetCertKey, wpa2EntEnable, + setClientCert, setCertKey, NULL, NULL, sendDataTcp, getDataBufTcp, insertDataBuf, NULL, NULL, NULL, wpa2EntSetIdentity, wpa2EntSetUsername, wpa2EntSetPassword, wpa2EntSetCACert, wpa2EntSetCertKey, wpa2EntEnable, // 0x50 -> 0x5f setPinMode, setDigitalWrite, setAnalogWrite,