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 <aditya.patwardhan@espressif.com>
This commit is contained in:
Akos Vandra 2022-09-26 16:14:09 +02:00 committed by BOT
parent 3c18cc482c
commit e9e3dc7904
5 changed files with 123 additions and 33 deletions

View File

@ -57,6 +57,14 @@ menu "ESP-TLS"
help help
Sets the session ticket timeout used in the tls server. 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 config ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL
bool "ESP-TLS Server: Set minimum Certificate Verification mode to Optional" bool "ESP-TLS Server: Set minimum Certificate Verification mode to Optional"
depends on ESP_TLS_SERVER && ESP_TLS_USING_MBEDTLS depends on ESP_TLS_SERVER && ESP_TLS_USING_MBEDTLS

View File

@ -197,6 +197,20 @@ typedef struct esp_tls_server_session_ticket_ctx {
} esp_tls_server_session_ticket_ctx_t; } esp_tls_server_session_ticket_ctx_t;
#endif #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 { typedef struct esp_tls_cfg_server {
const char **alpn_protos; /*!< Application protocols required for HTTP2. const char **alpn_protos; /*!< Application protocols required for HTTP2.
If HTTP2/ALPN support is required, a list 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 Call esp_tls_cfg_server_session_tickets_free
to free the data associated with this context. */ to free the data associated with this context. */
#endif #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; } esp_tls_cfg_server_t;
/** /**

View File

@ -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; return ESP_ERR_MBEDTLS_SSL_CONFIG_DEFAULTS_FAILED;
} }
mbedtls_ssl_conf_set_user_data_p(&tls->conf, cfg->userdata);
#ifdef CONFIG_MBEDTLS_SSL_ALPN #ifdef CONFIG_MBEDTLS_SSL_ALPN
if (cfg->alpn_protos) { if (cfg->alpn_protos) {
mbedtls_ssl_conf_alpn_protocols(&tls->conf, cfg->alpn_protos); mbedtls_ssl_conf_alpn_protocols(&tls->conf, cfg->alpn_protos);
} }
#endif #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) { if (cfg->cacert_buf != NULL) {
esp_ret = set_ca_cert(tls, cfg->cacert_buf, cfg->cacert_bytes); esp_ret = set_ca_cert(tls, cfg->cacert_buf, cfg->cacert_bytes);
if (esp_ret != ESP_OK) { 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; return esp_ret;
} }
} else { } 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"); ESP_LOGE(TAG, "Missing server certificate and/or key");
#endif
return ESP_ERR_INVALID_STATE; 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; tls->conn_state = ESP_TLS_FAIL;
return -1; return -1;
} }
tls->read = esp_mbedtls_read; tls->read = esp_mbedtls_read;
tls->write = esp_mbedtls_write; tls->write = esp_mbedtls_write;
int ret; int ret;

View File

@ -96,6 +96,11 @@ struct httpd_ssl_config {
/** User callback for esp_https_server */ /** User callback for esp_https_server */
esp_https_server_user_cb *user_cb; 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; typedef struct httpd_ssl_config httpd_ssl_config_t;

View File

@ -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)); esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)calloc(1, sizeof(esp_tls_cfg_server_t));
if (!cfg) { if (!cfg) {
free(ssl_ctx); goto free_ssl_ctx;
return NULL;
} }
if (config->session_tickets) { if (config->session_tickets) {
if ( esp_tls_cfg_server_session_tickets_init(cfg) != ESP_OK ) { if ( esp_tls_cfg_server_session_tickets_init(cfg) != ESP_OK ) {
ESP_LOGE(TAG, "Failed to init session ticket support"); ESP_LOGE(TAG, "Failed to init session ticket support");
free(ssl_ctx); goto free_cfg;
free(cfg);
return NULL;
} }
} }
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->tls_cfg = cfg;
ssl_ctx->user_cb = config->user_cb; ssl_ctx->user_cb = config->user_cb;
/* cacert = CA which signs client cert, or client cert itself */ /* 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); cfg->cacert_buf = (unsigned char *)malloc(config->cacert_len);
if (!cfg->cacert_buf) {
ESP_LOGE(TAG, "Could not allocate memory"); if (cfg->cacert_buf) {
free(cfg); memcpy((char *) cfg->cacert_buf, config->cacert_pem, config->cacert_len);
free(ssl_ctx); cfg->cacert_bytes = config->cacert_len;
return NULL; } 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 */ /* servercert = cert of server itself */
cfg->servercert_buf = (unsigned char *)malloc(config->servercert_len); if (config->servercert != NULL && config->servercert_len > 0) {
if (!cfg->servercert_buf) { cfg->servercert_buf = (unsigned char *)malloc(config->servercert_len);
ESP_LOGE(TAG, "Could not allocate memory");
free((void *)cfg->cacert_buf); if (cfg->servercert_buf) {
free(cfg); memcpy((char *) cfg->servercert_buf, config->servercert, config->servercert_len);
free(ssl_ctx); cfg->servercert_bytes = config->servercert_len;
return NULL; } 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 */ /* Pass on secure element boolean */
cfg->use_secure_element = config->use_secure_element; cfg->use_secure_element = config->use_secure_element;
if (!cfg->use_secure_element) { if (!cfg->use_secure_element) {
cfg->serverkey_buf = (unsigned char *)malloc(config->prvtkey_len); if (config->prvtkey_pem != NULL && config->prvtkey_len > 0) {
if (!cfg->serverkey_buf) { cfg->serverkey_buf = (unsigned char *) malloc(config->prvtkey_len);
ESP_LOGE(TAG, "Could not allocate memory");
free((void *)cfg->servercert_buf); if (cfg->serverkey_buf) {
free((void *)cfg->cacert_buf); memcpy((char *) cfg->serverkey_buf, config->prvtkey_pem, config->prvtkey_len);
free(cfg); cfg->serverkey_bytes = config->prvtkey_len;
free(ssl_ctx); } else {
return NULL; 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; 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 */ /** Start the server */