From 7041c014bb5dc2f697cf209149f5cb7f245185f3 Mon Sep 17 00:00:00 2001 From: rongsaws Date: Mon, 29 Aug 2016 10:55:32 -0700 Subject: [PATCH] Added a MQTT pub/sub example of using AWS IoT (#173) * Added a MQTT pub/sub example of using AWS IoT (via ECC based TLS1.2 connection). * Fixed a buffer overflow issue when receiving large MQTT packet. * Reset TLS connection on read/write errors. --- examples/aws_iot/Makefile | 3 + examples/aws_iot/README.md | 60 +++++++ examples/aws_iot/aws_iot.c | 280 ++++++++++++++++++++++++++++++ examples/aws_iot/ca_cert.c | 29 ++++ examples/aws_iot/client_config.c | 17 ++ examples/aws_iot/mbedtls/config.h | 119 +++++++++++++ examples/aws_iot/ssl_connection.c | 170 ++++++++++++++++++ examples/aws_iot/ssl_connection.h | 40 +++++ extras/paho_mqtt_c/MQTTClient.c | 9 +- 9 files changed, 725 insertions(+), 2 deletions(-) create mode 100644 examples/aws_iot/Makefile create mode 100644 examples/aws_iot/README.md create mode 100644 examples/aws_iot/aws_iot.c create mode 100644 examples/aws_iot/ca_cert.c create mode 100644 examples/aws_iot/client_config.c create mode 100644 examples/aws_iot/mbedtls/config.h create mode 100644 examples/aws_iot/ssl_connection.c create mode 100644 examples/aws_iot/ssl_connection.h diff --git a/examples/aws_iot/Makefile b/examples/aws_iot/Makefile new file mode 100644 index 0000000..677148a --- /dev/null +++ b/examples/aws_iot/Makefile @@ -0,0 +1,3 @@ +PROGRAM=aws_iot +EXTRA_COMPONENTS = extras/paho_mqtt_c extras/mbedtls +include ../../common.mk diff --git a/examples/aws_iot/README.md b/examples/aws_iot/README.md new file mode 100644 index 0000000..daebef0 --- /dev/null +++ b/examples/aws_iot/README.md @@ -0,0 +1,60 @@ +Please follow the steps below to build and run the example on your ESP8266. + +1. Modify client_config.c to provide your own account-specific AWS IoT + endpoint, ECC-based client certificate, and private key. + + Your endpoint is in the form of ```.iot..amazonaws.com```. + It can be retrieved using the following command: + + ```sh + $ aws iot describe-endpoint + ``` + + Your ECC-based certificate and private key can be generated by using + the following commands: + + ```sh + $ openssl ecparam -out ecckey.key -name prime256v1 -genkey + $ openssl req -new -sha256 -key ecckey.key -nodes -out eccCsr.csr + $ aws iot create-certificate-from-csr --certificate-signing-request file://eccCsr.csr --certificate-pem-outfile eccCert.crt --set-as-active + ``` + + To convert the certificate or key file into C string, you could try + the following example: + + ```sh + $ cat ecckey.key | sed -e 's/^/"/g' | sed -e 's/$/\\r\\n"/g' + ``` + + *Note, more information about using ECC-based certificate with AWS IoT + can be found in the following blog* + + https://aws.amazon.com/blogs/iot/elliptic-curve-cryptography-and-forward-secrecy-support-in-aws-iot-3/ + +2. Create and attach AWS IoT access policy to the certificate + + ```sh + $ aws iot create-policy --policy-name test-thing-policy --policy-document '{ "Version": "2012-10-17", "Statement": [{"Action": ["iot:*"], "Resource": ["*"], "Effect": "Allow" }] }' + $ aws iot attach-principal-policy --policy-name test-thing-policy --principal "arn:aws:iot:eu-west-1:892804553548:cert/2d9c2da32a95b5e95a277c3b8f7af40869727f5259dc2e907fc8aba916c857e" + ``` + + *Note, the 'principal' argument is the certificate ARN generated from the + pervious command 'aws iot create-certificate-from-csr'.* + +3. Modify include/ssid_config.h with your Wifi access Id and credential. + +4. Build and flash the example firmware to the device using the command + below: + + ```sh + $ make flash -C examples/aws_iot ESPPORT=/dev/ttyUSB0 + ``` + + *Note, it assumes your ESP8266 is connected through USB and exposed under + your Linux host as /dev/ttyUSB0.* + +5. Once the ESP8266 is connected to AWS IoT, you can use the MQTT client + on the AWS IoT console to receive the messages published by the ESP8266 + to topic 'esp8266/status'. You could also publish 'on' or 'off' message + to topic 'esp8266/control' to toggle the GPIO/LED (GPIO2 is used by the + example). diff --git a/examples/aws_iot/aws_iot.c b/examples/aws_iot/aws_iot.c new file mode 100644 index 0000000..01ce9a1 --- /dev/null +++ b/examples/aws_iot/aws_iot.c @@ -0,0 +1,280 @@ +/* + * Derived from examples/mqtt_client/mqtt_client.c - added TLS1.2 support and some minor modifications. + */ +#include "espressif/esp_common.h" +#include "esp/uart.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +// this must be ahead of any mbedtls header files so the local mbedtls/config.h can be properly referenced +#include "ssl_connection.h" + +#define MQTT_PUB_TOPIC "esp8266/status" +#define MQTT_SUB_TOPIC "esp8266/control" +#define GPIO_LED 2 +#define MQTT_PORT 8883 + +/* certs, key, and endpoint */ +extern char *ca_cert, *client_endpoint, *client_cert, *client_key; + +static int wifi_alive = 0; +static int ssl_reset; +static SSLConnection *ssl_conn; +static xQueueHandle publish_queue; + +static void beat_task(void *pvParameters) { + char msg[16]; + int count = 0; + + while (1) { + if (!wifi_alive) { + vTaskDelay(1000 / portTICK_RATE_MS); + continue; + } + + printf("Schedule to publish\r\n"); + + snprintf(msg, sizeof(msg), "%d", count); + if (xQueueSend(publish_queue, (void *) msg, 0) == pdFALSE) { + printf("Publish queue overflow\r\n"); + } + + vTaskDelay(10000 / portTICK_RATE_MS); + } +} + +static void topic_received(MessageData *md) { + MQTTMessage *message = md->message; + int i; + + printf("Received: "); + for (i = 0; i < md->topic->lenstring.len; ++i) + printf("%c", md->topic->lenstring.data[i]); + + printf(" = "); + for (i = 0; i < (int) message->payloadlen; ++i) + printf("%c", ((char *) (message->payload))[i]); + printf("\r\n"); + + if (!strncmp(message->payload, "on", 2)) { + printf("Turning on LED\r\n"); + gpio_write(GPIO_LED, 0); + } else if (!strncmp(message->payload, "off", 3)) { + printf("Turning off LED\r\n"); + gpio_write(GPIO_LED, 1); + } +} + +static const char *get_my_id(void) { + // Use MAC address for Station as unique ID + static char my_id[13]; + static bool my_id_done = false; + int8_t i; + uint8_t x; + if (my_id_done) + return my_id; + if (!sdk_wifi_get_macaddr(STATION_IF, (uint8_t *) my_id)) + return NULL; + for (i = 5; i >= 0; --i) { + x = my_id[i] & 0x0F; + if (x > 9) + x += 7; + my_id[i * 2 + 1] = x + '0'; + x = my_id[i] >> 4; + if (x > 9) + x += 7; + my_id[i * 2] = x + '0'; + } + my_id[12] = '\0'; + my_id_done = true; + return my_id; +} + +static int mqtt_ssl_read(Network* n, unsigned char* buffer, int len, + int timeout_ms) { + int r = ssl_read(ssl_conn, buffer, len, timeout_ms); + if (r <= 0 + && (r != MBEDTLS_ERR_SSL_WANT_READ + && r != MBEDTLS_ERR_SSL_WANT_WRITE + && r != MBEDTLS_ERR_SSL_TIMEOUT)) { + printf("%s: TLS read error (%d), resetting\n\r", __func__, r); + ssl_reset = 1; + }; + return r; +} + +static int mqtt_ssl_write(Network* n, unsigned char* buffer, int len, + int timeout_ms) { + int r = ssl_write(ssl_conn, buffer, len, timeout_ms); + if (r <= 0 + && (r != MBEDTLS_ERR_SSL_WANT_READ + && r != MBEDTLS_ERR_SSL_WANT_WRITE)) { + printf("%s: TLS write error (%d), resetting\n\r", __func__, r); + ssl_reset = 1; + } + return r; +} + +static void mqtt_task(void *pvParameters) { + int ret = 0; + struct Network network; + MQTTClient client = DefaultClient; + char mqtt_client_id[20]; + uint8_t mqtt_buf[100]; + uint8_t mqtt_readbuf[100]; + MQTTPacket_connectData data = MQTTPacket_connectData_initializer; + + memset(mqtt_client_id, 0, sizeof(mqtt_client_id)); + strcpy(mqtt_client_id, "ESP-"); + strcat(mqtt_client_id, get_my_id()); + + ssl_conn = (SSLConnection *) malloc(sizeof(SSLConnection)); + while (1) { + if (!wifi_alive) { + vTaskDelay(1000 / portTICK_RATE_MS); + continue; + } + + printf("%s: started\n\r", __func__); + ssl_reset = 0; + ssl_init(ssl_conn); + ssl_conn->ca_cert_str = ca_cert; + ssl_conn->client_cert_str = client_cert; + ssl_conn->client_key_str = client_key; + + NewNetwork(&network); + network.mqttread = mqtt_ssl_read; + network.mqttwrite = mqtt_ssl_write; + + printf("%s: connecting to MQTT server %s ... ", __func__, + client_endpoint); + ret = ssl_connect(ssl_conn, client_endpoint, MQTT_PORT); + + if (ret) { + printf("error: %d\n\r", ret); + ssl_destroy(ssl_conn); + continue; + } + printf("done\n\r"); + NewMQTTClient(&client, &network, 5000, mqtt_buf, 100, mqtt_readbuf, + 100); + + data.willFlag = 0; + data.MQTTVersion = 4; + data.cleansession = 1; + data.clientID.cstring = mqtt_client_id; + data.username.cstring = NULL; + data.password.cstring = NULL; + data.keepAliveInterval = 1000; + printf("Send MQTT connect ... "); + ret = MQTTConnect(&client, &data); + if (ret) { + printf("error: %d\n\r", ret); + ssl_destroy(ssl_conn); + continue; + } + printf("done\r\n"); + MQTTSubscribe(&client, MQTT_SUB_TOPIC, QOS1, topic_received); + xQueueReset(publish_queue); + + while (wifi_alive && !ssl_reset) { + char msg[64]; + while (xQueueReceive(publish_queue, (void *) msg, 0) == pdTRUE) { + portTickType task_tick = xTaskGetTickCount(); + uint32_t free_heap = xPortGetFreeHeapSize(); + uint32_t free_stack = uxTaskGetStackHighWaterMark(NULL); + snprintf(msg, sizeof(msg), "%u: free heap %u, free stack %u", + task_tick, free_heap, free_stack * 4); + printf("Publishing: %s\r\n", msg); + + MQTTMessage message; + message.payload = msg; + message.payloadlen = strlen(msg); + message.dup = 0; + message.qos = QOS1; + message.retained = 0; + ret = MQTTPublish(&client, MQTT_PUB_TOPIC, &message); + if (ret != SUCCESS) { + printf("error while publishing message: %d\n", ret); + break; + } + } + + ret = MQTTYield(&client, 1000); + if (ret == DISCONNECTED) + break; + } + printf("Connection dropped, request restart\n\r"); + ssl_destroy(ssl_conn); + } +} + +static void wifi_task(void *pvParameters) { + uint8_t status = 0; + uint8_t retries = 30; + struct sdk_station_config config = { .ssid = WIFI_SSID, .password = + WIFI_PASS, }; + + printf("%s: Connecting to WiFi\n\r", __func__); + sdk_wifi_set_opmode (STATION_MODE); + sdk_wifi_station_set_config(&config); + + while (1) { + wifi_alive = 0; + + while ((status != STATION_GOT_IP) && (retries)) { + status = sdk_wifi_station_get_connect_status(); + printf("%s: status = %d\n\r", __func__, status); + if (status == STATION_WRONG_PASSWORD) { + printf("WiFi: wrong password\n\r"); + break; + } else if (status == STATION_NO_AP_FOUND) { + printf("WiFi: AP not found\n\r"); + break; + } else if (status == STATION_CONNECT_FAIL) { + printf("WiFi: connection failed\r\n"); + break; + } + vTaskDelay(1000 / portTICK_RATE_MS); + --retries; + } + + while ((status = sdk_wifi_station_get_connect_status()) + == STATION_GOT_IP) { + if (wifi_alive == 0) { + printf("WiFi: Connected\n\r"); + wifi_alive = 1; + } + vTaskDelay(500 / portTICK_RATE_MS); + } + + wifi_alive = 0; + printf("WiFi: disconnected\n\r"); + vTaskDelay(1000 / portTICK_RATE_MS); + } +} + +void user_init(void) { + uart_set_baud(0, 115200); + printf("SDK version: %s, free heap %u\n", sdk_system_get_sdk_version(), + xPortGetFreeHeapSize()); + + gpio_enable(GPIO_LED, GPIO_OUTPUT); + gpio_write(GPIO_LED, 1); + + publish_queue = xQueueCreate(3, 16); + xTaskCreate(&wifi_task, (int8_t *) "wifi_task", 256, NULL, 2, NULL); + xTaskCreate(&beat_task, (int8_t *) "beat_task", 256, NULL, 2, NULL); + xTaskCreate(&mqtt_task, (int8_t *) "mqtt_task", 2048, NULL, 2, NULL); +} diff --git a/examples/aws_iot/ca_cert.c b/examples/aws_iot/ca_cert.c new file mode 100644 index 0000000..b2db9fb --- /dev/null +++ b/examples/aws_iot/ca_cert.c @@ -0,0 +1,29 @@ +// trusted root CA certificate - https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem +const char *ca_cert = "-----BEGIN CERTIFICATE-----\r\n" + "MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB\r\n" + "yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\r\n" + "ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp\r\n" + "U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW\r\n" + "ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0\r\n" + "aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL\r\n" + "MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW\r\n" + "ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln\r\n" + "biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp\r\n" + "U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y\r\n" + "aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1\r\n" + "nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex\r\n" + "t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz\r\n" + "SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG\r\n" + "BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+\r\n" + "rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/\r\n" + "NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E\r\n" + "BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH\r\n" + "BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy\r\n" + "aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv\r\n" + "MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE\r\n" + "p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y\r\n" + "5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK\r\n" + "WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ\r\n" + "4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N\r\n" + "hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq\r\n" + "-----END CERTIFICATE-----\r\n"; diff --git a/examples/aws_iot/client_config.c b/examples/aws_iot/client_config.c new file mode 100644 index 0000000..8b587c2 --- /dev/null +++ b/examples/aws_iot/client_config.c @@ -0,0 +1,17 @@ +// AWS IoT client endpoint +const char *client_endpoint = ".iot..amazonaws.com"; + +// AWS IoT device certificate (ECC) +const char *client_cert = +"-----BEGIN CERTIFICATE-----\r\n" +"------------------ -------------------\r\n" +"-----END CERTIFICATE-----\r\n"; + +// AWS IoT device private key (ECC) +const char *client_key = +"-----BEGIN EC PARAMETERS-----\r\n" +"BggqhkjOPQMBBw==\r\n" +"-----END EC PARAMETERS-----\r\n" +"-----BEGIN EC PRIVATE KEY-----\r\n" +"------------------ -------------------\r\n" +"-----END EC PRIVATE KEY-----\r\n"; diff --git a/examples/aws_iot/mbedtls/config.h b/examples/aws_iot/mbedtls/config.h new file mode 100644 index 0000000..f3b4411 --- /dev/null +++ b/examples/aws_iot/mbedtls/config.h @@ -0,0 +1,119 @@ +/** + * \file config.h + * + * \brief Configuration options (set of defines) + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +/* + * This set of compile-time options may be used to enable + * or disable features selectively, and reduce the global + * memory footprint. + */ +#ifndef MBEDTLS_CONFIG_H +#define MBEDTLS_CONFIG_H + +/* System support */ +#define MBEDTLS_HAVE_ASM +#define MBEDTLS_DEPRECATED_WARNING +#define MBEDTLS_ENTROPY_HARDWARE_ALT +#define MBEDTLS_NO_PLATFORM_ENTROPY + +/* mbed TLS feature support */ +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#define MBEDTLS_SSL_PROTO_TLS1_2 + +/* Debug support (optional) */ +// #define MBEDTLS_ERROR_C +// #define MBEDTLS_DEBUG_C + +/* mbed TLS modules */ +#define MBEDTLS_AES_C +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECP_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_GCM_C +#define MBEDTLS_MD_C +#define MBEDTLS_NET_C +#define MBEDTLS_OID_C +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA512_C +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_SRV_C +#define MBEDTLS_SSL_TLS_C +#define MBEDTLS_X509_CRT_PARSE_C +#define MBEDTLS_X509_USE_C + +/* For verify RSA based root CA certificate (optional if root CA cert is signed using ECC) */ +#define MBEDTLS_RSA_C +#define MBEDTLS_SHA1_C +#define MBEDTLS_PKCS1_V15 +#define MBEDTLS_PKCS1_V21 + +/* For test certificates */ +#define MBEDTLS_BASE64_C +#define MBEDTLS_CERTS_C +#define MBEDTLS_PEM_PARSE_C + +/* Save RAM at the expense of ROM */ +#define MBEDTLS_AES_ROM_TABLES + +/* Save RAM by adjusting to our exact needs */ +#define MBEDTLS_ECP_MAX_BITS 384 +#define MBEDTLS_MPI_MAX_SIZE 256 // 2048 bits + +/* Save RAM at the expense of speed, see ecp.h */ +#define MBEDTLS_ECP_WINDOW_SIZE 2 +#define MBEDTLS_ECP_FIXED_POINT_OPTIM 0 + +/* Significant speed benefit at the expense of some ROM */ +#define MBEDTLS_ECP_NIST_OPTIM + +/* + * You should adjust this to the exact number of sources you're using: default + * is the "mbedtls_platform_entropy_poll" source, but you may want to add other ones. + * Minimum is 2 for the entropy test suite. + */ +#define MBEDTLS_ENTROPY_MAX_SOURCES 2 + +/* Save ROM and a few bytes of RAM by specifying our own ciphersuite list */ +#define MBEDTLS_SSL_CIPHERSUITES \ + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + +/* + * Save RAM at the expense of interoperability: do this only if you control + * both ends of the connection! (See coments in "mbedtls/ssl.h".) + * The minimum size here depends on the certificate chain used as well as the + * typical size of records. + */ +#define MBEDTLS_SSL_MAX_CONTENT_LEN 4096 + +#include "mbedtls/check_config.h" + +#endif /* MBEDTLS_CONFIG_H */ diff --git a/examples/aws_iot/ssl_connection.c b/examples/aws_iot/ssl_connection.c new file mode 100644 index 0000000..be5bb23 --- /dev/null +++ b/examples/aws_iot/ssl_connection.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include + +// this must be ahead of any mbedtls header files so the local mbedtls/config.h can be properly referenced +#include "ssl_connection.h" + +#define SSL_READ_TIMEOUT_MS 2000 + +const char *pers = "esp-tls"; + +static int handle_error(int err) { + +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + + mbedtls_strerror(err, error_buf, 100); + printf("%s\n", error_buf); +#endif + printf("Error: %d\n", err); + return err; +} + +#ifdef MBEDTLS_DEBUG_C +static void my_debug(void *ctx, int level, const char *file, int line, + const char *str) { + ((void) level); + fprintf((FILE *) ctx, "%s:%04d: %s", file, line, str); + fflush((FILE *) ctx); +} +#endif + +void ssl_init(SSLConnection* conn) { + /* + * Initialize the RNG and the session data + */ + mbedtls_net_init(&conn->net_ctx); + mbedtls_ssl_init(&conn->ssl_ctx); + mbedtls_ssl_config_init(&conn->ssl_conf); + + mbedtls_x509_crt_init(&conn->ca_cert); + mbedtls_x509_crt_init(&conn->client_cert); + mbedtls_pk_init(&conn->client_key); + + mbedtls_ctr_drbg_init(&conn->drbg_ctx); + mbedtls_entropy_init(&conn->entropy_ctx); + +} + +int ssl_connect(SSLConnection* conn, const char* host, int port) { + int ret; + char buffer[8]; + + ret = mbedtls_ctr_drbg_seed(&conn->drbg_ctx, mbedtls_entropy_func, + &conn->entropy_ctx, (const unsigned char *) pers, strlen(pers)); + if (ret < 0) { + return -1; + } + + ret = mbedtls_x509_crt_parse(&conn->ca_cert, + (const unsigned char *) conn->ca_cert_str, + strlen(conn->ca_cert_str) + 1); + if (ret < 0) { + return handle_error(ret); + } + + ret = mbedtls_x509_crt_parse(&conn->client_cert, + (const unsigned char *) conn->client_cert_str, + strlen(conn->client_cert_str) + 1); + if (ret < 0) { + return handle_error(ret); + } + + ret = mbedtls_pk_parse_key(&conn->client_key, + (const unsigned char *) conn->client_key_str, + strlen(conn->client_key_str) + 1, NULL, 0); + if (ret != 0) { + return handle_error(ret); + } + + snprintf(buffer, sizeof(buffer), "%d", port); + ret = mbedtls_net_connect(&conn->net_ctx, host, buffer, + MBEDTLS_NET_PROTO_TCP); + if (ret != 0) { + return handle_error(ret); + } + + ret = mbedtls_ssl_config_defaults(&conn->ssl_conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + return handle_error(ret); + } + +#ifdef MBEDTLS_DEBUG_C + mbedtls_ssl_conf_dbg(&conn->ssl_conf, my_debug, stdout); + mbedtls_debug_set_threshold(5); +#endif + + mbedtls_ssl_conf_authmode(&conn->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_rng(&conn->ssl_conf, mbedtls_ctr_drbg_random, + &conn->drbg_ctx); + mbedtls_ssl_conf_read_timeout(&conn->ssl_conf, SSL_READ_TIMEOUT_MS); + mbedtls_ssl_conf_ca_chain(&conn->ssl_conf, &conn->ca_cert, NULL); + + ret = mbedtls_ssl_conf_own_cert(&conn->ssl_conf, &conn->client_cert, + &conn->client_key); + if (ret != 0) { + return handle_error(ret); + } + + ret = mbedtls_ssl_setup(&conn->ssl_ctx, &conn->ssl_conf); + if (ret != 0) { + return handle_error(ret); + } + + ret = mbedtls_ssl_set_hostname(&conn->ssl_ctx, host); + if (ret != 0) { + return handle_error(ret); + } + + mbedtls_ssl_set_bio(&conn->ssl_ctx, &conn->net_ctx, mbedtls_net_send, NULL, + mbedtls_net_recv_timeout); + + while ((ret = mbedtls_ssl_handshake(&conn->ssl_ctx)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ + && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { + return handle_error(ret); + } + } + handle_error(ret); + + vTaskDelay(5000 / portTICK_RATE_MS); + } + + mbedtls_ssl_get_record_expansion(&conn->ssl_ctx); + ret = mbedtls_ssl_get_verify_result(&conn->ssl_ctx); + if (ret != 0) { + return handle_error(ret); + } + + return ret; +} + +int ssl_destroy(SSLConnection* conn) { + mbedtls_net_free(&conn->net_ctx); + mbedtls_ssl_free(&conn->ssl_ctx); + mbedtls_ssl_config_free(&conn->ssl_conf); + mbedtls_ctr_drbg_free(&conn->drbg_ctx); + mbedtls_entropy_free(&conn->entropy_ctx); + mbedtls_x509_crt_free(&conn->ca_cert); + mbedtls_x509_crt_free(&conn->client_cert); + mbedtls_pk_free(&conn->client_key); + + return 0; +} + +int ssl_read(SSLConnection* n, unsigned char* buffer, int len, int timeout_ms) { + // NB: timeout_ms is ignored, so blocking read will timeout after SSL_READ_TIMEOUT_MS + return mbedtls_ssl_read(&n->ssl_ctx, buffer, len); +} + +int ssl_write(SSLConnection* n, unsigned char* buffer, int len, + int timeout_ms) { + // NB: timeout_ms is ignored, so write is always block write + return mbedtls_ssl_write(&n->ssl_ctx, buffer, len); +} diff --git a/examples/aws_iot/ssl_connection.h b/examples/aws_iot/ssl_connection.h new file mode 100644 index 0000000..b8d1bf1 --- /dev/null +++ b/examples/aws_iot/ssl_connection.h @@ -0,0 +1,40 @@ +#ifndef _SSL_CONNECTION_H_ +#define _SSL_CONNECTION_H_ + +// this must be ahead of any mbedtls header files so the local mbedtls/config.h can be properly referenced +#include "mbedtls/config.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" +#include "mbedtls/certs.h" + +typedef struct SSLConnection { + mbedtls_net_context net_ctx; + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + + char *ca_cert_str; + char *client_cert_str; + char *client_key_str; +} SSLConnection; + +extern void ssl_init(SSLConnection* n); +extern int ssl_connect(SSLConnection* n, const char* host, int port); +extern int ssl_destroy(SSLConnection* n); +extern int ssl_read(SSLConnection* n, unsigned char* buffer, int len, + int timeout_ms); +extern int ssl_write(SSLConnection* n, unsigned char* buffer, int len, + int timeout_ms); + +#endif /* _SSL_CONNECTION_H_ */ diff --git a/extras/paho_mqtt_c/MQTTClient.c b/extras/paho_mqtt_c/MQTTClient.c index 4df3d8e..9651ca7 100644 --- a/extras/paho_mqtt_c/MQTTClient.c +++ b/extras/paho_mqtt_c/MQTTClient.c @@ -94,8 +94,13 @@ int readPacket(MQTTClient* c, Timer* timer) goto exit; len = 1; /* 2. read the remaining length. This is variable in itself */ - decodePacket(c, &rem_len, left_ms(timer)); - len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ + len += decodePacket(c, &rem_len, left_ms(timer)); + if (len <= 1 || len + rem_len > c->readbuf_size) /* if packet is too big to fit in our readbuf, abort */ + { + rc = READ_ERROR; + goto exit; + } + MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ /* 3. read the rest of the buffer using a callback to supply the rest of the data */ if (rem_len > 0 && (c->ipstack->mqttread(c->ipstack, c->readbuf + len, rem_len, left_ms(timer)) != rem_len)) {