Merge branch 'feature/mqtt_mutual_auth' into 'master'

MQTT: Added client cert ssl example per PR from GitHub

See merge request idf/esp-idf!3473
This commit is contained in:
Angus Gratton 2018-10-31 07:06:58 +08:00
commit e4369aaed4
14 changed files with 368 additions and 14 deletions

View file

@ -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);
}
}

View file

@ -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

@ -1 +1 @@
Subproject commit 85ee406d03fd84f5613c6dead1ea653e384b9559
Subproject commit a7b1cea5b3e246298607a8c64447765297626f36

View file

@ -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
}

View file

@ -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();

View file

@ -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)

View file

@ -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

View file

@ -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
```

View file

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "app_main.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View file

@ -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

View file

@ -0,0 +1,152 @@
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#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();
}

View file

@ -0,0 +1 @@
Please paste your client certificate here (follow instructions in README.md)

View file

@ -0,0 +1 @@
Please paste here your client key (follow instructions in README.md)

View file

@ -0,0 +1 @@
COMPONENT_EMBED_TXTFILES := client.crt client.key