+// 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
return NULL;
}
- ESP_LOGD(TAG, "host:%s: strlen %zu\n", use_host, hostlen);
+ 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:\n", use_host);
+ ESP_LOGE(TAG, "couldn't get hostname for :%s:", use_host);
free(use_host);
return NULL;
}
return res;
}
-static ssize_t tcp_read(struct esp_tls *tls, char *data, size_t datalen)
+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(struct esp_tls *tls, char *data, size_t datalen)
+static ssize_t tls_read(esp_tls_t *tls, char *data, size_t datalen)
{
ssize_t ret = SSL_read(tls->ssl, data, datalen);
if (ret < 0) {
int err = SSL_get_error(tls->ssl, ret);
if (err != SSL_ERROR_WANT_WRITE && err != SSL_ERROR_WANT_READ) {
- ESP_LOGE(TAG, "read error :%d:\n", ret);
+ ESP_LOGE(TAG, "read error :%d:", ret);
}
return -err;
}
return -1;
}
-static int create_ssl_handle(struct esp_tls *tls, const char *hostname, size_t hostlen, struct esp_tls_cfg *cfg)
+static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostlen, const esp_tls_cfg_t *cfg)
{
int ret;
-
- SSL_CTX *ssl_ctx = SSL_CTX_new(TLSv1_2_client_method());
+
+ const SSL_METHOD *method = cfg->ssl_method!= NULL ? cfg->ssl_method : TLSv1_2_client_method();
+ SSL_CTX *ssl_ctx = SSL_CTX_new(method);
if (!ssl_ctx) {
return -1;
}
X509 *ca = PEM_read_bio_X509(bio, NULL, 0, NULL);
if (!ca) {
- ESP_LOGE(TAG, "CA Error\n");
+ ESP_LOGE(TAG, "CA Error");
+ X509_free(ca);
+ BIO_free(bio);
+ SSL_CTX_free(ssl_ctx);
+ return -1;
}
- ESP_LOGD(TAG, "CA OK\n");
+ ESP_LOGD(TAG, "CA OK");
X509_STORE_add_cert(SSL_CTX_get_cert_store(ssl_ctx), ca);
char *use_host = strndup(hostname, hostlen);
if (!use_host) {
- SSL_CTX_free(ssl_ctx);
- return -1;
+ SSL_CTX_free(ssl_ctx);
+ return -1;
}
SSL_set_tlsext_host_name(ssl, use_host);
free(use_host);
return 0;
}
-void esp_tls_conn_delete(struct esp_tls *tls)
+/**
+ * @brief Close the TLS connection and free any allocated resources.
+ */
+void esp_tls_conn_delete(esp_tls_t *tls)
{
if (!tls) {
return;
free(tls);
};
-static ssize_t tcp_write(struct esp_tls *tls, const char *data, size_t datalen)
+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(struct esp_tls *tls, const char *data, size_t datalen)
+static ssize_t tls_write(esp_tls_t *tls, const char *data, size_t datalen)
{
ssize_t ret = SSL_write(tls->ssl, data, datalen);
if (ret < 0) {
int err = SSL_get_error(tls->ssl, ret);
if (err != SSL_ERROR_WANT_WRITE && err != SSL_ERROR_WANT_READ) {
- ESP_LOGE(TAG, "write error :%d:\n", ret);
+ ESP_LOGE(TAG, "write error :%d:", ret);
}
return -err;
}
return ret;
}
-struct esp_tls *esp_tls_conn_new(const char *hostname, int hostlen, int port, struct esp_tls_cfg *cfg)
+/**
+ * @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;
}
- struct esp_tls *tls = (struct esp_tls *)calloc(1, sizeof(struct esp_tls));
+ esp_tls_t *tls = (esp_tls_t *)calloc(1, sizeof(esp_tls_t));
if (!tls) {
close(sockfd);
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;
}
return 0;
}
-struct esp_tls *esp_tls_conn_http_new(const char *url, struct esp_tls_cfg *cfg)
+/**
+ * @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;
+// 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 <stdbool.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
+#include <fcntl.h>
#ifdef __cplusplus
extern "C" {
#endif
-struct esp_tls_cfg {
- /* 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 *alpn_protos;
- const unsigned char *cacert_pem_buf;
- const unsigned int cacert_pem_bytes;
-};
-
-struct esp_tls {
- SSL_CTX *ctx;
- SSL *ssl;
- int sockfd;
- ssize_t (*read)(struct esp_tls *tls, char *data, size_t datalen);
- ssize_t (*write)(struct esp_tls *tls, const char *data, size_t datalen);
-};
-
-/*
+/**
+ * @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 */
+ const SSL_METHOD *ssl_method; /*!< SSL method that describes internal ssl library
+ methods/functions which implements the various protocol
+ versions. If set to NULL, it defaults to
+ method returned by TLSv1_2_client_method() API. */
+ 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 {
+ SSL_CTX *ctx; /*!< SSL_CTX object is used to establish
+ TLS/SSL enabled connection */
+ SSL *ssl; /*!< SSL object which is needed to hold the data for a
+ TLS/SSL connection. The new structure inherits the settings of the
+ underlying context ctx: connection method (SSLv2/v3/TLSv1),
+ options, verification settings, timeout settings. */
+ 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
*
- * cfg: If you wish to open non-TLS connection, keep this NULL. For TLS
- * connection, a pass pointer to 'struct esp_tls_cfg'. At a minimum, this
- * structure should be zero-initialized.
+ * 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.
*/
-struct esp_tls *esp_tls_conn_new(const char *hostname, int hostlen, int port, struct esp_tls_cfg *cfg);
+esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg);
-/* Convenience API for HTTP URIs */
-struct esp_tls *esp_tls_conn_http_new(const char *url, struct esp_tls_cfg *cfg);
-
-static inline ssize_t esp_tls_conn_write(struct esp_tls *tls, const char *data, size_t datalen)
+/**
+ * @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, data, datalen);
+ return tls->write(tls, (char *)data, datalen);
}
-static inline ssize_t esp_tls_conn_read(struct esp_tls *tls, char *data, size_t 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, data, datalen);
+ return tls->read(tls, (char *)data, datalen);
}
-void esp_tls_conn_delete(struct esp_tls *tls);
+/**
+ * @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
}
##
## Protocols - API Reference
##
+ ## ESP-TLS
+ ../../components/esp-tls/esp_tls.h \
+ ## mDNS
../../components/mdns/include/mdns.h \
##
## Storage - API Reference
--- /dev/null
+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
+
:maxdepth: 1
mDNS <mdns>
-
+ ESP-TLS <esp_tls>
Example code for this API section is provided in :example:`protocols` directory of ESP-IDF examples.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
-#include <fcntl.h>
#include <ctype.h>
#include <netdb.h>
#include <esp_log.h>
static ssize_t callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data,
size_t length)
{
- int rv = esp_tls_conn_write(hd->http2_tls, (const char *)data, (int)length);
+ int rv = esp_tls_conn_write(hd->http2_tls, data, length);
if (rv <= 0) {
if (rv == -SSL_ERROR_WANT_WRITE || rv == -SSL_ERROR_WANT_READ) {
rv = NGHTTP2_ERR_WOULDBLOCK;
int sh2lib_connect(struct sh2lib_handle *hd, const char *uri)
{
memset(hd, 0, sizeof(*hd));
- struct esp_tls_cfg tls_cfg;
- tls_cfg.alpn_protos = (unsigned char *) "\x02h2";
+ 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;
goto error;
}
- int flags = fcntl(hd->http2_tls->sockfd, F_GETFL, 0);
- fcntl(hd->http2_tls->sockfd, F_SETFL, flags | O_NONBLOCK);
-
return 0;
error:
sh2lib_free(hd);
#ifndef __ESP_EXAMPLE_SH2_LIB_H_
#define __ESP_EXAMPLE_SH2_LIB_H_
-#include <openssl/ssl.h>
+#include "esp-tls.h"
#include <nghttp2/nghttp2.h>
/*
# 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.
#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
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
false, true, portMAX_DELAY);
ESP_LOGI(TAG, "Connected to AP");
- struct esp_tls_cfg cfg = {
+ 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,
};
size_t written_bytes = 0;
do {
ret = esp_tls_conn_write(tls,
- (const char *)REQUEST + written_bytes,
+ REQUEST + written_bytes,
strlen(REQUEST) - written_bytes);
if (ret >= 0) {
ESP_LOGI(TAG, "%d bytes written", ret);
written_bytes += ret;
- } else if (-ret != SSL_ERROR_WANT_WRITE && -ret != SSL_ERROR_WANT_READ) {
- ESP_LOGE(TAG, "esp_tls_conn_write returned -0x%x", ret);
+ } else if (ret != -SSL_ERROR_WANT_WRITE && ret != -SSL_ERROR_WANT_READ) {
+ ESP_LOGE(TAG, "esp_tls_conn_write returned 0x%x", ret);
goto exit;
}
} while(written_bytes < strlen(REQUEST));
bzero(buf, sizeof(buf));
ret = esp_tls_conn_read(tls, (char *)buf, len);
- if(-ret == SSL_ERROR_WANT_WRITE || -ret == SSL_ERROR_WANT_READ)
+ if(ret == -SSL_ERROR_WANT_WRITE || ret == -SSL_ERROR_WANT_READ)
continue;
-
- if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
- ret = 0;
- break;
- }
-
+
if(ret < 0)
{
- ESP_LOGE(TAG, "esp_tls_conn_read returned -0x%x", ret);
+ ESP_LOGE(TAG, "esp_tls_conn_read returned 0x%x", ret);
break;
}
} while(1);
exit:
- 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