From e9e3dc790443429c7ccb74f967b5f6d908ae41af Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Mon, 26 Sep 2022 16:14:09 +0200 Subject: [PATCH] esp-tls: Add support for the CERTIFICATE SELECTION HOOK. The hook has access to required information so that the application can make a more informed decision on which certificate to serve (such as alpn value, server certificate type, etc.) Closes https://github.com/espressif/esp-idf/pull/9833 Signed-off-by: Aditya Patwardhan --- components/esp-tls/Kconfig | 8 ++ components/esp-tls/esp_tls.h | 22 ++++ components/esp-tls/esp_tls_mbedtls.c | 19 ++++ .../include/esp_https_server.h | 5 + .../esp_https_server/src/https_server.c | 102 ++++++++++++------ 5 files changed, 123 insertions(+), 33 deletions(-) diff --git a/components/esp-tls/Kconfig b/components/esp-tls/Kconfig index afd38d22a9c..6c0d7586bc4 100644 --- a/components/esp-tls/Kconfig +++ b/components/esp-tls/Kconfig @@ -57,6 +57,14 @@ menu "ESP-TLS" help Sets the session ticket timeout used in the tls server. + config ESP_TLS_SERVER_CERT_SELECT_HOOK + bool "Certificate selection hook" + depends on ESP_TLS_USING_MBEDTLS + help + Ability to configure and use a certificate selection callback during server handshake, + to select a certificate to present to the client based on the TLS extensions supplied in + the client hello (alpn, sni, etc). + config ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL bool "ESP-TLS Server: Set minimum Certificate Verification mode to Optional" depends on ESP_TLS_SERVER && ESP_TLS_USING_MBEDTLS diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h index 24680b48d41..d018824f136 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -197,6 +197,20 @@ typedef struct esp_tls_server_session_ticket_ctx { } esp_tls_server_session_ticket_ctx_t; #endif + +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) +/** + * @brief tls handshake callback + * Can be used to configure per-handshake attributes for the TLS connection. + * E.g. Client certificate / Key, Authmode, Client CA verification, etc. + * + * @param ssl mbedtls_ssl_context that can be used for changing settings + * @return The reutn value of the callback must be 0 if successful, + * or a specific MBEDTLS_ERR_XXX code, which will cause the handhsake to abort + */ +typedef mbedtls_ssl_hs_cb_t esp_tls_handshake_callback; +#endif + typedef struct esp_tls_cfg_server { const char **alpn_protos; /*!< Application protocols required for HTTP2. If HTTP2/ALPN support is required, a list @@ -259,6 +273,14 @@ typedef struct esp_tls_cfg_server { Call esp_tls_cfg_server_session_tickets_free to free the data associated with this context. */ #endif + +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + esp_tls_handshake_callback cert_select_cb; /*!< Certificate selection callback that gets called after ClientHello is processed. + Can be used as an SNI callback, but also has access to other + TLS extensions, such as ALPN and server_certificate_type . */ +#endif + + void *userdata; /*!< User data to be add to the ssl context. Can be retrieved by callbacks */ } esp_tls_cfg_server_t; /** diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index 53ff9e65393..4bd0e5852dd 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -512,12 +512,21 @@ esp_err_t set_server_config(esp_tls_cfg_server_t *cfg, esp_tls_t *tls) return ESP_ERR_MBEDTLS_SSL_CONFIG_DEFAULTS_FAILED; } + mbedtls_ssl_conf_set_user_data_p(&tls->conf, cfg->userdata); + #ifdef CONFIG_MBEDTLS_SSL_ALPN if (cfg->alpn_protos) { mbedtls_ssl_conf_alpn_protocols(&tls->conf, cfg->alpn_protos); } #endif +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + if (cfg->cert_select_cb != NULL) { + ESP_LOGI(TAG, "Initializing server side certificate selection callback"); + mbedtls_ssl_conf_cert_cb(&tls->conf, cfg->cert_select_cb); + } +#endif + if (cfg->cacert_buf != NULL) { esp_ret = set_ca_cert(tls, cfg->cacert_buf, cfg->cacert_bytes); if (esp_ret != ESP_OK) { @@ -569,7 +578,16 @@ esp_err_t set_server_config(esp_tls_cfg_server_t *cfg, esp_tls_t *tls) return esp_ret; } } else { +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + if (cfg->cert_select_cb == NULL) { + ESP_LOGE(TAG, "Missing server certificate and/or key and no certificate selection callback is defined"); + } else { + ESP_LOGD(TAG, "Missing server certificate and/or key, but certificate selection callback is defined. Callback MUST ALWAYS call mbedtls_ssl_set_hs_own_cert, or the handshake will abort!"); + return ESP_OK; + } +#else ESP_LOGE(TAG, "Missing server certificate and/or key"); +#endif return ESP_ERR_INVALID_STATE; } @@ -790,6 +808,7 @@ int esp_mbedtls_server_session_create(esp_tls_cfg_server_t *cfg, int sockfd, esp tls->conn_state = ESP_TLS_FAIL; return -1; } + tls->read = esp_mbedtls_read; tls->write = esp_mbedtls_write; int ret; diff --git a/components/esp_https_server/include/esp_https_server.h b/components/esp_https_server/include/esp_https_server.h index 9fbfe4c2506..d224a8fa05b 100644 --- a/components/esp_https_server/include/esp_https_server.h +++ b/components/esp_https_server/include/esp_https_server.h @@ -96,6 +96,11 @@ struct httpd_ssl_config { /** User callback for esp_https_server */ esp_https_server_user_cb *user_cb; + +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + esp_tls_handshake_callback cert_select_cb; /*!< Certificate selection callback to use */ +#endif + void *ssl_userdata; /*!< user data to add to the ssl context */ }; typedef struct httpd_ssl_config httpd_ssl_config_t; diff --git a/components/esp_https_server/src/https_server.c b/components/esp_https_server/src/https_server.c index 50f581cae14..bfb4ed44672 100644 --- a/components/esp_https_server/src/https_server.c +++ b/components/esp_https_server/src/https_server.c @@ -200,65 +200,101 @@ static httpd_ssl_ctx_t *create_secure_context(const struct httpd_ssl_config *con } esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)calloc(1, sizeof(esp_tls_cfg_server_t)); if (!cfg) { - free(ssl_ctx); - return NULL; + goto free_ssl_ctx; } if (config->session_tickets) { if ( esp_tls_cfg_server_session_tickets_init(cfg) != ESP_OK ) { ESP_LOGE(TAG, "Failed to init session ticket support"); - free(ssl_ctx); - free(cfg); - return NULL; + goto free_cfg; } } + cfg->userdata = config->ssl_userdata; + +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + cfg->cert_select_cb = config->cert_select_cb; +#endif + ssl_ctx->tls_cfg = cfg; ssl_ctx->user_cb = config->user_cb; /* cacert = CA which signs client cert, or client cert itself */ - if(config->cacert_pem != NULL) { + if (config->cacert_pem != NULL && config->cacert_len > 0) { cfg->cacert_buf = (unsigned char *)malloc(config->cacert_len); - if (!cfg->cacert_buf) { - ESP_LOGE(TAG, "Could not allocate memory"); - free(cfg); - free(ssl_ctx); - return NULL; + + if (cfg->cacert_buf) { + memcpy((char *) cfg->cacert_buf, config->cacert_pem, config->cacert_len); + cfg->cacert_bytes = config->cacert_len; + } else { + ESP_LOGE(TAG, "Could not allocate memory for client certificate authority"); + goto free_cfg; } - memcpy((char *)cfg->cacert_buf, config->cacert_pem, config->cacert_len); - cfg->cacert_bytes = config->cacert_len; } /* servercert = cert of server itself */ - cfg->servercert_buf = (unsigned char *)malloc(config->servercert_len); - if (!cfg->servercert_buf) { - ESP_LOGE(TAG, "Could not allocate memory"); - free((void *)cfg->cacert_buf); - free(cfg); - free(ssl_ctx); - return NULL; + if (config->servercert != NULL && config->servercert_len > 0) { + cfg->servercert_buf = (unsigned char *)malloc(config->servercert_len); + + if (cfg->servercert_buf) { + memcpy((char *) cfg->servercert_buf, config->servercert, config->servercert_len); + cfg->servercert_bytes = config->servercert_len; + } else { + ESP_LOGE(TAG, "Could not allocate memory for server certificate"); + goto free_cacert; + } + } else { +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + if (config->cert_select_cb == NULL) { +#endif + ESP_LOGE(TAG, "No Server certificate supplied"); + goto free_cacert; +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + } else { + ESP_LOGW(TAG, "Server certificate not supplied, make sure to supply it in the certificate selection hook!"); + } +#endif } - memcpy((char *)cfg->servercert_buf, config->servercert, config->servercert_len); - cfg->servercert_bytes = config->servercert_len; /* Pass on secure element boolean */ cfg->use_secure_element = config->use_secure_element; if (!cfg->use_secure_element) { - cfg->serverkey_buf = (unsigned char *)malloc(config->prvtkey_len); - if (!cfg->serverkey_buf) { - ESP_LOGE(TAG, "Could not allocate memory"); - free((void *)cfg->servercert_buf); - free((void *)cfg->cacert_buf); - free(cfg); - free(ssl_ctx); - return NULL; + if (config->prvtkey_pem != NULL && config->prvtkey_len > 0) { + cfg->serverkey_buf = (unsigned char *) malloc(config->prvtkey_len); + + if (cfg->serverkey_buf) { + memcpy((char *) cfg->serverkey_buf, config->prvtkey_pem, config->prvtkey_len); + cfg->serverkey_bytes = config->prvtkey_len; + } else { + ESP_LOGE(TAG, "Could not allocate memory for server key"); + goto free_servercert; + } + } else { +#if defined(CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK) + if (config->cert_select_cb == NULL) { + ESP_LOGE(TAG, "No Server key supplied and no certificate selection hook is present"); + goto free_servercert; + } else { + ESP_LOGW(TAG, "Server key not supplied, make sure to supply it in the certificate selection hook"); + } +#else + ESP_LOGE(TAG, "No Server key supplied"); + goto free_servercert; +#endif } } - memcpy((char *)cfg->serverkey_buf, config->prvtkey_pem, config->prvtkey_len); - cfg->serverkey_bytes = config->prvtkey_len; - return ssl_ctx; + +free_servercert: + free((void *) cfg->servercert_buf); +free_cacert: + free((void *) cfg->cacert_buf); +free_cfg: + free(cfg); +free_ssl_ctx: + free(ssl_ctx); + return NULL; } /** Start the server */