Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server certificate selection callback #5454

Merged
merged 6 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ChangeLog.d/mbedtls_ssl_cert_cb.txt
Original file line number Diff line number Diff line change
@@ -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.
59 changes: 54 additions & 5 deletions include/mbedtls/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
mpg marked this conversation as resolved.
Show resolved Hide resolved
* \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
*
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions library/ssl_misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 20 additions & 4 deletions library/ssl_srv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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 )
mpg marked this conversation as resolved.
Show resolved Hide resolved
break;

ret = ssl_parse_servername_ext( ssl, ext + 4, ext_size );
if( ret != 0 )
return( ret );
Expand Down Expand Up @@ -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 );
paul-elliott-arm marked this conversation as resolved.
Show resolved Hide resolved
}
#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;
Expand Down
63 changes: 37 additions & 26 deletions library/ssl_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1291,13 +1299,33 @@ 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,
mbedtls_pk_context *key )
{
mbedtls_ssl_key_cert *new_cert;

if( cert == NULL )
mpg marked this conversation as resolved.
Show resolved Hide resolved
{
/* 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 );
Expand All @@ -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;
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
47 changes: 40 additions & 7 deletions programs/ssl/ssl_server2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
mpg marked this conversation as resolved.
Show resolved Hide resolved
* 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;
Expand All @@ -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)
Expand Down Expand Up @@ -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 )
{
Expand Down
2 changes: 0 additions & 2 deletions tests/ssl-opt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"

Expand Down
9 changes: 9 additions & 0 deletions tests/suites/test_suite_ssl.function
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down