diff --git a/ChangeLog.d/mbedtls_ssl_cert_cb.txt b/ChangeLog.d/mbedtls_ssl_cert_cb.txt new file mode 100644 index 000000000000..fcdc23cf0c19 --- /dev/null +++ b/ChangeLog.d/mbedtls_ssl_cert_cb.txt @@ -0,0 +1,7 @@ +Features + * Add server certificate selection callback near end of Client Hello. + Register callback with mbedtls_ssl_conf_cert_cb(). + * Provide mechanism to reset handshake cert list by calling + mbedtls_ssl_set_hs_own_cert() with NULL value for own_cert param. + * Add accessor mbedtls_ssl_get_hs_sni() to retrieve SNI from within + cert callback (mbedtls_ssl_conf_cert_cb()) during handshake. diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index 7544f42637f0..b819bbad8f7a 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -1475,6 +1475,10 @@ struct mbedtls_ssl_config * access it afterwards. */ mbedtls_ssl_user_data_t MBEDTLS_PRIVATE(user_data); + +#if defined(MBEDTLS_SSL_SRV_C) + int (*MBEDTLS_PRIVATE(f_cert_cb))(mbedtls_ssl_context *); /*!< certificate selection callback */ +#endif /* MBEDTLS_SSL_SRV_C */ }; struct mbedtls_ssl_context @@ -2220,6 +2224,28 @@ void mbedtls_ssl_set_timer_cb( mbedtls_ssl_context *ssl, mbedtls_ssl_set_timer_t *f_set_timer, mbedtls_ssl_get_timer_t *f_get_timer ); +#if defined(MBEDTLS_SSL_SRV_C) +/** + * \brief Set the certificate selection callback (server-side only). + * + * If set, the callback is always called for each handshake, + * after `ClientHello` processing has finished. + * + * The callback has the following parameters: + * - \c mbedtls_ssl_context*: The SSL context to which + * the operation applies. + * The return value of the callback is 0 if successful, + * or a specific MBEDTLS_ERR_XXX code, which will cause + * the handshake to be aborted. + * + * \param conf The SSL configuration to register the callback with. + * \param f_cert_cb The callback for selecting server certificate after + * `ClientHello` processing has finished. + */ +void mbedtls_ssl_conf_cert_cb( mbedtls_ssl_config *conf, + int (*f_cert_cb)(mbedtls_ssl_context *) ); +#endif /* MBEDTLS_SSL_SRV_C */ + /** * \brief Callback type: generate and write session ticket * @@ -3514,11 +3540,35 @@ int mbedtls_ssl_set_hostname( mbedtls_ssl_context *ssl, const char *hostname ); #endif /* MBEDTLS_X509_CRT_PARSE_C */ #if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) +/** + * \brief Retrieve SNI extension value for the current handshake. + * Available in \p f_cert_cb of \c mbedtls_ssl_conf_cert_cb(), + * this is the same value passed to \p f_sni callback of + * \c mbedtls_ssl_conf_sni() and may be used instead of + * \c mbedtls_ssl_conf_sni(). + * + * \param ssl SSL context + * \param name_len pointer into which to store length of returned value. + * 0 if SNI extension is not present or not yet processed. + * + * \return const pointer to SNI extension value. + * - value is valid only when called in \p f_cert_cb + * registered with \c mbedtls_ssl_conf_cert_cb(). + * - value is NULL if SNI extension is not present. + * - value is not '\0'-terminated. Use \c name_len for len. + * - value must not be freed. + */ +const unsigned char *mbedtls_ssl_get_hs_sni( mbedtls_ssl_context *ssl, + size_t *name_len ); + /** * \brief Set own certificate and key for the current handshake * * \note Same as \c mbedtls_ssl_conf_own_cert() but for use within - * the SNI callback. + * the SNI callback or the certificate selection callback. + * + * \note Passing null \c own_cert clears the certificate list for + * the current handshake. * * \param ssl SSL context * \param own_cert own public certificate chain @@ -3535,7 +3585,7 @@ int mbedtls_ssl_set_hs_own_cert( mbedtls_ssl_context *ssl, * current handshake * * \note Same as \c mbedtls_ssl_conf_ca_chain() but for use within - * the SNI callback. + * the SNI callback or the certificate selection callback. * * \param ssl SSL context * \param ca_chain trusted CA chain (meaning all fully trusted top-level CAs) @@ -3549,7 +3599,7 @@ void mbedtls_ssl_set_hs_ca_chain( mbedtls_ssl_context *ssl, * \brief Set authmode for the current handshake. * * \note Same as \c mbedtls_ssl_conf_authmode() but for use within - * the SNI callback. + * the SNI callback or the certificate selection callback. * * \param ssl SSL context * \param authmode MBEDTLS_SSL_VERIFY_NONE, MBEDTLS_SSL_VERIFY_OPTIONAL or @@ -3574,8 +3624,7 @@ void mbedtls_ssl_set_hs_authmode( mbedtls_ssl_context *ssl, * mbedtls_ssl_set_hs_ca_chain() as well as the client * authentication mode with \c mbedtls_ssl_set_hs_authmode(), * then must return 0. If no matching name is found, the - * callback must either set a default cert, or - * return non-zero to abort the handshake at this point. + * callback may return non-zero to abort the handshake. * * \param conf SSL configuration * \param f_sni verification function diff --git a/library/ssl_misc.h b/library/ssl_misc.h index 0656439678d8..4ecf9155b215 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -849,6 +849,11 @@ struct mbedtls_ssl_handshake_params * The library does not use it internally. */ void *user_async_ctx; #endif /* MBEDTLS_SSL_ASYNC_PRIVATE */ + +#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) + const unsigned char *sni_name; /*!< raw SNI */ + size_t sni_name_len; /*!< raw SNI len */ +#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */ }; typedef struct mbedtls_ssl_hs_buffer mbedtls_ssl_hs_buffer; diff --git a/library/ssl_srv.c b/library/ssl_srv.c index e9febfd84393..c757ac803d31 100644 --- a/library/ssl_srv.c +++ b/library/ssl_srv.c @@ -118,6 +118,11 @@ static int ssl_parse_servername_ext( mbedtls_ssl_context *ssl, if( p[0] == MBEDTLS_TLS_EXT_SERVERNAME_HOSTNAME ) { + ssl->handshake->sni_name = p + 3; + ssl->handshake->sni_name_len = hostname_len; + if( ssl->conf->f_sni == NULL ) + return( 0 ); + ret = ssl->conf->f_sni( ssl->conf->p_sni, ssl, p + 3, hostname_len ); if( ret != 0 ) @@ -1643,9 +1648,6 @@ static int ssl_parse_client_hello( mbedtls_ssl_context *ssl ) #if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) case MBEDTLS_TLS_EXT_SERVERNAME: MBEDTLS_SSL_DEBUG_MSG( 3, ( "found ServerName extension" ) ); - if( ssl->conf->f_sni == NULL ) - break; - ret = ssl_parse_servername_ext( ssl, ext + 4, ext_size ); if( ret != 0 ) return( ret ); @@ -1870,10 +1872,24 @@ static int ssl_parse_client_hello( mbedtls_ssl_context *ssl ) return( MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE ); } + /* + * Server certification selection (after processing TLS extensions) + */ + if( ssl->conf->f_cert_cb && ( ret = ssl->conf->f_cert_cb( ssl ) ) != 0 ) + { + MBEDTLS_SSL_DEBUG_RET( 1, "f_cert_cb", ret ); + return( ret ); + } +#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) + ssl->handshake->sni_name = NULL; + ssl->handshake->sni_name_len = 0; +#endif + /* * Search for a matching ciphersuite * (At the end because we need information from the EC-based extensions - * and certificate from the SNI callback triggered by the SNI extension.) + * and certificate from the SNI callback triggered by the SNI extension + * or certificate from server certificate selection callback.) */ got_common_suite = 0; ciphersuites = ssl->conf->ciphersuite_list; diff --git a/library/ssl_tls.c b/library/ssl_tls.c index adb18ab6c2f4..2220721f1e2e 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -1232,6 +1232,14 @@ void mbedtls_ssl_set_timer_cb( mbedtls_ssl_context *ssl, mbedtls_ssl_set_timer( ssl, 0 ); } +#if defined(MBEDTLS_SSL_SRV_C) +void mbedtls_ssl_conf_cert_cb( mbedtls_ssl_config *conf, + int (*f_cert_cb)(mbedtls_ssl_context *) ) +{ + conf->f_cert_cb = f_cert_cb; +} +#endif /* MBEDTLS_SSL_SRV_C */ + #if defined(MBEDTLS_SSL_SRV_C) void mbedtls_ssl_conf_session_cache( mbedtls_ssl_config *conf, void *p_cache, @@ -1291,6 +1299,18 @@ void mbedtls_ssl_conf_cert_profile( mbedtls_ssl_config *conf, conf->cert_profile = profile; } +static void ssl_key_cert_free( mbedtls_ssl_key_cert *key_cert ) +{ + mbedtls_ssl_key_cert *cur = key_cert, *next; + + while( cur != NULL ) + { + next = cur->next; + mbedtls_free( cur ); + cur = next; + } +} + /* Append a new keycert entry to a (possibly empty) list */ static int ssl_append_key_cert( mbedtls_ssl_key_cert **head, mbedtls_x509_crt *cert, @@ -1298,6 +1318,14 @@ static int ssl_append_key_cert( mbedtls_ssl_key_cert **head, { mbedtls_ssl_key_cert *new_cert; + if( cert == NULL ) + { + /* Free list if cert is null */ + ssl_key_cert_free( *head ); + *head = NULL; + return( 0 ); + } + new_cert = mbedtls_calloc( 1, sizeof( mbedtls_ssl_key_cert ) ); if( new_cert == NULL ) return( MBEDTLS_ERR_SSL_ALLOC_FAILED ); @@ -1306,7 +1334,7 @@ static int ssl_append_key_cert( mbedtls_ssl_key_cert **head, new_cert->key = key; new_cert->next = NULL; - /* Update head is the list was null, else add to the end */ + /* Update head if the list was null, else add to the end */ if( *head == NULL ) { *head = new_cert; @@ -1361,6 +1389,13 @@ void mbedtls_ssl_conf_ca_cb( mbedtls_ssl_config *conf, #endif /* MBEDTLS_X509_CRT_PARSE_C */ #if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) +const unsigned char *mbedtls_ssl_get_hs_sni( mbedtls_ssl_context *ssl, + size_t *name_len ) +{ + *name_len = ssl->handshake->sni_name_len; + return( ssl->handshake->sni_name ); +} + int mbedtls_ssl_set_hs_own_cert( mbedtls_ssl_context *ssl, mbedtls_x509_crt *own_cert, mbedtls_pk_context *pk_key ) @@ -2941,20 +2976,6 @@ int mbedtls_ssl_renegotiate( mbedtls_ssl_context *ssl ) } #endif /* MBEDTLS_SSL_RENEGOTIATION */ -#if defined(MBEDTLS_X509_CRT_PARSE_C) -static void ssl_key_cert_free( mbedtls_ssl_key_cert *key_cert ) -{ - mbedtls_ssl_key_cert *cur = key_cert, *next; - - while( cur != NULL ) - { - next = cur->next; - mbedtls_free( cur ); - cur = next; - } -} -#endif /* MBEDTLS_X509_CRT_PARSE_C */ - void mbedtls_ssl_handshake_free( mbedtls_ssl_context *ssl ) { mbedtls_ssl_handshake_params *handshake = ssl->handshake; @@ -3042,17 +3063,7 @@ void mbedtls_ssl_handshake_free( mbedtls_ssl_context *ssl ) * Free only the linked list wrapper, not the keys themselves * since the belong to the SNI callback */ - if( handshake->sni_key_cert != NULL ) - { - mbedtls_ssl_key_cert *cur = handshake->sni_key_cert, *next; - - while( cur != NULL ) - { - next = cur->next; - mbedtls_free( cur ); - cur = next; - } - } + ssl_key_cert_free( handshake->sni_key_cert ); #endif /* MBEDTLS_X509_CRT_PARSE_C && MBEDTLS_SSL_SERVER_NAME_INDICATION */ #if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED) diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index 595300e85593..802078c23b5b 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -823,18 +823,23 @@ int sni_callback( void *p_info, mbedtls_ssl_context *ssl, { const sni_entry *cur = (const sni_entry *) p_info; + /* preserve behavior which checks for SNI match in sni_callback() for + * the benefits of tests using sni_callback(), even though the actual + * certificate assignment has moved to certificate selection callback + * in this application. This exercises sni_callback and cert_callback + * even though real applications might choose to do this differently. + * Application might choose to save name and name_len in user_data for + * later use in certificate selection callback. + */ while( cur != NULL ) { if( name_len == strlen( cur->name ) && memcmp( name, cur->name, name_len ) == 0 ) { - if( cur->ca != NULL ) - mbedtls_ssl_set_hs_ca_chain( ssl, cur->ca, cur->crl ); - - if( cur->authmode != DFL_AUTH_MODE ) - mbedtls_ssl_set_hs_authmode( ssl, cur->authmode ); - - return( mbedtls_ssl_set_hs_own_cert( ssl, cur->cert, cur->key ) ); + void *p; + *(const void **)&p = cur; + mbedtls_ssl_set_user_data_p( ssl, p ); + return( 0 ); } cur = cur->next; @@ -843,6 +848,33 @@ int sni_callback( void *p_info, mbedtls_ssl_context *ssl, return( -1 ); } +/* + * server certificate selection callback. + */ +int cert_callback( mbedtls_ssl_context *ssl ) +{ + const sni_entry *cur = (sni_entry *) mbedtls_ssl_get_user_data_p( ssl ); + if( cur != NULL ) + { + /*(exercise mbedtls_ssl_get_hs_sni(); not otherwise used here)*/ + size_t name_len; + const unsigned char *name = mbedtls_ssl_get_hs_sni( ssl, &name_len ); + if( strlen( cur->name ) != name_len || + memcmp( cur->name, name, name_len ) != 0 ) + return( MBEDTLS_ERR_SSL_DECODE_ERROR ); + + if( cur->ca != NULL ) + mbedtls_ssl_set_hs_ca_chain( ssl, cur->ca, cur->crl ); + + if( cur->authmode != DFL_AUTH_MODE ) + mbedtls_ssl_set_hs_authmode( ssl, cur->authmode ); + + return( mbedtls_ssl_set_hs_own_cert( ssl, cur->cert, cur->key ) ); + } + + return( 0 ); +} + #endif /* SNI_OPTION */ #if defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED) @@ -2923,6 +2955,7 @@ int main( int argc, char *argv[] ) if( opt.sni != NULL ) { mbedtls_ssl_conf_sni( &conf, sni_callback, sni_info ); + mbedtls_ssl_conf_cert_cb( &conf, cert_callback ); #if defined(MBEDTLS_SSL_ASYNC_PRIVATE) if( opt.async_private_delay2 >= 0 ) { diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 2fe7a4016f8c..a0d007f3f549 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -5025,7 +5025,6 @@ run_test "SNI: no SNI callback" \ crt_file=data_files/server5.crt key_file=data_files/server5.key" \ "$P_CLI server_name=localhost" \ 0 \ - -S "parse ServerName extension" \ -c "issuer name *: C=NL, O=PolarSSL, CN=Polarssl Test EC CA" \ -c "subject name *: C=NL, O=PolarSSL, CN=localhost" @@ -5175,7 +5174,6 @@ run_test "SNI: DTLS, no SNI callback" \ crt_file=data_files/server5.crt key_file=data_files/server5.key" \ "$P_CLI server_name=localhost dtls=1" \ 0 \ - -S "parse ServerName extension" \ -c "issuer name *: C=NL, O=PolarSSL, CN=Polarssl Test EC CA" \ -c "subject name *: C=NL, O=PolarSSL, CN=localhost" diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 4f5ee9762d2d..0c8928f16a53 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -854,6 +854,15 @@ int mbedtls_endpoint_certificate_init( mbedtls_endpoint *ep, int pk_alg ) ret = mbedtls_ssl_conf_own_cert( &( ep->conf ), &( cert->cert ), &( cert->pkey ) ); TEST_ASSERT( ret == 0 ); + TEST_ASSERT( ep->conf.key_cert != NULL ); + + ret = mbedtls_ssl_conf_own_cert( &( ep->conf ), NULL, NULL ); + TEST_ASSERT( ret == 0 ); + TEST_ASSERT( ep->conf.key_cert == NULL ); + + ret = mbedtls_ssl_conf_own_cert( &( ep->conf ), &( cert->cert ), + &( cert->pkey ) ); + TEST_ASSERT( ret == 0 ); exit: if( ret != 0 )