diff --git a/components/esp-tls/component.mk b/components/esp-tls/component.mk new file mode 100644 index 000000000..7267d5f37 --- /dev/null +++ b/components/esp-tls/component.mk @@ -0,0 +1,3 @@ +COMPONENT_SRCDIRS := . + +COMPONENT_ADD_INCLUDEDIRS := . diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c new file mode 100644 index 000000000..d84cf77bf --- /dev/null +++ b/components/esp-tls/esp_tls.c @@ -0,0 +1,319 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "esp_tls.h" + +static const char *TAG = "esp-tls"; + +#ifdef ESP_PLATFORM +#include +#else +#define ESP_LOGD(TAG, ...) //printf(__VA_ARGS__); +#define ESP_LOGE(TAG, ...) printf(__VA_ARGS__); +#endif + +static struct addrinfo *resolve_host_name(const char *host, size_t hostlen) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + char *use_host = strndup(host, hostlen); + if (!use_host) { + return NULL; + } + + ESP_LOGD(TAG, "host:%s: strlen %lu", use_host, (unsigned long)hostlen); + struct addrinfo *res; + if (getaddrinfo(use_host, NULL, &hints, &res)) { + ESP_LOGE(TAG, "couldn't get hostname for :%s:", use_host); + free(use_host); + return NULL; + } + free(use_host); + return res; +} + +static ssize_t tcp_read(esp_tls_t *tls, char *data, size_t datalen) +{ + return recv(tls->sockfd, data, datalen, 0); +} + +static ssize_t tls_read(esp_tls_t *tls, char *data, size_t datalen) +{ + ssize_t ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)data, datalen); + if (ret < 0) { + if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + return 0; + } + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "read error :%d:", ret); + } + } + return ret; +} + +static int esp_tcp_connect(const char *host, int hostlen, int port) +{ + struct addrinfo *res = resolve_host_name(host, hostlen); + if (!res) { + return -1; + } + + int ret = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (ret < 0) { + goto err_freeaddr; + } + int fd = ret; + + void *addr_ptr; + if (res->ai_family == AF_INET) { + struct sockaddr_in *p = (struct sockaddr_in *)res->ai_addr; + p->sin_port = htons(port); + addr_ptr = p; + } else if (res->ai_family == AF_INET6) { + struct sockaddr_in6 *p = (struct sockaddr_in6 *)res->ai_addr; + p->sin6_port = htons(port); + p->sin6_family = AF_INET6; + addr_ptr = p; + } else { + /* Unsupported Protocol Family */ + goto err_freesocket; + } + + ret = connect(fd, addr_ptr, res->ai_addrlen); + if (ret < 0) { + goto err_freesocket; + } + + freeaddrinfo(res); + return fd; + +err_freesocket: + close(fd); +err_freeaddr: + freeaddrinfo(res); + return -1; +} + +static void verify_certificate(esp_tls_t *tls) +{ + int flags; + char buf[100]; + if ((flags = mbedtls_ssl_get_verify_result(&tls->ssl)) != 0) { + ESP_LOGI(TAG, "Failed to verify peer certificate!"); + bzero(buf, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + ESP_LOGI(TAG, "verification info: %s", buf); + } else { + ESP_LOGI(TAG, "Certificate verified."); + } +} + +static void mbedtls_cleanup(esp_tls_t *tls) +{ + if (!tls) { + return; + } + + mbedtls_entropy_free(&tls->entropy); + mbedtls_ssl_config_free(&tls->conf); + mbedtls_ctr_drbg_free(&tls->ctr_drbg); + mbedtls_ssl_free(&tls->ssl); + mbedtls_net_free(&tls->server_fd); +} + +static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostlen, const esp_tls_cfg_t *cfg) +{ + int ret; + + mbedtls_net_init(&tls->server_fd); + tls->server_fd.fd = tls->sockfd; + mbedtls_ssl_init(&tls->ssl); + mbedtls_ctr_drbg_init(&tls->ctr_drbg); + mbedtls_ssl_config_init(&tls->conf); + mbedtls_entropy_init(&tls->entropy); + + if ((ret = mbedtls_ctr_drbg_seed(&tls->ctr_drbg, + mbedtls_entropy_func, &tls->entropy, NULL, 0)) != 0) { + ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret); + goto exit; + } + + /* Hostname set here should match CN in server certificate */ + char *use_host = strndup(hostname, hostlen); + if (!use_host) { + goto exit; + } + + if ((ret = mbedtls_ssl_set_hostname(&tls->ssl, use_host)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); + free(use_host); + goto exit; + } + free(use_host); + + if ((ret = mbedtls_ssl_config_defaults(&tls->conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret); + goto exit; + } + + if (cfg->cacert_pem_buf != NULL) { + mbedtls_x509_crt_init(&tls->cacert); + ret = mbedtls_x509_crt_parse(&tls->cacert, cfg->cacert_pem_buf, cfg->cacert_pem_bytes); + if (ret < 0) { + ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); + goto exit; + } + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->cacert, NULL); + } else { + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE); + } + + mbedtls_ssl_conf_rng(&tls->conf, mbedtls_ctr_drbg_random, &tls->ctr_drbg); + +#ifdef CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log(&tls->conf, 4); +#endif + + if ((ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret); + goto exit; + } + + mbedtls_ssl_set_bio(&tls->ssl, &tls->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + while ((ret = mbedtls_ssl_handshake(&tls->ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret); + if (cfg->cacert_pem_buf != NULL) { + /* This is to check whether handshake failed due to invalid certificate*/ + verify_certificate(tls); + } + goto exit; + } + } + + return 0; +exit: + mbedtls_cleanup(tls); + return -1; +} + +/** + * @brief Close the TLS connection and free any allocated resources. + */ +void esp_tls_conn_delete(esp_tls_t *tls) +{ + mbedtls_cleanup(tls); + if (tls->sockfd) { + close(tls->sockfd); + } + free(tls); +}; + +static ssize_t tcp_write(esp_tls_t *tls, const char *data, size_t datalen) +{ + return send(tls->sockfd, data, datalen, 0); +} + +static ssize_t tls_write(esp_tls_t *tls, const char *data, size_t datalen) +{ + ssize_t ret = mbedtls_ssl_write(&tls->ssl, (unsigned char*) data, datalen); + if (ret < 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "write error :%d:", ret); + } + } + return ret; +} + +/** + * @brief Create a new TLS/SSL connection + */ +esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg) +{ + int sockfd = esp_tcp_connect(hostname, hostlen, port); + if (sockfd < 0) { + return NULL; + } + + esp_tls_t *tls = (esp_tls_t *)calloc(1, sizeof(esp_tls_t)); + if (!tls) { + close(sockfd); + return NULL; + } + tls->sockfd = sockfd; + tls->read = tcp_read; + tls->write = tcp_write; + + if (cfg) { + if (create_ssl_handle(tls, hostname, hostlen, cfg) != 0) { + esp_tls_conn_delete(tls); + return NULL; + } + tls->read = tls_read; + tls->write = tls_write; + } + + if (cfg->non_block == true) { + int flags = fcntl(tls->sockfd, F_GETFL, 0); + fcntl(tls->sockfd, F_SETFL, flags | O_NONBLOCK); + } + + return tls; +} + +static int get_port(const char *url, struct http_parser_url *u) +{ + if (u->field_data[UF_PORT].len) { + return strtol(&url[u->field_data[UF_PORT].off], NULL, 10); + } else { + if (strncmp(&url[u->field_data[UF_SCHEMA].off], "http", u->field_data[UF_SCHEMA].len) == 0) { + return 80; + } else if (strncmp(&url[u->field_data[UF_SCHEMA].off], "https", u->field_data[UF_SCHEMA].len) == 0) { + return 443; + } + } + return 0; +} + +/** + * @brief Create a new TLS/SSL connection with a given "HTTP" url + */ +esp_tls_t *esp_tls_conn_http_new(const char *url, const esp_tls_cfg_t *cfg) +{ + /* Parse URI */ + struct http_parser_url u; + http_parser_url_init(&u); + http_parser_parse_url(url, strlen(url), 0, &u); + + /* Connect to host */ + return esp_tls_conn_new(&url[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len, + get_port(url, &u), cfg); +} diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h new file mode 100644 index 000000000..4f2f35a03 --- /dev/null +++ b/components/esp-tls/esp_tls.h @@ -0,0 +1,172 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#ifndef _ESP_TLS_H_ +#define _ESP_TLS_H_ + +#include +#include +#include + + +#include "mbedtls/platform.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/esp_debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ESP-TLS configuration parameters + */ +typedef struct esp_tls_cfg { + const unsigned char *alpn_protos; /*!< Application protocols required for HTTP2. + If HTTP2/ALPN support is required, a list + of protocols that should be negotiated. + The format is length followed by protocol + name. + For the most common cases the following is ok: + "\x02h2" + - where the first '2' is the length of the protocol and + - the subsequent 'h2' is the protocol name */ + + const unsigned char *cacert_pem_buf; /*!< Certificate Authority's certificate in a buffer */ + + const unsigned int cacert_pem_bytes; /*!< Size of Certificate Authority certificate + pointed to by cacert_pem_buf */ + + 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 */ +} esp_tls_cfg_t; + +/** + * @brief ESP-TLS Connection Handle + */ +typedef struct esp_tls { + mbedtls_ssl_context ssl; /*!< TLS/SSL context */ + + mbedtls_entropy_context entropy; /*!< mbedTLS entropy context structure */ + + mbedtls_ctr_drbg_context ctr_drbg; /*!< mbedTLS ctr drbg context structure. + CTR_DRBG is deterministic random + bit generation based on AES-256 */ + + mbedtls_ssl_config conf; /*!< TLS/SSL configuration to be shared + between mbedtls_ssl_context + structures */ + + mbedtls_net_context server_fd; /*!< mbedTLS wrapper type for sockets */ + + mbedtls_x509_crt cacert; /*!< Container for an X.509 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 + connection. */ + + ssize_t (*write)(struct esp_tls *tls, const char *data, size_t datalen); /*!< Callback function for writing data to TLS/SSL + connection. */ +} esp_tls_t; + +/** + * @brief Create a new TLS/SSL connection + * + * This function establishes a TLS/SSL connection with the specified host. + * + * @param[in] hostname Hostname of the host. + * @param[in] hostlen Length of hostname. + * @param[in] port Port number of the host. + * @param[in] cfg TLS configuration as esp_tls_cfg_t. If you wish to open + * non-TLS connection, keep this NULL. For TLS connection, + * a pass pointer to esp_tls_cfg_t. At a minimum, this + * structure should be zero-initialized. + * @return pointer to esp_tls_t, or NULL if connection couldn't be opened. + */ +esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg); + +/** + * @brief Create a new TLS/SSL connection with a given "HTTP" url + * + * The behaviour is same as esp_tls_conn_new() API. However this API accepts host's url. + * + * @param[in] url url of host. + * @param[in] cfg TLS configuration as esp_tls_cfg_t. If you wish to open + * non-TLS connection, keep this NULL. For TLS connection, + * a pass pointer to 'esp_tls_cfg_t'. At a minimum, this + * structure should be zero-initialized. + * @return pointer to esp_tls_t, or NULL if connection couldn't be opened. + */ +esp_tls_t *esp_tls_conn_http_new(const char *url, const esp_tls_cfg_t *cfg); + +/** + * @brief Write from buffer 'data' into specified tls connection. + * + * @param[in] tls pointer to esp-tls as esp-tls handle. + * @param[in] data Buffer from which data will be written. + * @param[in] datalen Length of data buffer. + * + * @return + * - >0 if write operation was successful, the return value is the number + * of bytes actually written to the TLS/SSL connection. + * - 0 if write operation was not successful. The underlying + * connection was closed. + * - <0 if write operation was not successful, because either an + * error occured or an action must be taken by the calling process. + */ +static inline ssize_t esp_tls_conn_write(esp_tls_t *tls, const void *data, size_t datalen) +{ + return tls->write(tls, (char *)data, datalen); +} + +/** + * @brief Read from specified tls connection into the buffer 'data'. + * + * @param[in] tls pointer to esp-tls as esp-tls handle. + * @param[in] data Buffer to hold read data. + * @param[in] datalen Length of data buffer. + * + * @return +* - >0 if read operation was successful, the return value is the number +* of bytes actually read from the TLS/SSL connection. +* - 0 if read operation was not successful. The underlying +* connection was closed. +* - <0 if read operation was not successful, because either an +* error occured or an action must be taken by the calling process. +*/ +static inline ssize_t esp_tls_conn_read(esp_tls_t *tls, void *data, size_t datalen) +{ + return tls->read(tls, (char *)data, datalen); +} + +/** + * @brief Close the TLS/SSL connection and free any allocated resources. + * + * This function should be called to close each tls connection opened with esp_tls_conn_new() or + * esp_tls_conn_http_new() APIs. + * + * @param[in] tls pointer to esp-tls as esp-tls handle. + */ +void esp_tls_conn_delete(esp_tls_t *tls); + +#ifdef __cplusplus +} +#endif + +#endif /* ! _ESP_TLS_H_ */ diff --git a/docs/Doxyfile b/docs/Doxyfile index 9e9f37780..e0c1e0d11 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -87,6 +87,9 @@ INPUT = \ ## ## Protocols - API Reference ## + ## ESP-TLS + ../../components/esp-tls/esp_tls.h \ + ## mDNS ../../components/mdns/include/mdns.h \ ## ## Storage - API Reference diff --git a/docs/en/api-reference/protocols/esp_tls.rst b/docs/en/api-reference/protocols/esp_tls.rst new file mode 100644 index 000000000..1552d7dab --- /dev/null +++ b/docs/en/api-reference/protocols/esp_tls.rst @@ -0,0 +1,24 @@ +ESP-TLS +======= + +Overview +-------- + +The ESP-TLS component provides a simplified API interface for accessing the commonly used TLS functionality. +It supports common scenarios like CA certification validation, SNI, ALPN negotiation, non-blocking connection among others. +All the configuration can be specified in the esp_tls_cfg_t data structure. Once done, TLS communication can be conducted using the following APIs: +* esp_tls_conn_new(): for opening a new TLS connection +* esp_tls_conn_read/write(): for reading/writing from the connection +* esp_tls_conn_delete(): for freeing up the connection +Any application layer protocol like HTTP1, HTTP2 etc can be executed on top of this layer. + +Application Example +------------------- + +Simple HTTPS example that uses ESP-TLS to establish a secure socket connection: :example:`protocols/https_request`. + +API Reference +------------- + +.. include:: /_build/inc/esp_tls.inc + diff --git a/docs/en/api-reference/protocols/index.rst b/docs/en/api-reference/protocols/index.rst index b5572d8cd..fb9ca36a7 100644 --- a/docs/en/api-reference/protocols/index.rst +++ b/docs/en/api-reference/protocols/index.rst @@ -5,6 +5,6 @@ Protocols API :maxdepth: 1 mDNS - + ESP-TLS Example code for this API section is provided in :example:`protocols` directory of ESP-IDF examples. diff --git a/docs/zh_CN/api-reference/protocols/esp_tls.rst b/docs/zh_CN/api-reference/protocols/esp_tls.rst new file mode 100644 index 000000000..d15c745d8 --- /dev/null +++ b/docs/zh_CN/api-reference/protocols/esp_tls.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/protocols/esp_tls.rst diff --git a/examples/protocols/http2_request/components/sh2lib/connectlib.c b/examples/protocols/http2_request/components/sh2lib/connectlib.c deleted file mode 100644 index 2fe42cbdc..000000000 --- a/examples/protocols/http2_request/components/sh2lib/connectlib.c +++ /dev/null @@ -1,164 +0,0 @@ -/* With adaptations by Espressif Systems - * - * Copyright (c) 2013 Tatsuhiro Tsujikawa - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "connectlib.h" - -/* Basic parser from nghttp2/examples/client.c */ -int parse_uri(struct uri *res, const char *uri) -{ - /* We only interested in https */ - size_t len, i, offset; - int ipv6addr = 0; - memset(res, 0, sizeof(struct uri)); - len = strlen(uri); - if (len < 9 || memcmp("https://", uri, 8) != 0) { - return -1; - } - offset = 8; - res->host = res->hostport = &uri[offset]; - res->hostlen = 0; - if (uri[offset] == '[') { - /* IPv6 literal address */ - ++offset; - ++res->host; - ipv6addr = 1; - for (i = offset; i < len; ++i) { - if (uri[i] == ']') { - res->hostlen = i - offset; - offset = i + 1; - break; - } - } - } else { - const char delims[] = ":/?#"; - for (i = offset; i < len; ++i) { - if (strchr(delims, uri[i]) != NULL) { - break; - } - } - res->hostlen = i - offset; - offset = i; - } - if (res->hostlen == 0) { - return -1; - } - /* Assuming https */ - res->port = 443; - if (offset < len) { - if (uri[offset] == ':') { - /* port */ - const char delims[] = "/?#"; - int port = 0; - ++offset; - for (i = offset; i < len; ++i) { - if (strchr(delims, uri[i]) != NULL) { - break; - } - if ('0' <= uri[i] && uri[i] <= '9') { - port *= 10; - port += uri[i] - '0'; - if (port > 65535) { - return -1; - } - } else { - return -1; - } - } - if (port == 0) { - return -1; - } - offset = i; - res->port = (uint16_t)port; - } - } - res->hostportlen = (size_t)(uri + offset + ipv6addr - res->host); - for (i = offset; i < len; ++i) { - if (uri[i] == '#') { - break; - } - } - if (i - offset == 0) { - res->path = "/"; - res->pathlen = 1; - } else { - res->path = &uri[offset]; - res->pathlen = i - offset; - } - return 0; -} - -int connect_to_host(const char *host, size_t hostlen, uint16_t port) -{ - int ret; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - }; - - char service[6]; - snprintf(service, sizeof(service), "%u", port); - - char *use_host = calloc(1, hostlen + 1); - if (!use_host) { - return -1; - } - strncpy(use_host, host, hostlen); - - struct addrinfo *res; - ret = getaddrinfo(use_host, service, &hints, &res); - if (ret) { - free(use_host); - return -1; - } - free(use_host); - - ret = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (ret < 0) { - goto err_freeaddr; - } - - int fd = ret; - ret = connect(fd, res->ai_addr, res->ai_addrlen); - if (ret < 0) { - goto err_freesocket; - } - - return fd; - -err_freesocket: - close(fd); -err_freeaddr: - freeaddrinfo(res); - return -1; -} - diff --git a/examples/protocols/http2_request/components/sh2lib/connectlib.h b/examples/protocols/http2_request/components/sh2lib/connectlib.h deleted file mode 100644 index 86677fd7a..000000000 --- a/examples/protocols/http2_request/components/sh2lib/connectlib.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 Espressif Systems (Shanghai) PTE LTD -// -// 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. -#ifndef __ESP_EXAMPLE_CONNECT_LIB_H_ -#define __ESP_EXAMPLE_CONNECT_LIB_H_ - -struct uri { - const char *host; - /* In this program, path contains query component as well. */ - const char *path; - size_t pathlen; - const char *hostport; - size_t hostlen; - size_t hostportlen; - uint16_t port; -}; - -/* connect() to a TCP host, return socket descriptor */ -int connect_to_host(const char *host, size_t hostlen, uint16_t port); - -/* Parse a URI into its components */ -int parse_uri(struct uri *res, const char *uri); - -#endif /* ! __ESP_EXAMPLE_CONNECT_LIB_H_ */ diff --git a/examples/protocols/http2_request/components/sh2lib/sh2lib.c b/examples/protocols/http2_request/components/sh2lib/sh2lib.c index ba46593f6..3abfc5c5c 100644 --- a/examples/protocols/http2_request/components/sh2lib/sh2lib.c +++ b/examples/protocols/http2_request/components/sh2lib/sh2lib.c @@ -17,55 +17,16 @@ #include #include #include -#include #include #include #include -#include "connectlib.h" #include "sh2lib.h" static const char *TAG = "sh2lib"; #define DBG_FRAME_SEND 1 -/* SSL connection on the TCP socket that is already connected */ -static int do_ssl_connect(struct sh2lib_handle *hd, int sockfd, const char *hostname) -{ - SSL_CTX *ssl_ctx = SSL_CTX_new(TLSv1_2_client_method()); - if (!ssl_ctx) { - return -1; - } - - unsigned char vector[] = "\x02h2"; - SSL_CTX_set_alpn_protos(ssl_ctx, vector, strlen((char *)vector)); - SSL *ssl = SSL_new(ssl_ctx); - if (!ssl) { - SSL_CTX_free(ssl_ctx); - return -1; - } - - SSL_set_tlsext_host_name(ssl, hostname); - SSL_set_fd(ssl, sockfd); - int ret = SSL_connect(ssl); - if (ret < 1) { - int err = SSL_get_error(ssl, ret); - ESP_LOGE(TAG, "[ssl-connect] Failed SSL handshake ret=%d error=%d", ret, err); - SSL_CTX_free(ssl_ctx); - SSL_free(ssl); - return -1; - } - hd->ssl_ctx = ssl_ctx; - hd->ssl = ssl; - hd->sockfd = sockfd; - hd->hostname = strdup(hostname); - - int flags = fcntl(hd->sockfd, F_GETFL, 0); - fcntl(hd->sockfd, F_SETFL, flags | O_NONBLOCK); - - return 0; -} - /* * The implementation of nghttp2_send_callback type. Here we write * |data| with size |length| to the network and return the number of @@ -75,10 +36,9 @@ static int do_ssl_connect(struct sh2lib_handle *hd, int sockfd, const char *host static ssize_t callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data, size_t length) { - int rv = SSL_write(hd->ssl, data, (int)length); + int rv = esp_tls_conn_write(hd->http2_tls, data, length); if (rv <= 0) { - int err = SSL_get_error(hd->ssl, rv); - if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + if (rv == MBEDTLS_ERR_SSL_WANT_READ || rv == MBEDTLS_ERR_SSL_WANT_WRITE) { rv = NGHTTP2_ERR_WOULDBLOCK; } else { rv = NGHTTP2_ERR_CALLBACK_FAILURE; @@ -128,10 +88,9 @@ static ssize_t callback_recv(nghttp2_session *session, uint8_t *buf, { struct sh2lib_handle *hd = user_data; int rv; - rv = SSL_read(hd->ssl, buf, (int)length); + rv = esp_tls_conn_read(hd->http2_tls, (char *)buf, (int)length); if (rv < 0) { - int err = SSL_get_error(hd->ssl, rv); - if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + if (rv == MBEDTLS_ERR_SSL_WANT_READ || rv == MBEDTLS_ERR_SSL_WANT_WRITE) { rv = NGHTTP2_ERR_WOULDBLOCK; } else { rv = NGHTTP2_ERR_CALLBACK_FAILURE; @@ -281,24 +240,12 @@ static int do_http2_connect(struct sh2lib_handle *hd) int sh2lib_connect(struct sh2lib_handle *hd, const char *uri) { memset(hd, 0, sizeof(*hd)); - - struct uri res; - /* Parse the URI */ - if (parse_uri(&res, uri) != 0) { - ESP_LOGE(TAG, "[sh2-connect] Failed to parse URI"); - return -1; - } - - /* TCP connection with the server */ - int sockfd = connect_to_host(res.host, res.hostlen, res.port); - if (sockfd < 0) { - ESP_LOGE(TAG, "[sh2-connect] Failed to connect to %s", uri); - return -1; - } - - /* SSL Connection on the socket */ - if (do_ssl_connect(hd, sockfd, res.host) != 0) { - ESP_LOGE(TAG, "[sh2-connect] SSL Handshake failed with %s", uri); + esp_tls_cfg_t tls_cfg = { + .alpn_protos = (unsigned char *) "\x02h2", + .non_block = true, + }; + if ((hd->http2_tls = esp_tls_conn_http_new(uri, &tls_cfg)) == NULL) { + ESP_LOGE(TAG, "[sh2-connect] esp-tls connection failed"); goto error; } @@ -320,17 +267,9 @@ void sh2lib_free(struct sh2lib_handle *hd) nghttp2_session_del(hd->http2_sess); hd->http2_sess = NULL; } - if (hd->ssl) { - SSL_free(hd->ssl); - hd->ssl = NULL; - } - if (hd->ssl_ctx) { - SSL_CTX_free(hd->ssl_ctx); - hd->ssl_ctx = NULL; - } - if (hd->sockfd) { - close(hd->sockfd); - hd->ssl_ctx = 0; + if (hd->http2_tls) { + esp_tls_conn_delete(hd->http2_tls); + hd->http2_tls = NULL; } if (hd->hostname) { free(hd->hostname); @@ -346,11 +285,13 @@ int sh2lib_execute(struct sh2lib_handle *hd) ESP_LOGE(TAG, "[sh2-execute] HTTP2 session send failed %d", ret); return -1; } + ret = nghttp2_session_recv(hd->http2_sess); if (ret != 0) { ESP_LOGE(TAG, "[sh2-execute] HTTP2 session recv failed %d", ret); return -1; } + return 0; } diff --git a/examples/protocols/http2_request/components/sh2lib/sh2lib.h b/examples/protocols/http2_request/components/sh2lib/sh2lib.h index 97095eb5a..44d1a5333 100644 --- a/examples/protocols/http2_request/components/sh2lib/sh2lib.h +++ b/examples/protocols/http2_request/components/sh2lib/sh2lib.h @@ -14,7 +14,7 @@ #ifndef __ESP_EXAMPLE_SH2_LIB_H_ #define __ESP_EXAMPLE_SH2_LIB_H_ -#include +#include "esp_tls.h" #include /* @@ -33,14 +33,10 @@ * @brief Handle for working with sh2lib APIs */ struct sh2lib_handle { - /* Ideally, CTX is per-program, so we could potentially take it out of this - * per-connection structure - */ - SSL_CTX *ssl_ctx; /*!< Pointer to the SSL context */ - SSL *ssl; /*!< Pointer to the SSL handle */ nghttp2_session *http2_sess; /*!< Pointer to the HTTP2 session handle */ int sockfd; /*!< Socket file descriptor */ char *hostname; /*!< The hostname we are connected to */ + struct esp_tls *http2_tls; /*!< Pointer to the TLS session handle */ }; /** Flag indicating receive stream is reset */ diff --git a/examples/protocols/https_mbedtls/Makefile b/examples/protocols/https_mbedtls/Makefile new file mode 100644 index 000000000..070f89048 --- /dev/null +++ b/examples/protocols/https_mbedtls/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := https-mbedtls + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/https_mbedtls/main/Kconfig.projbuild b/examples/protocols/https_mbedtls/main/Kconfig.projbuild new file mode 100644 index 000000000..1c7241da3 --- /dev/null +++ b/examples/protocols/https_mbedtls/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +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. + + Can be left blank if the network has no security set. + +endmenu diff --git a/examples/protocols/https_mbedtls/main/component.mk b/examples/protocols/https_mbedtls/main/component.mk new file mode 100644 index 000000000..818e2a182 --- /dev/null +++ b/examples/protocols/https_mbedtls/main/component.mk @@ -0,0 +1,10 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +# embed files from the "certs" directory as binary data symbols +# in the app +COMPONENT_EMBED_TXTFILES := server_root_cert.pem + + diff --git a/examples/protocols/https_mbedtls/main/https_mbedtls_example_main.c b/examples/protocols/https_mbedtls/main/https_mbedtls_example_main.c new file mode 100644 index 000000000..93aaba74f --- /dev/null +++ b/examples/protocols/https_mbedtls/main/https_mbedtls_example_main.c @@ -0,0 +1,341 @@ +/* HTTPS GET Example using plain mbedTLS sockets + * + * Contacts the howsmyssl.com API via TLS v1.2 and reads a JSON + * response. + * + * Adapted from the ssl_client1 example in mbedtls. + * + * Original Copyright (C) 2006-2016, ARM Limited, All Rights Reserved, Apache 2.0 License. + * Additions Copyright (C) Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD, Apache 2.0 License. + * + * + * 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. + */ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +#include "mbedtls/platform.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/esp_debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = BIT0; + +/* Constants that aren't configurable in menuconfig */ +#define WEB_SERVER "www.howsmyssl.com" +#define WEB_PORT "443" +#define WEB_URL "https://www.howsmyssl.com/a/check" + +static const char *TAG = "example"; + +static const char *REQUEST = "GET " WEB_URL " HTTP/1.0\r\n" + "Host: "WEB_SERVER"\r\n" + "User-Agent: esp-idf/1.0 esp32\r\n" + "\r\n"; + +/* Root cert for howsmyssl.com, taken from server_root_cert.pem + + The PEM file was extracted from the output of this command: + openssl s_client -showcerts -connect www.howsmyssl.com:443 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: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(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 = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static void https_get_task(void *pvParameters) +{ + char buf[512]; + int ret, flags, len; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_x509_crt cacert; + mbedtls_ssl_config conf; + mbedtls_net_context server_fd; + + mbedtls_ssl_init(&ssl); + mbedtls_x509_crt_init(&cacert); + mbedtls_ctr_drbg_init(&ctr_drbg); + ESP_LOGI(TAG, "Seeding the random number generator"); + + mbedtls_ssl_config_init(&conf); + + mbedtls_entropy_init(&entropy); + if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + NULL, 0)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret); + abort(); + } + + ESP_LOGI(TAG, "Loading the CA root certificate..."); + + ret = mbedtls_x509_crt_parse(&cacert, server_root_cert_pem_start, + server_root_cert_pem_end-server_root_cert_pem_start); + + if(ret < 0) + { + ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); + abort(); + } + + ESP_LOGI(TAG, "Setting hostname for TLS session..."); + + /* Hostname set here should match CN in server certificate */ + if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); + abort(); + } + + ESP_LOGI(TAG, "Setting up the SSL/TLS structure..."); + + if((ret = mbedtls_ssl_config_defaults(&conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret); + goto exit; + } + + /* MBEDTLS_SSL_VERIFY_OPTIONAL is bad for security, in this example it will print + a warning if CA verification fails but it will continue to connect. + + You should consider using MBEDTLS_SSL_VERIFY_REQUIRED in your own code. + */ + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); +#ifdef CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log(&conf, 4); +#endif + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret); + goto exit; + } + + while(1) { + /* Wait for the callback to set the CONNECTED_BIT in the + event group. + */ + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, + false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to AP"); + + mbedtls_net_init(&server_fd); + + ESP_LOGI(TAG, "Connecting to %s:%s...", WEB_SERVER, WEB_PORT); + + if ((ret = mbedtls_net_connect(&server_fd, WEB_SERVER, + WEB_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) + { + ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Connected."); + + mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + ESP_LOGI(TAG, "Performing the SSL/TLS handshake..."); + + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) + { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) + { + ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret); + goto exit; + } + } + + ESP_LOGI(TAG, "Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0) + { + /* In real life, we probably want to close connection if ret != 0 */ + ESP_LOGW(TAG, "Failed to verify peer certificate!"); + bzero(buf, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + ESP_LOGW(TAG, "verification info: %s", buf); + } + else { + ESP_LOGI(TAG, "Certificate verified."); + } + + ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl)); + + ESP_LOGI(TAG, "Writing HTTP request..."); + + size_t written_bytes = 0; + do { + ret = mbedtls_ssl_write(&ssl, + (const unsigned char *)REQUEST + written_bytes, + strlen(REQUEST) - written_bytes); + if (ret >= 0) { + ESP_LOGI(TAG, "%d bytes written", ret); + written_bytes += ret; + } else if (ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_WANT_READ) { + ESP_LOGE(TAG, "mbedtls_ssl_write returned -0x%x", -ret); + goto exit; + } + } while(written_bytes < strlen(REQUEST)); + + ESP_LOGI(TAG, "Reading HTTP response..."); + + do + { + len = sizeof(buf) - 1; + bzero(buf, sizeof(buf)); + ret = mbedtls_ssl_read(&ssl, (unsigned char *)buf, len); + + if(ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) + continue; + + if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + ret = 0; + break; + } + + if(ret < 0) + { + ESP_LOGE(TAG, "mbedtls_ssl_read returned -0x%x", -ret); + break; + } + + if(ret == 0) + { + ESP_LOGI(TAG, "connection closed"); + break; + } + + len = ret; + ESP_LOGD(TAG, "%d bytes read", len); + /* Print response directly to stdout as it is read */ + for(int i = 0; i < len; i++) { + putchar(buf[i]); + } + } while(1); + + mbedtls_ssl_close_notify(&ssl); + + exit: + mbedtls_ssl_session_reset(&ssl); + mbedtls_net_free(&server_fd); + + if(ret != 0) + { + mbedtls_strerror(ret, buf, 100); + ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf); + } + + putchar('\n'); // JSON output doesn't have a newline at end + + static int request_count; + ESP_LOGI(TAG, "Completed %d requests", ++request_count); + + for(int countdown = 10; countdown >= 0; countdown--) { + ESP_LOGI(TAG, "%d...", countdown); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + ESP_LOGI(TAG, "Starting again!"); + } +} + +void app_main() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_wifi(); + xTaskCreate(&https_get_task, "https_get_task", 8192, NULL, 5, NULL); +} diff --git a/examples/protocols/https_mbedtls/main/server_root_cert.pem b/examples/protocols/https_mbedtls/main/server_root_cert.pem new file mode 100644 index 000000000..0002462ce --- /dev/null +++ b/examples/protocols/https_mbedtls/main/server_root_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/examples/protocols/https_request/README.md b/examples/protocols/https_request/README.md index fb7c6df95..7b158b6af 100644 --- a/examples/protocols/https_request/README.md +++ b/examples/protocols/https_request/README.md @@ -1,5 +1,5 @@ # HTTPS Request Example -Uses an mbedTLS socket to make a very simple HTTPS request over a secure connection, including verifying the server TLS certificate. +Uses APIs from `esp-tls` component to make a very simple HTTPS request over a secure connection, including verifying the server TLS certificate. See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/https_request/main/https_request_example_main.c b/examples/protocols/https_request/main/https_request_example_main.c index 93aaba74f..3964b4ea5 100644 --- a/examples/protocols/https_request/main/https_request_example_main.c +++ b/examples/protocols/https_request/main/https_request_example_main.c @@ -38,14 +38,7 @@ #include "lwip/netdb.h" #include "lwip/dns.h" -#include "mbedtls/platform.h" -#include "mbedtls/net_sockets.h" -#include "mbedtls/esp_debug.h" -#include "mbedtls/ssl.h" -#include "mbedtls/entropy.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/error.h" -#include "mbedtls/certs.h" +#include "esp_tls.h" /* The examples use simple WiFi configuration that you can set via 'make menuconfig'. @@ -88,7 +81,7 @@ static const char *REQUEST = "GET " WEB_URL " HTTP/1.0\r\n" */ extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start"); extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end"); - + static esp_err_t event_handler(void *ctx, system_event_t *event) { switch(event->event_id) { @@ -133,78 +126,7 @@ static void initialise_wifi(void) static void https_get_task(void *pvParameters) { char buf[512]; - int ret, flags, len; - - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_ssl_context ssl; - mbedtls_x509_crt cacert; - mbedtls_ssl_config conf; - mbedtls_net_context server_fd; - - mbedtls_ssl_init(&ssl); - mbedtls_x509_crt_init(&cacert); - mbedtls_ctr_drbg_init(&ctr_drbg); - ESP_LOGI(TAG, "Seeding the random number generator"); - - mbedtls_ssl_config_init(&conf); - - mbedtls_entropy_init(&entropy); - if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - NULL, 0)) != 0) - { - ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret); - abort(); - } - - ESP_LOGI(TAG, "Loading the CA root certificate..."); - - ret = mbedtls_x509_crt_parse(&cacert, server_root_cert_pem_start, - server_root_cert_pem_end-server_root_cert_pem_start); - - if(ret < 0) - { - ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - abort(); - } - - ESP_LOGI(TAG, "Setting hostname for TLS session..."); - - /* Hostname set here should match CN in server certificate */ - if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0) - { - ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); - abort(); - } - - ESP_LOGI(TAG, "Setting up the SSL/TLS structure..."); - - if((ret = mbedtls_ssl_config_defaults(&conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT)) != 0) - { - ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret); - goto exit; - } - - /* MBEDTLS_SSL_VERIFY_OPTIONAL is bad for security, in this example it will print - a warning if CA verification fails but it will continue to connect. - - You should consider using MBEDTLS_SSL_VERIFY_REQUIRED in your own code. - */ - mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); - mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); - mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); -#ifdef CONFIG_MBEDTLS_DEBUG - mbedtls_esp_enable_debug_log(&conf, 4); -#endif - - if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) - { - ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret); - goto exit; - } + int ret, len; while(1) { /* Wait for the callback to set the CONNECTED_BIT in the @@ -213,61 +135,30 @@ static void https_get_task(void *pvParameters) xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); ESP_LOGI(TAG, "Connected to AP"); - - mbedtls_net_init(&server_fd); - - ESP_LOGI(TAG, "Connecting to %s:%s...", WEB_SERVER, WEB_PORT); - - if ((ret = mbedtls_net_connect(&server_fd, WEB_SERVER, - WEB_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) - { - ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + esp_tls_cfg_t cfg = { + .cacert_pem_buf = server_root_cert_pem_start, + .cacert_pem_bytes = server_root_cert_pem_end - server_root_cert_pem_start, + }; + + struct esp_tls *tls = esp_tls_conn_http_new(WEB_URL, &cfg); + + if(tls != NULL) { + ESP_LOGI(TAG, "Connection established..."); + } else { + ESP_LOGE(TAG, "Connection failed..."); goto exit; } - - ESP_LOGI(TAG, "Connected."); - - mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); - - ESP_LOGI(TAG, "Performing the SSL/TLS handshake..."); - - while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) - { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) - { - ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret); - goto exit; - } - } - - ESP_LOGI(TAG, "Verifying peer X.509 certificate..."); - - if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0) - { - /* In real life, we probably want to close connection if ret != 0 */ - ESP_LOGW(TAG, "Failed to verify peer certificate!"); - bzero(buf, sizeof(buf)); - mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); - ESP_LOGW(TAG, "verification info: %s", buf); - } - else { - ESP_LOGI(TAG, "Certificate verified."); - } - - ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl)); - - ESP_LOGI(TAG, "Writing HTTP request..."); - + size_t written_bytes = 0; do { - ret = mbedtls_ssl_write(&ssl, - (const unsigned char *)REQUEST + written_bytes, - strlen(REQUEST) - written_bytes); + ret = esp_tls_conn_write(tls, + REQUEST + written_bytes, + strlen(REQUEST) - written_bytes); if (ret >= 0) { ESP_LOGI(TAG, "%d bytes written", ret); written_bytes += ret; - } else if (ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_WANT_READ) { - ESP_LOGE(TAG, "mbedtls_ssl_write returned -0x%x", -ret); + } else if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "esp_tls_conn_write returned 0x%x", ret); goto exit; } } while(written_bytes < strlen(REQUEST)); @@ -278,19 +169,14 @@ static void https_get_task(void *pvParameters) { len = sizeof(buf) - 1; bzero(buf, sizeof(buf)); - ret = mbedtls_ssl_read(&ssl, (unsigned char *)buf, len); - - if(ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) + ret = esp_tls_conn_read(tls, (char *)buf, len); + + if(ret == MBEDTLS_ERR_SSL_WANT_WRITE || ret == MBEDTLS_ERR_SSL_WANT_READ) continue; - - if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { - ret = 0; - break; - } - + if(ret < 0) - { - ESP_LOGE(TAG, "mbedtls_ssl_read returned -0x%x", -ret); + { + ESP_LOGE(TAG, "esp_tls_conn_read returned -0x%x", -ret); break; } @@ -308,18 +194,8 @@ static void https_get_task(void *pvParameters) } } while(1); - mbedtls_ssl_close_notify(&ssl); - exit: - mbedtls_ssl_session_reset(&ssl); - mbedtls_net_free(&server_fd); - - if(ret != 0) - { - mbedtls_strerror(ret, buf, 100); - ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf); - } - + esp_tls_conn_delete(tls); putchar('\n'); // JSON output doesn't have a newline at end static int request_count;