From 1b82463deed4de95bb9664953e1f10420b6c2fe0 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 23 May 2017 16:12:09 +0300 Subject: [PATCH] net: http: Add HTTPS client support Add HTTPS support into http-client library. The init of the HTTPS client connection is different compared to HTTP client, but the actual HTTP request sending is using the same API as HTTP client. Signed-off-by: Jukka Rissanen --- include/net/http.h | 182 ++++-- subsys/net/lib/http/http_client.c | 881 +++++++++++++++++++++++++++++- 2 files changed, 1013 insertions(+), 50 deletions(-) diff --git a/include/net/http.h b/include/net/http.h index 74f71de1d61..eb1878e2125 100644 --- a/include/net/http.h +++ b/include/net/http.h @@ -7,6 +7,8 @@ #ifndef __HTTP_H__ #define __HTTP_H__ +#include + #if defined(CONFIG_HTTPS) #if defined(CONFIG_MBEDTLS) #if !defined(CONFIG_MBEDTLS_CFG_FILE) @@ -44,6 +46,37 @@ void http_heap_init(void); #define http_heap_init() #endif /* MBEDTLS_MEMORY_BUFFER_ALLOC_C */ +typedef int (*http_send_data_t)(struct net_pkt *pkt, + net_context_send_cb_t cb, + s32_t timeout, + void *token, + void *user_data); + +#if defined(CONFIG_HTTPS) +/* Internal information for managing HTTPS data */ +struct https_context { + struct net_pkt *rx_pkt; + struct net_buf *frag; + struct k_sem tx_sem; + struct k_fifo rx_fifo; + struct k_fifo tx_fifo; + int remaining; +}; + +/** + * @typedef https_entropy_src_cb_t + * @brief Callback used when the API user needs to setup the entropy source. + * @detail This is the same as mbedtls_entropy_f_source_ptr callback. + * + * @param data Callback-specific data pointer + * @param output Data to fill + * @param len Maximum size to provide + * @param olen The actual amount of bytes put into the buffer (Can be 0) + */ +typedef int (*https_entropy_src_cb_t)(void *data, unsigned char *output, + size_t len, size_t *olen); +#endif /* CONFIG_HTTPS */ + #if defined(CONFIG_HTTP_CLIENT) #include @@ -136,6 +169,22 @@ typedef void (*http_response_cb_t)(struct http_client_ctx *ctx, enum http_final_call final_data, void *user_data); +#if defined(CONFIG_HTTPS) +/** + * @typedef https_ca_cert_cb_t + * @brief Callback used when the API user needs to setup the + * HTTPS certs. + * + * @param ctx HTTPS client context. + * @param ca_cert MBEDTLS certificate. This is of type mbedtls_x509_crt + * if MBEDTLS_X509_CRT_PARSE_C is defined. + * + * @return 0 if ok, <0 if there is an error + */ +typedef int (*https_ca_cert_cb_t)(struct http_client_ctx *ctx, + void *ca_cert); +#endif /* CONFIG_HTTPS */ + /** * HTTP client context information. This contains all the data that is * needed when doing HTTP requests. @@ -147,6 +196,10 @@ struct http_client_ctx { /** Server name */ const char *server; + /** Is this instance HTTPS or not. + */ + bool is_https; + #if defined(CONFIG_DNS_RESOLVER) /** Remember the DNS query id so that it can be cancelled * if the HTTP context is released and the query is active @@ -169,9 +222,20 @@ struct http_client_ctx { s32_t timeout; /** User can define this callback if it wants to have - * special handling of the received raw data. + * special handling of the received raw data. Note that this + * callback is not called for HTTPS data. */ http_receive_cb_t receive_cb; + + /** Internal function that is called when HTTP data is + * received from network. + */ + net_context_recv_cb_t recv_cb; + + /** Internal function that is called when HTTP data is sent to + * network. + */ + http_send_data_t send_data; } tcp; /** HTTP request information */ @@ -242,6 +306,42 @@ struct http_client_ctx { u8_t cl_present:1; u8_t body_found:1; } rsp; + +#if defined(CONFIG_HTTPS) + struct { + /** HTTPS stack for mbedtls library. */ + u8_t *stack; + + /** HTTPS stack size. */ + int stack_size; + + /** HTTPS thread id */ + k_tid_t tid; + + /** HTTPS thread */ + struct k_thread thread; + + /** Memory pool for RX data */ + struct k_mem_pool *pool; + + /** Hostname to be used in the certificate verification */ + const char *cert_host; + + /** mbedtls related configuration. */ + struct { + struct https_context ssl_ctx; + https_ca_cert_cb_t cert_cb; + https_entropy_src_cb_t entropy_src_cb; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt ca_cert; + u8_t *personalization_data; + size_t personalization_data_len; + } mbedtls; + } https; +#endif /* CONFIG_HTTPS */ }; /** @@ -283,13 +383,14 @@ struct http_client_request { * @brief Generic function to send a HTTP request to the network. Normally * applications would not need to use this function. * - * @param net_ctx Network context. + * @param ctx HTTP client context. * @param req HTTP request to perform. * @param timeout Timeout when doing net_buf allocations. * * @return Return 0 if ok, and <0 if error. */ -int http_request(struct net_context *net_ctx, struct http_client_request *req, +int http_request(struct http_client_ctx *ctx, + struct http_client_request *req, s32_t timeout); /** @@ -414,7 +515,7 @@ static inline int http_client_send_post_req(struct http_client_ctx *http_ctx, * @param http_ctx HTTP context. * @param server HTTP server address or host name. If host name is given, * then DNS resolver support (CONFIG_DNS_RESOLVER) must be enabled. If caller - * sets the server parameter as NULL, then it no attempt is done to figure out + * sets the server parameter as NULL, then no attempt is done to figure out * the remote address and caller must set the address in http_ctx.tcp.remote * itself. * @param server_port HTTP server TCP port. @@ -424,6 +525,51 @@ static inline int http_client_send_post_req(struct http_client_ctx *http_ctx, int http_client_init(struct http_client_ctx *http_ctx, const char *server, u16_t server_port); +#if defined(CONFIG_HTTPS) +/** + * @brief Initialize user supplied HTTP context when using HTTPS. + * + * @detail Caller can set the various fields in http_ctx after this call + * if needed. + * + * @param http_ctx HTTPS context. + * @param server HTTPS server address or host name. If host name is given, + * then DNS resolver support (CONFIG_DNS_RESOLVER) must be enabled. If caller + * sets the server parameter as NULL, then no attempt is done to figure out + * the remote address and caller must set the address in http_ctx.tcp.remote + * itself. + * @param server_port HTTPS server TCP port. + * @param personalization_data Personalization data (Device specific + * identifiers) for random number generator. (Can be NULL). + * @param personalization_data_len Length of the personalization data. + * @param cert_cb User supplied callback that setups the certifacates. + * @param cert_host Hostname that is used to verify the server certificate. + * This value is used when HTTP client API calls mbedtls_ssl_set_hostname() + * which sets the hostname to check against the received server certificate. + * See https://tls.mbed.org/kb/how-to/use-sni for more details. + * This can be left NULL in which case mbedtls will silently skip certificate + * verification entirely. This option is only used if MBEDTLS_X509_CRT_PARSE_C + * is enabled in mbedtls config file. + * @param entropy_src_cb User supplied callback that setup the entropy. This + * can be set to NULL, in which case default entropy source is used. + * @param pool Memory pool for RX data reads. + * @param https_stack HTTPS thread stack. + * @param https_stack_len HTTP thread stack size. + * + * @return Return 0 if ok, <0 if error. + */ +int https_client_init(struct http_client_ctx *http_ctx, + const char *server, u16_t server_port, + u8_t *personalization_data, + size_t personalization_data_len, + https_ca_cert_cb_t cert_cb, + const char *cert_host, + https_entropy_src_cb_t entropy_src_cb, + struct k_mem_pool *pool, + u8_t *https_stack, + size_t https_stack_size); +#endif /* CONFIG_HTTPS */ + /** * @brief Release all the resources allocated for HTTP context. * @@ -491,15 +637,6 @@ struct http_server_urls { }; #if defined(CONFIG_HTTPS) -/* Internal information for managing HTTPS data */ -struct https_context { - struct net_pkt *rx_pkt; - struct net_buf *frag; - struct k_sem tx_sem; - struct k_fifo rx_fifo; - int remaining; -}; - /** * @typedef https_server_cert_cb_t * @brief Callback used when the API user needs to setup the @@ -514,27 +651,8 @@ struct https_context { typedef int (*https_server_cert_cb_t)(struct http_server_ctx *ctx, mbedtls_x509_crt *cert, mbedtls_pk_context *pkey); - -/** - * @typedef https_entropy_src_cb_t - * @brief Callback used when the API user needs to setup the entropy source. - * @detail This is the same as mbedtls_entropy_f_source_ptr callback. - * - * @param data Callback-specific data pointer - * @param output Data to fill - * @param len Maximum size to provide - * @param olen The actual amount of bytes put into the buffer (Can be 0) - */ -typedef int (*https_entropy_src_cb_t)(void *data, unsigned char *output, - size_t len, size_t *olen); #endif /* CONFIG_HTTPS */ -typedef int (*http_send_data_t)(struct net_pkt *pkt, - net_context_send_cb_t cb, - s32_t timeout, - void *token, - void *user_data); - /* The HTTP server context struct */ struct http_server_ctx { /** Collection of URLs that this server context will handle */ diff --git a/subsys/net/lib/http/http_client.c b/subsys/net/lib/http/http_client.c index 6b366e957ea..50a2768c7d8 100644 --- a/subsys/net/lib/http/http_client.c +++ b/subsys/net/lib/http/http_client.c @@ -5,10 +5,16 @@ */ #if defined(CONFIG_NET_DEBUG_HTTP) +#if defined(CONFIG_HTTPS) +#define SYS_LOG_DOMAIN "https/client" +#else #define SYS_LOG_DOMAIN "http/client" +#endif #define NET_LOG_ENABLED 1 #endif +#define RX_EXTRA_DEBUG 0 + #include #include @@ -18,7 +24,23 @@ #include -#define BUF_ALLOC_TIMEOUT K_SECONDS(1) +#if defined(CONFIG_HTTPS) +#if defined(MBEDTLS_DEBUG_C) +#include +/* - Debug levels (from ext/lib/crypto/mbedtls/include/mbedtls/debug.h) + * - 0 No debug + * - 1 Error + * - 2 State change + * - 3 Informational + * - 4 Verbose + */ +#define DEBUG_THRESHOLD 0 +#endif +#endif /* CONFIG_HTTPS */ + +#define BUF_ALLOC_TIMEOUT 100 + +#define HTTPS_STARTUP_TIMEOUT K_SECONDS(5) /* HTTP client defines */ #define HTTP_EOF "\r\n\r\n" @@ -31,14 +53,15 @@ struct waiter { struct k_sem wait; }; -int http_request(struct net_context *net_ctx, struct http_client_request *req, +int http_request(struct http_client_ctx *ctx, + struct http_client_request *req, s32_t timeout) { const char *method = http_method_str(req->method); struct net_pkt *pkt; int ret = -ENOMEM; - pkt = net_pkt_get_tx(net_ctx, timeout); + pkt = net_pkt_get_tx(ctx->tcp.ctx, timeout); if (!pkt) { return -ENOMEM; } @@ -129,7 +152,22 @@ int http_request(struct net_context *net_ctx, struct http_client_request *req, } } - return net_context_send(pkt, NULL, timeout, NULL, NULL); +#if defined(CONFIG_NET_IPV6) + if (net_pkt_family(pkt) == AF_INET6) { + net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt) - + net_pkt_ip_hdr_len(pkt) - + net_pkt_ipv6_ext_opt_len(pkt)); + } else +#endif + { + net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt) - + net_pkt_ip_hdr_len(pkt)); + } + + ret = ctx->tcp.send_data(pkt, NULL, timeout, NULL, ctx); + if (ret == 0) { + return 0; + } out: net_pkt_unref(pkt); @@ -289,7 +327,7 @@ static int on_headers_complete(struct http_parser *parser) static int on_message_begin(struct http_parser *parser) { -#if defined(CONFIG_NET_DEBUG_HTTP) +#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); @@ -491,6 +529,12 @@ static int tcp_connect(struct http_client_ctx *ctx) socklen_t addrlen = sizeof(struct sockaddr_in); int ret; + if (ctx->tcp.ctx && net_context_is_used(ctx->tcp.ctx) && + net_context_get_state(ctx->tcp.ctx) == NET_CONTEXT_CONNECTED) { + /* If we are already connected, then just return */ + return -EALREADY; + } + if (ctx->tcp.remote.family == AF_INET6) { addrlen = sizeof(struct sockaddr_in6); @@ -534,10 +578,11 @@ static int tcp_connect(struct http_client_ctx *ctx) goto out; } - return net_context_recv(ctx->tcp.ctx, recv_cb, K_NO_WAIT, ctx); + return net_context_recv(ctx->tcp.ctx, ctx->tcp.recv_cb, K_NO_WAIT, ctx); out: net_context_put(ctx->tcp.ctx); + ctx->tcp.ctx = NULL; return ret; } @@ -576,6 +621,612 @@ static inline void print_info(struct http_client_ctx *ctx, #endif } +#if defined(CONFIG_HTTPS) +#if defined(MBEDTLS_ERROR_C) +#define print_error(fmt, ret) \ + do { \ + char error[64]; \ + \ + mbedtls_strerror(ret, error, sizeof(error)); \ + \ + NET_ERR(fmt " (%s)", -ret, error); \ + } while (0) +#else +#define print_error(fmt, ret) NET_ERR(fmt, -ret) +#endif /* MBEDTLS_ERROR_C */ + +static void ssl_sent(struct net_context *context, + int status, void *token, void *user_data) +{ + struct http_client_ctx *http_ctx = user_data; + + k_sem_give(&http_ctx->https.mbedtls.ssl_ctx.tx_sem); +} + +/* Send encrypted data */ +static int ssl_tx(void *context, const unsigned char *buf, size_t size) +{ + struct http_client_ctx *ctx = context; + struct net_pkt *send_pkt; + int ret, len; + + send_pkt = net_pkt_get_tx(ctx->tcp.ctx, BUF_ALLOC_TIMEOUT); + if (!send_pkt) { + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + + ret = net_pkt_append_all(send_pkt, size, (u8_t *)buf, + BUF_ALLOC_TIMEOUT); + if (!ret) { + /* Cannot append data */ + net_pkt_unref(send_pkt); + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + + len = size; + + ret = net_context_send(send_pkt, ssl_sent, K_NO_WAIT, NULL, ctx); + if (ret < 0) { + net_pkt_unref(send_pkt); + return ret; + } + + k_sem_take(&ctx->https.mbedtls.ssl_ctx.tx_sem, K_FOREVER); + + return len; +} + +struct rx_fifo_block { + sys_snode_t snode; + struct k_mem_block block; + struct net_pkt *pkt; +}; + +struct tx_fifo_block { + struct k_mem_block block; + struct http_client_request *req; +}; + +/* Receive encrypted data from network. Put that data into fifo + * that will be read by https thread. + */ +static void ssl_received(struct net_context *context, + struct net_pkt *pkt, + int status, + void *user_data) +{ + struct http_client_ctx *http_ctx = user_data; + struct rx_fifo_block *rx_data = NULL; + struct k_mem_block block; + int ret; + + ARG_UNUSED(context); + ARG_UNUSED(status); + + if (pkt && !net_pkt_appdatalen(pkt)) { + net_pkt_unref(pkt); + return; + } + + ret = k_mem_pool_alloc(http_ctx->https.pool, &block, + sizeof(struct rx_fifo_block), + BUF_ALLOC_TIMEOUT); + if (ret < 0) { + if (pkt) { + net_pkt_unref(pkt); + } + + return; + } + + rx_data = block.data; + rx_data->pkt = pkt; + + /* For freeing memory later */ + memcpy(&rx_data->block, &block, sizeof(struct k_mem_block)); + + k_fifo_put(&http_ctx->https.mbedtls.ssl_ctx.rx_fifo, (void *)rx_data); + + /* Let the ssl_rx() to run */ + k_yield(); +} + +int ssl_rx(void *context, unsigned char *buf, size_t size) +{ + struct http_client_ctx *ctx = context; + struct rx_fifo_block *rx_data; + u16_t read_bytes; + u8_t *ptr; + int pos; + int len; + int ret = 0; + + if (!ctx->https.mbedtls.ssl_ctx.frag) { + rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo, + K_FOREVER); + if (!rx_data || !rx_data->pkt) { + NET_DBG("Closing %p connection", ctx); + + if (rx_data) { + k_mem_pool_free(&rx_data->block); + } + + return MBEDTLS_ERR_SSL_CONN_EOF; + } + + ctx->https.mbedtls.ssl_ctx.rx_pkt = rx_data->pkt; + + k_mem_pool_free(&rx_data->block); + + read_bytes = net_pkt_appdatalen( + ctx->https.mbedtls.ssl_ctx.rx_pkt); + + ctx->https.mbedtls.ssl_ctx.remaining = read_bytes; + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.rx_pkt->frags; + + ptr = net_pkt_appdata(ctx->https.mbedtls.ssl_ctx.rx_pkt); + len = ptr - ctx->https.mbedtls.ssl_ctx.frag->data; + + if (len > ctx->https.mbedtls.ssl_ctx.frag->size) { + NET_ERR("Buf overflow (%d > %u)", len, + ctx->https.mbedtls.ssl_ctx.frag->size); + return -EINVAL; + } + + /* This will get rid of IP header */ + net_buf_pull(ctx->https.mbedtls.ssl_ctx.frag, len); + } else { + read_bytes = ctx->https.mbedtls.ssl_ctx.remaining; + ptr = ctx->https.mbedtls.ssl_ctx.frag->data; + } + + len = ctx->https.mbedtls.ssl_ctx.frag->len; + pos = 0; + if (read_bytes > size) { + while (ctx->https.mbedtls.ssl_ctx.frag) { + read_bytes = len < (size - pos) ? len : (size - pos); + +#if RX_EXTRA_DEBUG == 1 + NET_DBG("Copying %d bytes", read_bytes); +#endif + + memcpy(buf + pos, ptr, read_bytes); + + pos += read_bytes; + if (pos < size) { + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.frag->frags; + ptr = ctx->https.mbedtls.ssl_ctx.frag->data; + len = ctx->https.mbedtls.ssl_ctx.frag->len; + } else { + if (read_bytes == len) { + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.frag->frags; + } else { + net_buf_pull( + ctx->https.mbedtls.ssl_ctx.frag, + read_bytes); + } + + ctx->https.mbedtls.ssl_ctx.remaining -= size; + return size; + } + } + } else { + while (ctx->https.mbedtls.ssl_ctx.frag) { +#if RX_EXTRA_DEBUG == 1 + NET_DBG("Copying all %d bytes", len); +#endif + + memcpy(buf + pos, ptr, len); + + pos += len; + ctx->https.mbedtls.ssl_ctx.frag = + ctx->https.mbedtls.ssl_ctx.frag->frags; + if (!ctx->https.mbedtls.ssl_ctx.frag) { + break; + } + + ptr = ctx->https.mbedtls.ssl_ctx.frag->data; + len = ctx->https.mbedtls.ssl_ctx.frag->len; + } + + net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt); + ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL; + ctx->https.mbedtls.ssl_ctx.frag = NULL; + ctx->https.mbedtls.ssl_ctx.remaining = 0; + + if (read_bytes != pos) { + return -EIO; + } + + ret = read_bytes; + } + + return ret; +} + +#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP) +static void my_debug(void *ctx, int level, + const char *file, int line, const char *str) +{ + const char *p, *basename; + int len; + + ARG_UNUSED(ctx); + + /* Extract basename from file */ + for (p = basename = file; *p != '\0'; p++) { + if (*p == '/' || *p == '\\') { + basename = p + 1; + } + + } + + /* Avoid printing double newlines */ + len = strlen(str); + if (str[len - 1] == '\n') { + ((char *)str)[len - 1] = '\0'; + } + + NET_DBG("%s:%04d: |%d| %s", basename, line, level, str); +} +#endif /* MBEDTLS_DEBUG_C && CONFIG_NET_DEBUG_HTTP */ + +static int entropy_source(void *data, unsigned char *output, size_t len, + size_t *olen) +{ + u32_t seed; + + ARG_UNUSED(data); + + seed = sys_rand32_get(); + + if (len > sizeof(seed)) { + len = sizeof(seed); + } + + memcpy(output, &seed, len); + + *olen = len; + return 0; +} + +static int https_init(struct http_client_ctx *ctx) +{ + int ret = 0; + + k_sem_init(&ctx->https.mbedtls.ssl_ctx.tx_sem, 0, UINT_MAX); + k_fifo_init(&ctx->https.mbedtls.ssl_ctx.rx_fifo); + k_fifo_init(&ctx->https.mbedtls.ssl_ctx.tx_fifo); + + mbedtls_platform_set_printf(printk); + + http_heap_init(); + + mbedtls_ssl_init(&ctx->https.mbedtls.ssl); + mbedtls_ssl_config_init(&ctx->https.mbedtls.conf); + mbedtls_entropy_init(&ctx->https.mbedtls.entropy); + mbedtls_ctr_drbg_init(&ctx->https.mbedtls.ctr_drbg); + +#if defined(MBEDTLS_X509_CRT_PARSE_C) + mbedtls_x509_crt_init(&ctx->https.mbedtls.ca_cert); +#endif + +#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP) + mbedtls_debug_set_threshold(DEBUG_THRESHOLD); + mbedtls_ssl_conf_dbg(&ctx->https.mbedtls.conf, my_debug, NULL); +#endif + + /* Seed the RNG */ + mbedtls_entropy_add_source(&ctx->https.mbedtls.entropy, + ctx->https.mbedtls.entropy_src_cb, + NULL, + MBEDTLS_ENTROPY_MAX_GATHER, + MBEDTLS_ENTROPY_SOURCE_STRONG); + + ret = mbedtls_ctr_drbg_seed( + &ctx->https.mbedtls.ctr_drbg, + mbedtls_entropy_func, + &ctx->https.mbedtls.entropy, + (const unsigned char *)ctx->https.mbedtls.personalization_data, + ctx->https.mbedtls.personalization_data_len); + if (ret != 0) { + print_error("mbedtls_ctr_drbg_seed returned -0x%x", ret); + goto exit; + } + + /* Setup SSL defaults etc. */ + ret = mbedtls_ssl_config_defaults(&ctx->https.mbedtls.conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + print_error("mbedtls_ssl_config_defaults returned -0x%x", ret); + goto exit; + } + + mbedtls_ssl_conf_rng(&ctx->https.mbedtls.conf, + mbedtls_ctr_drbg_random, + &ctx->https.mbedtls.ctr_drbg); + + /* Load the certificates and other related stuff. This needs to be done + * by the user so we call a callback that user must have provided. + */ + ret = ctx->https.mbedtls.cert_cb(ctx, &ctx->https.mbedtls.ca_cert); + if (ret != 0) { + goto exit; + } + + ret = mbedtls_ssl_setup(&ctx->https.mbedtls.ssl, + &ctx->https.mbedtls.conf); + if (ret != 0) { + NET_ERR("mbedtls_ssl_setup returned -0x%x", ret); + goto exit; + } + +#if defined(MBEDTLS_X509_CRT_PARSE_C) + if (ctx->https.cert_host) { + ret = mbedtls_ssl_set_hostname(&ctx->https.mbedtls.ssl, + ctx->https.cert_host); + if (ret != 0) { + print_error("mbedtls_ssl_set_hostname returned -0x%x", + ret); + goto exit; + } + } +#endif + + NET_DBG("SSL setup done"); + + /* The HTTPS thread is started do initiate HTTPS handshake etc when + * the first HTTP request is being done. + */ + +exit: + return ret; +} + +static void https_handler(struct http_client_ctx *ctx, + struct k_sem *startup_sync) +{ + struct tx_fifo_block *tx_data; + struct http_client_request req; + size_t len; + int ret; + + /* First mbedtls specific initialization */ + ret = https_init(ctx); + + k_sem_give(startup_sync); + + if (ret < 0) { + return; + } + +reset: + http_parser_init(&ctx->parser, HTTP_RESPONSE); + ctx->rsp.data_len = 0; + + /* Wait that the sender sends the data, and the peer to respond to. + */ + tx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.tx_fifo, K_FOREVER); + if (tx_data) { + /* Because the req pointer might disappear as it is controlled + * by application, copy the data here. + */ + memcpy(&req, tx_data->req, sizeof(req)); + } else { + NET_ASSERT(tx_data); + goto reset; + } + + print_info(ctx, ctx->req.method); + + /* If the connection is not active, then re-connect */ + ret = tcp_connect(ctx); + if (ret < 0 && ret != -EALREADY) { + k_sem_give(&ctx->req.wait); + goto reset; + } + + mbedtls_ssl_session_reset(&ctx->https.mbedtls.ssl); + mbedtls_ssl_set_bio(&ctx->https.mbedtls.ssl, ctx, ssl_tx, + ssl_rx, NULL); + + /* SSL handshake. The ssl_rx() function will be called next by + * mbedtls library. The ssl_rx() will block and wait that data is + * received by ssl_received() and passed to it via fifo. After + * receiving the data, this function will then proceed with secure + * connection establishment. + */ + /* Waiting SSL handshake */ + do { + ret = mbedtls_ssl_handshake(&ctx->https.mbedtls.ssl); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + if (ret == MBEDTLS_ERR_SSL_CONN_EOF) { + goto close; + } + + if (ret < 0) { + print_error("mbedtls_ssl_handshake returned " + "-0x%x", ret); + goto close; + } + } + } while (ret != 0); + + ret = http_request(ctx, &req, BUF_ALLOC_TIMEOUT); + + k_mem_pool_free(&tx_data->block); + + if (ret < 0) { + NET_DBG("Send error (%d)", ret); + goto close; + } + + NET_DBG("Read HTTPS response"); + + do { + len = ctx->rsp.response_buf_len - 1; + memset(ctx->rsp.response_buf, 0, ctx->rsp.response_buf_len); + + ret = mbedtls_ssl_read(&ctx->https.mbedtls.ssl, + ctx->rsp.response_buf, len); + if (ret == 0) { + goto close; + } + + if (ret == MBEDTLS_ERR_SSL_WANT_READ || + ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + continue; + } + + if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + NET_DBG("Connection was closed gracefully"); + goto close; + } + + if (ret == MBEDTLS_ERR_NET_CONN_RESET) { + NET_DBG("Connection was reset by peer"); + goto close; + } + + if (ret == -EIO) { + NET_DBG("Response received, waiting another ctx %p", + ctx); + goto next; + } + + if (ret < 0) { + print_error("mbedtls_ssl_read returned -0x%x", ret); + goto close; + } + + /* The data_len will count how many bytes we have read, + * this value is passed to user supplied response callback + * by on_body() and on_message_complete() functions. + */ + ctx->rsp.data_len += ret; + + ret = http_parser_execute(&ctx->parser, + &ctx->settings, + ctx->rsp.response_buf, + ret); + if (!ret) { + goto close; + } + + ctx->rsp.data_len = 0; + + if (ret > 0) { + /* Get more data */ + ret = MBEDTLS_ERR_SSL_WANT_READ; + } + } while (ret < 0); + +close: + /* If there is any pending data that have not been processed yet, + * we need to free it here. + */ + if (ctx->https.mbedtls.ssl_ctx.rx_pkt) { + net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt); + ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL; + ctx->https.mbedtls.ssl_ctx.frag = NULL; + } + + NET_DBG("Resetting HTTPS connection %p", ctx); + + tcp_disconnect(ctx); + +next: + mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl); + + goto reset; +} + +static void https_shutdown(struct http_client_ctx *ctx) +{ + if (!ctx->https.tid) { + return; + } + + /* Empty the fifo just in case there is any received packets + * still there. + */ + while (1) { + struct rx_fifo_block *rx_data; + + rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo, + K_NO_WAIT); + if (!rx_data) { + break; + } + + net_pkt_unref(rx_data->pkt); + + k_mem_pool_free(&rx_data->block); + } + + k_fifo_cancel_wait(&ctx->https.mbedtls.ssl_ctx.rx_fifo); + + /* Let the ssl_rx() run if there is anything there waiting */ + k_yield(); + + mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl); + mbedtls_ssl_free(&ctx->https.mbedtls.ssl); + mbedtls_ssl_config_free(&ctx->https.mbedtls.conf); + mbedtls_ctr_drbg_free(&ctx->https.mbedtls.ctr_drbg); + mbedtls_entropy_free(&ctx->https.mbedtls.entropy); + +#if defined(MBEDTLS_X509_CRT_PARSE_C) + mbedtls_x509_crt_free(&ctx->https.mbedtls.ca_cert); +#endif + + tcp_disconnect(ctx); + + NET_DBG("HTTPS thread %p stopped for %p", ctx->https.tid, ctx); + + k_thread_abort(ctx->https.tid); + ctx->https.tid = 0; +} + +static int start_https(struct http_client_ctx *ctx) +{ + struct k_sem startup_sync; + + /* Start the thread that handles HTTPS traffic. */ + if (ctx->https.tid) { + return -EALREADY; + } + + NET_DBG("Starting HTTPS thread for %p", ctx); + + k_sem_init(&startup_sync, 0, 1); + + ctx->https.tid = k_thread_create(&ctx->https.thread, + ctx->https.stack, + ctx->https.stack_size, + (k_thread_entry_t)https_handler, + ctx, &startup_sync, 0, + K_PRIO_COOP(7), 0, 0); + + /* Wait until we know that the HTTPS thread startup was ok */ + if (k_sem_take(&startup_sync, HTTPS_STARTUP_TIMEOUT) < 0) { + https_shutdown(ctx); + return -ECANCELED; + } + + NET_DBG("HTTPS thread %p started for %p", ctx->https.tid, ctx); + + return 0; +} +#else +#define start_https(...) 0 +#endif /* CONFIG_HTTPS */ + int http_client_send_req(struct http_client_ctx *ctx, struct http_client_request *req, http_response_cb_t cb, @@ -590,12 +1241,18 @@ int http_client_send_req(struct http_client_ctx *ctx, return -EINVAL; } + ctx->rsp.response_buf = response_buf; + ctx->rsp.response_buf_len = response_buf_len; + client_reset(ctx); - ret = tcp_connect(ctx); - if (ret) { - NET_DBG("TCP connect error (%d)", ret); - return ret; + /* HTTPS connection is established in https_handler() */ + if (!ctx->is_https) { + ret = tcp_connect(ctx); + if (ret < 0 && ret != -EALREADY) { + NET_DBG("TCP connect error (%d)", ret); + return ret; + } } if (!req->host) { @@ -607,15 +1264,55 @@ int http_client_send_req(struct http_client_ctx *ctx, ctx->req.user_data = user_data; ctx->rsp.cb = cb; - ctx->rsp.response_buf = response_buf; - ctx->rsp.response_buf_len = response_buf_len; - print_info(ctx, ctx->req.method); +#if defined(CONFIG_HTTPS) + if (ctx->is_https) { + struct tx_fifo_block *tx_data; + struct k_mem_block block; - ret = http_request(ctx->tcp.ctx, req, BUF_ALLOC_TIMEOUT); - if (ret) { - NET_DBG("Send error (%d)", ret); - goto out; + ret = start_https(ctx); + if (ret != 0 && ret != -EALREADY) { + NET_ERR("HTTPS init failed (%d)", ret); + goto out; + } + + ret = k_mem_pool_alloc(ctx->https.pool, &block, + sizeof(struct tx_fifo_block), + BUF_ALLOC_TIMEOUT); + if (ret < 0) { + goto out; + } + + tx_data = block.data; + tx_data->req = req; + + memcpy(&tx_data->block, &block, sizeof(struct k_mem_block)); + + /* We need to pass the HTTPS request to HTTPS thread because + * of the mbedtls API stack size requirements. + */ + k_fifo_put(&ctx->https.mbedtls.ssl_ctx.tx_fifo, + (void *)tx_data); + + /* Let the https_handler() to start to process the message. + * + * Note that if the timeout > 0 or is K_FOREVER, then this + * yield is not really necessary as the k_sem_take() will + * let the https handler thread to run. But if the timeout + * is K_NO_WAIT, then we need to let the https handler to + * run now. + */ + k_yield(); + } else +#endif /* CONFIG_HTTPS */ + { + print_info(ctx, ctx->req.method); + + ret = http_request(ctx, req, BUF_ALLOC_TIMEOUT); + if (ret < 0) { + NET_DBG("Send error (%d)", ret); + goto out; + } } if (timeout != 0 && k_sem_take(&ctx->req.wait, timeout)) { @@ -843,19 +1540,167 @@ int http_client_init(struct http_client_ctx *ctx, ctx->tcp.receive_cb = http_receive_cb; ctx->tcp.timeout = HTTP_NETWORK_TIMEOUT; + ctx->tcp.send_data = net_context_send; + ctx->tcp.recv_cb = recv_cb; k_sem_init(&ctx->req.wait, 0, 1); return 0; } +#if defined(CONFIG_HTTPS) +/* This gets plain data and it sends encrypted one to peer */ +static int https_send(struct net_pkt *pkt, + net_context_send_cb_t cb, + s32_t timeout, + void *token, + void *user_data) +{ + struct http_client_ctx *ctx = user_data; + int ret; + u16_t len; + + if (!ctx->rsp.response_buf || ctx->rsp.response_buf_len == 0) { + NET_DBG("Response buf not setup"); + return -EINVAL; + } + + len = net_pkt_appdatalen(pkt); + if (len == 0) { + NET_DBG("No application data to send"); + return -EINVAL; + } + + ret = net_frag_linearize(ctx->rsp.response_buf, + ctx->rsp.response_buf_len, + pkt, + net_pkt_ip_hdr_len(pkt) + + net_pkt_ipv6_ext_opt_len(pkt), + len); + if (ret < 0) { + NET_DBG("Cannot linearize send data (%d)", ret); + return ret; + } + + if (ret != len) { + NET_DBG("Linear copy error (%u vs %d)", len, ret); + return -EINVAL; + } + + do { + ret = mbedtls_ssl_write(&ctx->https.mbedtls.ssl, + ctx->rsp.response_buf, len); + if (ret == MBEDTLS_ERR_NET_CONN_RESET) { + NET_ERR("peer closed the connection -0x%x", ret); + goto out; + } + + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + if (ret < 0) { + print_error("mbedtls_ssl_write returned -0x%x", + ret); + goto out; + } + } + } while (ret <= 0); + +out: + if (cb) { + cb(net_pkt_context(pkt), ret, token, user_data); + } + + return ret; +} + +int https_client_init(struct http_client_ctx *ctx, + const char *server, u16_t server_port, + u8_t *personalization_data, + size_t personalization_data_len, + https_ca_cert_cb_t cert_cb, + const char *cert_host, + https_entropy_src_cb_t entropy_src_cb, + struct k_mem_pool *pool, + u8_t *https_stack, + size_t https_stack_size) +{ + int ret; + + if (!cert_cb) { + NET_ERR("Cert callback must be set"); + return -EINVAL; + } + + memset(ctx, 0, sizeof(*ctx)); + + if (server) { + ret = set_remote_addr(ctx, server, server_port); + if (ret < 0) { + return ret; + } + + ctx->tcp.local.family = ctx->tcp.remote.family; + ctx->server = server; + } + + k_sem_init(&ctx->req.wait, 0, 1); + + ctx->is_https = true; + + ctx->settings.on_body = on_body; + ctx->settings.on_chunk_complete = on_chunk_complete; + ctx->settings.on_chunk_header = on_chunk_header; + ctx->settings.on_headers_complete = on_headers_complete; + ctx->settings.on_header_field = on_header_field; + ctx->settings.on_header_value = on_header_value; + ctx->settings.on_message_begin = on_message_begin; + ctx->settings.on_message_complete = on_message_complete; + ctx->settings.on_status = on_status; + ctx->settings.on_url = on_url; + + ctx->tcp.timeout = HTTP_NETWORK_TIMEOUT; + ctx->tcp.send_data = https_send; + ctx->tcp.recv_cb = ssl_received; + + ctx->https.cert_host = cert_host; + ctx->https.stack = https_stack; + ctx->https.stack_size = https_stack_size; + ctx->https.mbedtls.cert_cb = cert_cb; + ctx->https.pool = pool; + ctx->https.mbedtls.personalization_data = personalization_data; + ctx->https.mbedtls.personalization_data_len = personalization_data_len; + + if (entropy_src_cb) { + ctx->https.mbedtls.entropy_src_cb = entropy_src_cb; + } else { + ctx->https.mbedtls.entropy_src_cb = entropy_source; + } + + /* The mbedtls is initialized in HTTPS thread because of mbedtls stack + * requirements. + */ + return 0; +} +#endif /* CONFIG_HTTPS */ + void http_client_release(struct http_client_ctx *ctx) { if (!ctx) { return; } - net_context_put(ctx->tcp.ctx); +#if defined(CONFIG_HTTPS) + if (ctx->is_https) { + https_shutdown(ctx); + } +#endif /* CONFIG_HTTPS */ + + /* https_shutdown() might have released the context already */ + if (ctx->tcp.ctx) { + net_context_put(ctx->tcp.ctx); + ctx->tcp.ctx = NULL; + } + ctx->tcp.receive_cb = NULL; ctx->rsp.cb = NULL; k_sem_give(&ctx->req.wait);