diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index 7ecccbed..179e9b42 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -20,6 +20,7 @@ #include #include +#include #include "esp_tls.h" #include @@ -123,7 +124,6 @@ static int esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, ESP_LOGE(TAG, "Failed to create socket (family %d socktype %d protocol %d)", res->ai_family, res->ai_socktype, res->ai_protocol); goto err_freeaddr; } - *sockfd = fd; void *addr_ptr; if (res->ai_family == AF_INET) { @@ -155,12 +155,13 @@ static int esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, } ret = connect(fd, addr_ptr, res->ai_addrlen); - if (ret < 0 && !(errno == EINPROGRESS && cfg->non_block)) { + if (ret < 0 && !(errno == EINPROGRESS && cfg && cfg->non_block)) { - ESP_LOGE(TAG, "Failed to connnect to host (errno %d)", errno); + ESP_LOGE(TAG, "Failed to connect to host (errno %d)", errno); goto err_freesocket; } + *sockfd = fd; freeaddrinfo(res); return 0; @@ -171,6 +172,20 @@ err_freeaddr: return ret; } +#if CONFIG_SSL_USING_MBEDTLS +esp_err_t esp_tls_init_global_ca_store() +{ + if (global_cacert == NULL) { + global_cacert = (mbedtls_x509_crt *)calloc(1, sizeof(mbedtls_x509_crt)); + if (global_cacert == NULL) { + ESP_LOGE(TAG, "global_cacert not allocated"); + return ESP_ERR_NO_MEM; + } + mbedtls_x509_crt_init(global_cacert); + } + return ESP_OK; +} +#endif esp_err_t esp_tls_set_global_ca_store(const unsigned char *cacert_pem_buf, const unsigned int cacert_pem_bytes) { @@ -178,17 +193,17 @@ esp_err_t esp_tls_set_global_ca_store(const unsigned char *cacert_pem_buf, const ESP_LOGE(TAG, "cacert_pem_buf is null"); return ESP_ERR_INVALID_ARG; } + #if CONFIG_SSL_USING_MBEDTLS - if (global_cacert != NULL) { - mbedtls_x509_crt_free(global_cacert); - } - global_cacert = (mbedtls_x509_crt *)calloc(1, sizeof(mbedtls_x509_crt)); + int ret; + if (global_cacert == NULL) { - ESP_LOGE(TAG, "global_cacert not allocated"); - return ESP_ERR_NO_MEM; + ret = esp_tls_init_global_ca_store(); + if (ret != ESP_OK) { + return ret; + } } - mbedtls_x509_crt_init(global_cacert); - int ret = mbedtls_x509_crt_parse(global_cacert, cacert_pem_buf, cacert_pem_bytes); + ret = mbedtls_x509_crt_parse(global_cacert, cacert_pem_buf, cacert_pem_bytes); if (ret < 0) { ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); mbedtls_x509_crt_free(global_cacert); @@ -272,11 +287,9 @@ static void esp_tls_cleanup(esp_tls_t *tls) mbedtls_ssl_config_free(&tls->conf); mbedtls_ctr_drbg_free(&tls->ctr_drbg); mbedtls_ssl_free(&tls->ssl); - mbedtls_net_free(&tls->server_fd); #elif CONFIG_SSL_USING_WOLFSSL wolfSSL_shutdown(tls->ssl); wolfSSL_free(tls->ssl); - close(tls->sockfd); wolfSSL_CTX_free(tls->ctx); wolfSSL_Cleanup(); #endif @@ -285,8 +298,8 @@ static void esp_tls_cleanup(esp_tls_t *tls) static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostlen, const esp_tls_cfg_t *cfg) { int ret; + #if CONFIG_SSL_USING_MBEDTLS - mbedtls_net_init(&tls->server_fd); tls->server_fd.fd = tls->sockfd; mbedtls_ssl_init(&tls->ssl); mbedtls_ctr_drbg_init(&tls->ctr_drbg); @@ -299,18 +312,26 @@ static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostle goto exit; } - /* Hostname set here should match CN in server certificate */ - char *use_host = strndup(hostname, hostlen); - if (!use_host) { - goto exit; - } + if (!cfg->skip_common_name) { + char *use_host = NULL; + if (cfg->common_name != NULL) { + use_host = strndup(cfg->common_name, strlen(cfg->common_name)); + } else { + use_host = strndup(hostname, hostlen); + } - if ((ret = mbedtls_ssl_set_hostname(&tls->ssl, use_host)) != 0) { - ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); + if (use_host == NULL) { + goto exit; + } + + /* Hostname set here should match CN in server certificate */ + 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); - goto exit; } - free(use_host); if ((ret = mbedtls_ssl_config_defaults(&tls->conf, MBEDTLS_SSL_IS_CLIENT, @@ -466,7 +487,12 @@ void esp_tls_conn_delete(esp_tls_t *tls) { if (tls != NULL) { esp_tls_cleanup(tls); - if (tls->sockfd) { +#if CONFIG_SSL_USING_MBEDTLS + if (tls->is_tls) { + mbedtls_net_free(&tls->server_fd); + } else +#endif + if (tls->sockfd >= 0) { close(tls->sockfd); } free(tls); @@ -514,16 +540,20 @@ static int esp_tls_low_level_conn(const char *hostname, int hostlen, int port, c and in case of blocking connect these cases will get executed one after the other */ switch (tls->conn_state) { case ESP_TLS_INIT: - ; - int sockfd; - int ret = esp_tcp_connect(hostname, hostlen, port, &sockfd, cfg); + tls->sockfd = -1; + if (cfg != NULL) { +#if CONFIG_SSL_USING_MBEDTLS + mbedtls_net_init(&tls->server_fd); +#endif + tls->is_tls = true; + } + int ret = esp_tcp_connect(hostname, hostlen, port, &tls->sockfd, cfg); if (ret < 0) { return -1; } - tls->sockfd = sockfd; if (!cfg) { - tls->esp_tls_read = tcp_read; - tls->esp_tls_write = tcp_write; + tls->_read = tcp_read; + tls->_write = tcp_write; ESP_LOGD(TAG, "non-tls connection established"); return 1; } @@ -565,8 +595,8 @@ static int esp_tls_low_level_conn(const char *hostname, int hostlen, int port, c tls->conn_state = ESP_TLS_FAIL; return -1; } - tls->esp_tls_read = tls_read; - tls->esp_tls_write = tls_write; + tls->_read = tls_read; + tls->_write = tls_write; tls->conn_state = ESP_TLS_HANDSHAKE; /* falls through */ case ESP_TLS_HANDSHAKE: @@ -654,6 +684,35 @@ int esp_tls_conn_new_async(const char *hostname, int hostlen, int port, const es return esp_tls_low_level_conn(hostname, hostlen, port, cfg, 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 (strncasecmp(&url[u->field_data[UF_SCHEMA].off], "http", u->field_data[UF_SCHEMA].len) == 0) { + return 80; + } else if (strncasecmp(&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); +} + size_t esp_tls_get_bytes_avail(esp_tls_t *tls) { if (!tls) { @@ -666,3 +725,18 @@ size_t esp_tls_get_bytes_avail(esp_tls_t *tls) return wolfSSL_pending(tls->ssl); #endif } + +/** + * @brief Create a new non-blocking TLS/SSL connection with a given "HTTP" url + */ +int esp_tls_conn_http_new_async(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls) +{ + /* 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_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 afbd18d4..566fbcd7 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -65,17 +65,20 @@ typedef struct esp_tls_cfg { - 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 char *cacert_pem_buf; /*!< Certificate Authority's certificate in a buffer. + This buffer should be NULL terminated */ 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 */ + const unsigned char *clientcert_pem_buf;/*!< Client certificate in a buffer + This buffer should be NULL terminated */ 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 */ + const unsigned char *clientkey_pem_buf; /*!< Client key in a buffer + This buffer should be NULL terminated */ unsigned int clientkey_pem_bytes; /*!< Size of client key pointed to by clientkey_pem_buf */ @@ -93,6 +96,11 @@ typedef struct esp_tls_cfg { bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */ + + const char *common_name; /*!< If non-NULL, server certificate CN must match this name. + If NULL, server certificate CN must match hostname. */ + + bool skip_common_name; /*!< Skip any validation of server certificate CN field */ } esp_tls_cfg_t; /** @@ -128,10 +136,10 @@ typedef struct esp_tls { #endif int sockfd; /*!< Underlying socket file descriptor. */ - ssize_t (*esp_tls_read)(struct esp_tls *tls, char *data, size_t datalen); /*!< Callback function for reading data from TLS/SSL + ssize_t (*_read)(struct esp_tls *tls, char *data, size_t datalen); /*!< Callback function for reading data from TLS/SSL connection. */ - ssize_t (*esp_tls_write)(struct esp_tls *tls, const char *data, size_t datalen); /*!< Callback function for writing data to TLS/SSL + ssize_t (*_write)(struct esp_tls *tls, const char *data, size_t datalen); /*!< Callback function for writing data to TLS/SSL connection. */ esp_tls_conn_state_t conn_state; /*!< ESP-TLS Connection state */ @@ -139,6 +147,8 @@ typedef struct esp_tls { fd_set rset; /*!< read file descriptors */ fd_set wset; /*!< write file descriptors */ + + bool is_tls; /*!< indicates connection type (TLS or NON-TLS) */ } esp_tls_t; /** @@ -156,6 +166,20 @@ typedef struct esp_tls { * @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 blocking 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 Create a new non-blocking TLS/SSL connection @@ -177,6 +201,22 @@ esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const e */ int esp_tls_conn_new_async(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_t *tls); +/** + * @brief Create a new non-blocking 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. + * @param[in] tls pointer to esp-tls as esp-tls handle. + * + * @return + * - -1 If connection establishment fails. + * - 0 If connection establishment is in progress. + * - 1 If connection establishment is successful. + */ +int esp_tls_conn_http_new_async(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls); + /** * @brief Write from buffer 'data' into specified tls connection. * @@ -194,7 +234,7 @@ int esp_tls_conn_new_async(const char *hostname, int hostlen, int port, const es */ static inline ssize_t esp_tls_conn_write(esp_tls_t *tls, const void *data, size_t datalen) { - return tls->esp_tls_write(tls, (char *)data, datalen); + return tls->_write(tls, (char *)data, datalen); } /** @@ -214,7 +254,7 @@ static inline ssize_t esp_tls_conn_write(esp_tls_t *tls, const void *data, size_ */ static inline ssize_t esp_tls_conn_read(esp_tls_t *tls, void *data, size_t datalen) { - return tls->esp_tls_read(tls, (char *)data, datalen); + return tls->_read(tls, (char *)data, datalen); } /** @@ -243,10 +283,25 @@ void esp_tls_conn_delete(esp_tls_t *tls); size_t esp_tls_get_bytes_avail(esp_tls_t *tls); /** - * @brief Create a global CA store with the buffer provided in cfg. + * @brief Create a global CA store, initially empty. * - * This function should be called if the application wants to use the same CA store for - * multiple connections. The application must call this function before calling esp_tls_conn_new(). + * This function should be called if the application wants to use the same CA store for multiple connections. + * This function initialises the global CA store which can be then set by calling esp_tls_set_global_ca_store(). + * To be effective, this function must be called before any call to esp_tls_set_global_ca_store(). + * + * @return + * - ESP_OK if creating global CA store was successful. + * - ESP_ERR_NO_MEM if an error occured when allocating the mbedTLS resources. + */ +esp_err_t esp_tls_init_global_ca_store(); + +/** + * @brief Set the global CA store with the buffer provided in pem format. + * + * This function should be called if the application wants to set the global CA store for + * multiple connections i.e. to add the certificates in the provided buffer to the certificate chain. + * This function implicitly calls esp_tls_init_global_ca_store() if it has not already been called. + * The application must call this function before calling esp_tls_conn_new(). * * @param[in] cacert_pem_buf Buffer which has certificates in pem format. This buffer * is used for creating a global CA store, which can be used @@ -254,7 +309,7 @@ size_t esp_tls_get_bytes_avail(esp_tls_t *tls); * @param[in] cacert_pem_bytes Length of the buffer. * * @return - * - ESP_OK if creating global CA store was successful. + * - ESP_OK if adding certificates was successful. * - Other if an error occured or an action must be taken by the calling process. */ esp_err_t esp_tls_set_global_ca_store(const unsigned char *cacert_pem_buf, const unsigned int cacert_pem_bytes);