diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index 2cd7a4471..d8411c619 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -204,6 +204,9 @@ static void mbedtls_cleanup(esp_tls_t *tls) mbedtls_x509_crt_free(tls->cacert_ptr); } tls->cacert_ptr = NULL; + mbedtls_x509_crt_free(&tls->cacert); + mbedtls_x509_crt_free(&tls->clientcert); + mbedtls_pk_free(&tls->clientkey); mbedtls_entropy_free(&tls->entropy); mbedtls_ssl_config_free(&tls->conf); mbedtls_ctr_drbg_free(&tls->ctr_drbg); @@ -274,7 +277,34 @@ static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostle } else { mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE); } - + + if (cfg->clientcert_pem_buf != NULL && cfg->clientkey_pem_buf != NULL) { + mbedtls_x509_crt_init(&tls->clientcert); + mbedtls_pk_init(&tls->clientkey); + + ret = mbedtls_x509_crt_parse(&tls->clientcert, cfg->clientcert_pem_buf, cfg->clientcert_pem_bytes); + if (ret < 0) { + ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); + goto exit; + } + + ret = mbedtls_pk_parse_key(&tls->clientkey, cfg->clientkey_pem_buf, cfg->clientkey_pem_bytes, + cfg->clientkey_password, cfg->clientkey_password_len); + if (ret < 0) { + ESP_LOGE(TAG, "mbedtls_pk_parse_keyfile returned -0x%x\n\n", -ret); + goto exit; + } + + ret = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->clientcert, &tls->clientkey); + if (ret < 0) { + ESP_LOGE(TAG, "mbedtls_ssl_conf_own_cert returned -0x%x\n\n", -ret); + goto exit; + } + } else if (cfg->clientcert_pem_buf != NULL || cfg->clientkey_pem_buf != NULL) { + ESP_LOGE(TAG, "You have to provide both clientcert_pem_buf and clientkey_pem_buf for mutual authentication\n\n"); + goto exit; + } + mbedtls_ssl_conf_rng(&tls->conf, mbedtls_ctr_drbg_random, &tls->ctr_drbg); #ifdef CONFIG_MBEDTLS_DEBUG @@ -502,4 +532,4 @@ int esp_tls_conn_http_new_async(const char *url, const esp_tls_cfg_t *cfg, esp_t /* Connect to host */ return esp_tls_conn_new_async(&url[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len, get_port(url, &u), cfg, tls); -} \ No newline at end of file +} diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h index eaa035312..ed7165e50 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -60,7 +60,22 @@ typedef struct esp_tls_cfg { unsigned int cacert_pem_bytes; /*!< Size of Certificate Authority certificate pointed to by cacert_pem_buf */ + + const unsigned char *clientcert_pem_buf;/*!< Client certificate in a buffer */ + unsigned int clientcert_pem_bytes; /*!< Size of client certificate pointed to by + clientcert_pem_buf */ + + const unsigned char *clientkey_pem_buf; /*!< Client key in a buffer */ + + unsigned int clientkey_pem_bytes; /*!< Size of client key pointed to by + clientkey_pem_buf */ + + const unsigned char *clientkey_password;/*!< Client key decryption password string */ + + unsigned int clientkey_password_len; /*!< String length of the password pointed to by + clientkey_password */ + bool non_block; /*!< Configure non-blocking mode. If set to true the underneath socket will be configured in non blocking mode after tls session is established */ @@ -89,10 +104,15 @@ typedef struct esp_tls { mbedtls_net_context server_fd; /*!< mbedTLS wrapper type for sockets */ - mbedtls_x509_crt cacert; /*!< Container for an X.509 certificate */ - + mbedtls_x509_crt cacert; /*!< Container for the X.509 CA certificate */ + mbedtls_x509_crt *cacert_ptr; /*!< Pointer to the cacert being used. */ + mbedtls_x509_crt clientcert; /*!< Container for the X.509 client certificate */ + + mbedtls_pk_context clientkey; /*!< Container for the private key of the client + certificate */ + int sockfd; /*!< Underlying socket file descriptor. */ ssize_t (*read)(struct esp_tls *tls, char *data, size_t datalen); /*!< Callback function for reading data from TLS/SSL diff --git a/components/mqtt/esp-mqtt b/components/mqtt/esp-mqtt index 85ee406d0..a7b1cea5b 160000 --- a/components/mqtt/esp-mqtt +++ b/components/mqtt/esp-mqtt @@ -1 +1 @@ -Subproject commit 85ee406d03fd84f5613c6dead1ea653e384b9559 +Subproject commit a7b1cea5b3e246298607a8c64447765297626f36 diff --git a/components/tcp_transport/include/esp_transport_ssl.h b/components/tcp_transport/include/esp_transport_ssl.h index 2065db6b0..34045ae77 100644 --- a/components/tcp_transport/include/esp_transport_ssl.h +++ b/components/tcp_transport/include/esp_transport_ssl.h @@ -32,7 +32,7 @@ esp_transport_handle_t esp_transport_ssl_init(); /** * @brief Set SSL certificate data (as PEM format). * Note that, this function stores the pointer to data, rather than making a copy. - * So we need to make sure to keep the data lifetime before cleanup the connection + * So this data must remain valid until after the connection is cleaned up * * @param t ssl transport * @param[in] data The pem data @@ -40,6 +40,27 @@ esp_transport_handle_t esp_transport_ssl_init(); */ void esp_transport_ssl_set_cert_data(esp_transport_handle_t t, const char *data, int len); +/** + * @brief Set SSL client certificate data for mutual authentication (as PEM format). + * Note that, this function stores the pointer to data, rather than making a copy. + * So this data must remain valid until after the connection is cleaned up + * + * @param t ssl transport + * @param[in] data The pem data + * @param[in] len The length + */ +void esp_transport_ssl_set_client_cert_data(esp_transport_handle_t t, const char *data, int len); + +/** + * @brief Set SSL client key data for mutual authentication (as PEM format). + * Note that, this function stores the pointer to data, rather than making a copy. + * So this data must remain valid until after the connection is cleaned up + * + * @param t ssl transport + * @param[in] data The pem data + * @param[in] len The length + */ +void esp_transport_ssl_set_client_key_data(esp_transport_handle_t t, const char *data, int len); #ifdef __cplusplus } diff --git a/components/tcp_transport/transport_ssl.c b/components/tcp_transport/transport_ssl.c index 08afe3c19..f2d346720 100644 --- a/components/tcp_transport/transport_ssl.c +++ b/components/tcp_transport/transport_ssl.c @@ -39,7 +39,6 @@ typedef struct { esp_tls_t *tls; esp_tls_cfg_t cfg; bool ssl_initialized; - bool verify_server; transport_ssl_conn_state_t conn_state; } transport_ssl_t; @@ -49,9 +48,6 @@ static int ssl_connect_async(esp_transport_handle_t t, const char *host, int por { transport_ssl_t *ssl = esp_transport_get_context_data(t); if (ssl->conn_state == TRANS_SSL_INIT) { - if (ssl->cfg.cacert_pem_buf) { - ssl->verify_server = true; - } ssl->cfg.timeout_ms = timeout_ms; ssl->cfg.non_block = true; ssl->ssl_initialized = true; @@ -70,9 +66,7 @@ static int ssl_connect_async(esp_transport_handle_t t, const char *host, int por static int ssl_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms) { transport_ssl_t *ssl = esp_transport_get_context_data(t); - if (ssl->cfg.cacert_pem_buf) { - ssl->verify_server = true; - } + ssl->cfg.timeout_ms = timeout_ms; ssl->ssl_initialized = true; ssl->tls = esp_tls_conn_new(host, strlen(host), port, &ssl->cfg); @@ -146,7 +140,6 @@ static int ssl_close(esp_transport_handle_t t) if (ssl->ssl_initialized) { esp_tls_conn_delete(ssl->tls); ssl->ssl_initialized = false; - ssl->verify_server = false; } return ret; } @@ -168,6 +161,24 @@ void esp_transport_ssl_set_cert_data(esp_transport_handle_t t, const char *data, } } +void esp_transport_ssl_set_client_cert_data(esp_transport_handle_t t, const char *data, int len) +{ + transport_ssl_t *ssl = esp_transport_get_context_data(t); + if (t && ssl) { + ssl->cfg.clientcert_pem_buf = (void *)data; + ssl->cfg.clientcert_pem_bytes = len + 1; + } +} + +void esp_transport_ssl_set_client_key_data(esp_transport_handle_t t, const char *data, int len) +{ + transport_ssl_t *ssl = esp_transport_get_context_data(t); + if (t && ssl) { + ssl->cfg.clientkey_pem_buf = (void *)data; + ssl->cfg.clientkey_pem_bytes = len + 1; + } +} + esp_transport_handle_t esp_transport_ssl_init() { esp_transport_handle_t t = esp_transport_init(); diff --git a/examples/protocols/mqtt/ssl_mutual_auth/CMakeLists.txt b/examples/protocols/mqtt/ssl_mutual_auth/CMakeLists.txt new file mode 100644 index 000000000..84bf37525 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(mqtt_ssl_mutual_auth) + +target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/client.crt" TEXT) +target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/client.key" TEXT) diff --git a/examples/protocols/mqtt/ssl_mutual_auth/Makefile b/examples/protocols/mqtt/ssl_mutual_auth/Makefile new file mode 100644 index 000000000..cfc04f81b --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := mqtt_ssl_mutual_auth + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/mqtt/ssl_mutual_auth/README.md b/examples/protocols/mqtt/ssl_mutual_auth/README.md new file mode 100644 index 000000000..7ffe53704 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/README.md @@ -0,0 +1,81 @@ +# ESP-MQTT SSL Sample application (mutual authentication) + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example connects to the broker test.mosquitto.org using ssl transport with client certificate and as a demonstration subscribes/unsubscribes and send a message on certain topic. + +It uses ESP-MQTT library which implements mqtt client to connect to mqtt broker. + +## How to use example + +### Hardware Required + +This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet. + +### Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +* Set ssid and password for the board to connect to AP. + +* Generate your client keys and certificate + +Navigate to the main directory + +``` +cd main +``` + +Generate a client key and a CSR. When you are generating the CSR, do not use the default values. At a minimum, the CSR must include the Country, Organisation and Common Name fields. + +``` +openssl genrsa -out client.key +openssl req -out client.csr -key client.key -new +``` + +Paste the generated CSR in the [Mosquitto test certificate signer](https://test.mosquitto.org/ssl/index.php), click Submit and copy the downloaded `client.crt` in the `main` directory. + +Please note, that the supplied files `client.crt` and `client.key` in the `main` directory are only placeholders for your client certificate and key (i.e. the example "as is" would compile but would not connect to the broker) + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (3714) event: sta ip: 192.168.0.139, mask: 255.255.255.0, gw: 192.168.0.2 +I (3714) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (3964) MQTT_CLIENT: Sending MQTT CONNECT message, type: 1, id: 0000 +I (4164) MQTTS_EXAMPLE: MQTT_EVENT_CONNECTED +I (4174) MQTTS_EXAMPLE: sent publish successful, msg_id=41464 +I (4174) MQTTS_EXAMPLE: sent subscribe successful, msg_id=17886 +I (4174) MQTTS_EXAMPLE: sent subscribe successful, msg_id=42970 +I (4184) MQTTS_EXAMPLE: sent unsubscribe successful, msg_id=50241 +I (4314) MQTTS_EXAMPLE: MQTT_EVENT_PUBLISHED, msg_id=41464 +I (4484) MQTTS_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=17886 +I (4484) MQTTS_EXAMPLE: sent publish successful, msg_id=0 +I (4684) MQTTS_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=42970 +I (4684) MQTTS_EXAMPLE: sent publish successful, msg_id=0 +I (4884) MQTT_CLIENT: deliver_publish, message_length_read=19, message_length=19 +I (4884) MQTTS_EXAMPLE: MQTT_EVENT_DATA +TOPIC=/topic/qos0 +DATA=data +I (5194) MQTT_CLIENT: deliver_publish, message_length_read=19, message_length=19 +I (5194) MQTTS_EXAMPLE: MQTT_EVENT_DATA +TOPIC=/topic/qos0 +DATA=data +``` + diff --git a/examples/protocols/mqtt/ssl_mutual_auth/main/CMakeLists.txt b/examples/protocols/mqtt/ssl_mutual_auth/main/CMakeLists.txt new file mode 100644 index 000000000..6b0350063 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "app_main.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/mqtt/ssl_mutual_auth/main/Kconfig.projbuild b/examples/protocols/mqtt/ssl_mutual_auth/main/Kconfig.projbuild new file mode 100644 index 000000000..176d8fb33 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +endmenu diff --git a/examples/protocols/mqtt/ssl_mutual_auth/main/app_main.c b/examples/protocols/mqtt/ssl_mutual_auth/main/app_main.c new file mode 100644 index 000000000..2ebce48b9 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/main/app_main.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include "esp_wifi.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_event_loop.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" + +#include "lwip/sockets.h" +#include "lwip/dns.h" +#include "lwip/netdb.h" + +#include "esp_log.h" +#include "mqtt_client.h" + +static const char *TAG = "MQTTS_EXAMPLE"; + +static EventGroupHandle_t wifi_event_group; +const static int CONNECTED_BIT = BIT0; + + + +static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_init(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_event_loop_init(wifi_event_handler, NULL)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_WIFI_SSID, + .password = CONFIG_WIFI_PASSWORD, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); + ESP_LOGI(TAG, "start the WIFI SSID:[%s]", CONFIG_WIFI_SSID); + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_LOGI(TAG, "Waiting for wifi"); + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); +} + +extern const uint8_t client_cert_pem_start[] asm("_binary_client_crt_start"); +extern const uint8_t client_cert_pem_end[] asm("_binary_client_crt_end"); +extern const uint8_t client_key_pem_start[] asm("_binary_client_key_start"); +extern const uint8_t client_key_pem_end[] asm("_binary_client_key_end"); + +static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event) +{ + esp_mqtt_client_handle_t client = event->client; + int msg_id; + // your_context_t *context = event->context; + switch (event->event_id) { + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1"); + ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0); + ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA"); + printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); + printf("DATA=%.*s\r\n", event->data_len, event->data); + break; + case MQTT_EVENT_ERROR: + ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); + break; + } + return ESP_OK; +} + +static void mqtt_app_start(void) +{ + const esp_mqtt_client_config_t mqtt_cfg = { + .uri = "mqtts://test.mosquitto.org:8884", + .event_handle = mqtt_event_handler, + .client_cert_pem = (const char *)client_cert_pem_start, + .client_key_pem = (const char *)client_key_pem_start, + }; + + ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); + esp_mqtt_client_start(client); +} + +void app_main() +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + esp_log_level_set("*", ESP_LOG_INFO); + esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE); + esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE); + esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE); + + nvs_flash_init(); + wifi_init(); + mqtt_app_start(); + +} diff --git a/examples/protocols/mqtt/ssl_mutual_auth/main/client.crt b/examples/protocols/mqtt/ssl_mutual_auth/main/client.crt new file mode 100644 index 000000000..7a3074b90 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/main/client.crt @@ -0,0 +1 @@ +Please paste your client certificate here (follow instructions in README.md) diff --git a/examples/protocols/mqtt/ssl_mutual_auth/main/client.key b/examples/protocols/mqtt/ssl_mutual_auth/main/client.key new file mode 100644 index 000000000..a956f850c --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/main/client.key @@ -0,0 +1 @@ +Please paste here your client key (follow instructions in README.md) diff --git a/examples/protocols/mqtt/ssl_mutual_auth/main/component.mk b/examples/protocols/mqtt/ssl_mutual_auth/main/component.mk new file mode 100644 index 000000000..01adda5c1 --- /dev/null +++ b/examples/protocols/mqtt/ssl_mutual_auth/main/component.mk @@ -0,0 +1 @@ +COMPONENT_EMBED_TXTFILES := client.crt client.key