diff --git a/include/mbedtls/config.h b/include/mbedtls/config.h index 92e1b62aaf..8e00fc477a 100644 --- a/include/mbedtls/config.h +++ b/include/mbedtls/config.h @@ -1350,6 +1350,20 @@ */ #define MBEDTLS_SSL_ALL_ALERT_MESSAGES +/** + * \def MBEDTLS_SSL_RECORD_CHECKING + * + * Enable the function mbedtls_ssl_check_record() which can be used to check + * the validity and authenticity of an incoming record, to verify that it has + * not been seen before. These checks are performed without modifying the + * externally visible state of the SSL context. + * + * See mbedtls_ssl_check_record() for more information. + * + * Uncomment to enable support for record checking. + */ +#define MBEDTLS_SSL_RECORD_CHECKING + /** * \def MBEDTLS_SSL_DTLS_CONNECTION_ID * diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index db210db25b..652c4f5afc 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -1756,6 +1756,56 @@ void mbedtls_ssl_set_verify( mbedtls_ssl_context *ssl, */ void mbedtls_ssl_conf_read_timeout( mbedtls_ssl_config *conf, uint32_t timeout ); +#if defined(MBEDTLS_SSL_RECORD_CHECKING) +/** + * \brief Check whether a buffer contains a valid and authentic record + * that has not been seen before. (DTLS only). + * + * This function does not change the user-visible state + * of the SSL context. Its sole purpose is to provide + * an indication of the legitimacy of an incoming record. + * + * This can be useful e.g. in distributed server environments + * using the DTLS Connection ID feature, in which connections + * might need to be passed between service instances on a change + * of peer address, but where such disruptive operations should + * only happen after the validity of incoming records has been + * confirmed. + * + * \param ssl The SSL context to use. + * \param buf The address of the buffer holding the record to be checked. + * This must be a read/write buffer of length \p buflen Bytes. + * \param buflen The length of \p buf in Bytes. + * + * \note This routine only checks whether the provided buffer begins + * with a valid and authentic record that has not been seen + * before, but does not check potential data following the + * initial record. In particular, it is possible to pass DTLS + * datagrams containing multiple records, in which case only + * the first record is checked. + * + * \note This function modifies the input buffer \p buf. If you need + * to preserve the original record, you have to maintain a copy. + * + * \return \c 0 if the record is valid and authentic and has not been + * seen before. + * \return MBEDTLS_ERR_SSL_INVALID_MAC if the check completed + * successfully but the record was found to be not authentic. + * \return MBEDTLS_ERR_SSL_INVALID_RECORD if the check completed + * successfully but the record was found to be invalid for + * a reason different from authenticity checking. + * \return MBEDTLS_ERR_SSL_UNEXPECTED_RECORD if the check completed + * successfully but the record was found to be unexpected + * in the state of the SSL context, including replayed records. + * \return Another negative error code on different kinds of failure. + * In this case, the SSL context becomes unusable and needs + * to be freed or reset before reuse. + */ +int mbedtls_ssl_check_record( mbedtls_ssl_context const *ssl, + unsigned char *buf, + size_t buflen ); +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + /** * \brief Set the timer callbacks (Mandatory for DTLS.) * diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 56745e3f5f..ad6c9fce01 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -108,6 +108,18 @@ static void ssl_update_out_pointers( mbedtls_ssl_context *ssl, mbedtls_ssl_transform *transform ); static void ssl_update_in_pointers( mbedtls_ssl_context *ssl ); +#if defined(MBEDTLS_SSL_RECORD_CHECKING) +int mbedtls_ssl_check_record( mbedtls_ssl_context const *ssl, + unsigned char *buf, + size_t buflen ) +{ + ((void) ssl); + ((void) buf); + ((void) buflen); + return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE ); +} +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + #define SSL_DONT_FORCE_FLUSH 0 #define SSL_FORCE_FLUSH 1 diff --git a/library/version_features.c b/library/version_features.c index 58acb5c79d..e83899d0a7 100644 --- a/library/version_features.c +++ b/library/version_features.c @@ -450,6 +450,9 @@ static const char * const features[] = { #if defined(MBEDTLS_SSL_ALL_ALERT_MESSAGES) "MBEDTLS_SSL_ALL_ALERT_MESSAGES", #endif /* MBEDTLS_SSL_ALL_ALERT_MESSAGES */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) + "MBEDTLS_SSL_RECORD_CHECKING", +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) "MBEDTLS_SSL_DTLS_CONNECTION_ID", #endif /* MBEDTLS_SSL_DTLS_CONNECTION_ID */ diff --git a/programs/ssl/query_config.c b/programs/ssl/query_config.c index 98a5df2307..c6d19bf093 100644 --- a/programs/ssl/query_config.c +++ b/programs/ssl/query_config.c @@ -1242,6 +1242,14 @@ int query_config( const char *config ) } #endif /* MBEDTLS_SSL_ALL_ALERT_MESSAGES */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) + if( strcmp( "MBEDTLS_SSL_RECORD_CHECKING", config ) == 0 ) + { + MACRO_EXPANSION_TO_STR( MBEDTLS_SSL_RECORD_CHECKING ); + return( 0 ); + } +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) if( strcmp( "MBEDTLS_SSL_DTLS_CONNECTION_ID", config ) == 0 ) { diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index f8d84f9598..7ba4565c21 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -617,7 +617,8 @@ exit: * Test recv/send functions that make sure each try returns * WANT_READ/WANT_WRITE at least once before sucesseding */ -static int my_recv( void *ctx, unsigned char *buf, size_t len ) + +static int delayed_recv( void *ctx, unsigned char *buf, size_t len ) { static int first_try = 1; int ret; @@ -634,7 +635,7 @@ static int my_recv( void *ctx, unsigned char *buf, size_t len ) return( ret ); } -static int my_send( void *ctx, const unsigned char *buf, size_t len ) +static int delayed_send( void *ctx, const unsigned char *buf, size_t len ) { static int first_try = 1; int ret; @@ -651,6 +652,137 @@ static int my_send( void *ctx, const unsigned char *buf, size_t len ) return( ret ); } +typedef struct +{ + mbedtls_ssl_context *ssl; + mbedtls_net_context *net; +} io_ctx_t; + +#if defined(MBEDTLS_SSL_RECORD_CHECKING) +static int ssl_check_record( mbedtls_ssl_context const *ssl, + unsigned char const *buf, size_t len ) +{ + int ret; + unsigned char *tmp_buf; + + tmp_buf = mbedtls_calloc( 1, len ); + if( tmp_buf == NULL ) + return( MBEDTLS_ERR_SSL_ALLOC_FAILED ); + memcpy( tmp_buf, buf, len ); + + ret = mbedtls_ssl_check_record( ssl, tmp_buf, len ); + if( ret != MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE ) + { + int ret_repeated; + + /* Test-only: Make sure that mbedtls_ssl_check_record() + * doesn't alter state. */ + memcpy( tmp_buf, buf, len ); /* Restore buffer */ + ret_repeated = mbedtls_ssl_check_record( ssl, tmp_buf, len ); + if( ret != ret_repeated ) + { + mbedtls_printf( "mbedtls_ssl_check_record() returned inconsistent results.\n" ); + return( -1 ); + } + + switch( ret ) + { + case 0: + break; + + case MBEDTLS_ERR_SSL_INVALID_RECORD: + if( opt.debug_level > 1 ) + mbedtls_printf( "mbedtls_ssl_check_record() detected invalid record.\n" ); + break; + + case MBEDTLS_ERR_SSL_INVALID_MAC: + if( opt.debug_level > 1 ) + mbedtls_printf( "mbedtls_ssl_check_record() detected unauthentic record.\n" ); + break; + + case MBEDTLS_ERR_SSL_UNEXPECTED_RECORD: + if( opt.debug_level > 1 ) + mbedtls_printf( "mbedtls_ssl_check_record() detected unexpected record.\n" ); + break; + + default: + mbedtls_printf( "mbedtls_ssl_check_record() failed fatally with -%#04x.\n", -ret ); + return( -1 ); + } + + /* Regardless of the outcome, forward the record to the stack. */ + } + + mbedtls_free( tmp_buf ); + + return( 0 ); +} +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + +static int recv_cb( void *ctx, unsigned char *buf, size_t len ) +{ + io_ctx_t *io_ctx = (io_ctx_t*) ctx; + size_t recv_len; + int ret; + + if( opt.nbio == 2 ) + ret = delayed_recv( io_ctx->net, buf, len ); + else + ret = mbedtls_net_recv( io_ctx->net, buf, len ); + if( ret < 0 ) + return( ret ); + recv_len = (size_t) ret; + + if( opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) + { + /* Here's the place to do any datagram/record checking + * in between receiving the packet from the underlying + * transport and passing it on to the TLS stack. */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) + if( ssl_check_record( io_ctx->ssl, buf, recv_len ) != 0 ) + return( -1 ); +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + } + + return( (int) recv_len ); +} + +static int recv_timeout_cb( void *ctx, unsigned char *buf, size_t len, + uint32_t timeout ) +{ + io_ctx_t *io_ctx = (io_ctx_t*) ctx; + int ret; + size_t recv_len; + + ret = mbedtls_net_recv_timeout( io_ctx->net, buf, len, timeout ); + if( ret < 0 ) + return( ret ); + recv_len = (size_t) ret; + + if( opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) + { + /* Here's the place to do any datagram/record checking + * in between receiving the packet from the underlying + * transport and passing it on to the TLS stack. */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) + if( ssl_check_record( io_ctx->ssl, buf, recv_len ) != 0 ) + return( -1 ); +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + } + + return( (int) recv_len ); +} + +static int send_cb( void *ctx, unsigned char const *buf, size_t len ) +{ + io_ctx_t *io_ctx = (io_ctx_t*) ctx; + + if( opt.nbio == 2 ) + return( delayed_send( io_ctx->net, buf, len ) ); + + return( mbedtls_net_send( io_ctx->net, buf, len ) ); +} + #if defined(MBEDTLS_X509_CRT_PARSE_C) static unsigned char peer_crt_info[1024]; @@ -874,6 +1006,7 @@ int main( int argc, char *argv[] ) { int ret = 0, len, tail_len, i, written, frags, retry_left; mbedtls_net_context server_fd; + io_ctx_t io_ctx; unsigned char buf[MAX_REQUEST_SIZE + 1]; @@ -2149,12 +2282,10 @@ int main( int argc, char *argv[] ) mbedtls_ssl_set_verify( &ssl, my_verify, NULL ); #endif /* MBEDTLS_X509_CRT_PARSE_C */ - if( opt.nbio == 2 ) - mbedtls_ssl_set_bio( &ssl, &server_fd, my_send, my_recv, NULL ); - else - mbedtls_ssl_set_bio( &ssl, &server_fd, - mbedtls_net_send, mbedtls_net_recv, - opt.nbio == 0 ? mbedtls_net_recv_timeout : NULL ); + io_ctx.ssl = &ssl; + io_ctx.net = &server_fd; + mbedtls_ssl_set_bio( &ssl, &io_ctx, send_cb, recv_cb, + opt.nbio == 0 ? recv_timeout_cb : NULL ); #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) if( opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index e8a6cfbe71..3279cda9e2 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -728,7 +728,7 @@ exit: * Test recv/send functions that make sure each try returns * WANT_READ/WANT_WRITE at least once before sucesseding */ -static int my_recv( void *ctx, unsigned char *buf, size_t len ) +static int delayed_recv( void *ctx, unsigned char *buf, size_t len ) { static int first_try = 1; int ret; @@ -745,7 +745,7 @@ static int my_recv( void *ctx, unsigned char *buf, size_t len ) return( ret ); } -static int my_send( void *ctx, const unsigned char *buf, size_t len ) +static int delayed_send( void *ctx, const unsigned char *buf, size_t len ) { static int first_try = 1; int ret; @@ -762,6 +762,139 @@ static int my_send( void *ctx, const unsigned char *buf, size_t len ) return( ret ); } +typedef struct +{ + mbedtls_ssl_context *ssl; + mbedtls_net_context *net; +} io_ctx_t; + +#if defined(MBEDTLS_SSL_RECORD_CHECKING) +static int ssl_check_record( mbedtls_ssl_context const *ssl, + unsigned char const *buf, size_t len ) +{ + int ret; + unsigned char *tmp_buf; + + /* Record checking may modify the input buffer, + * so make a copy. */ + tmp_buf = mbedtls_calloc( 1, len ); + if( tmp_buf == NULL ) + return( MBEDTLS_ERR_SSL_ALLOC_FAILED ); + memcpy( tmp_buf, buf, len ); + + ret = mbedtls_ssl_check_record( ssl, tmp_buf, len ); + if( ret != MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE ) + { + int ret_repeated; + + /* Test-only: Make sure that mbedtls_ssl_check_record() + * doesn't alter state. */ + memcpy( tmp_buf, buf, len ); /* Restore buffer */ + ret_repeated = mbedtls_ssl_check_record( ssl, tmp_buf, len ); + if( ret != ret_repeated ) + { + mbedtls_printf( "mbedtls_ssl_check_record() returned inconsistent results.\n" ); + return( -1 ); + } + + switch( ret ) + { + case 0: + break; + + case MBEDTLS_ERR_SSL_INVALID_RECORD: + if( opt.debug_level > 1 ) + mbedtls_printf( "mbedtls_ssl_check_record() detected invalid record.\n" ); + break; + + case MBEDTLS_ERR_SSL_INVALID_MAC: + if( opt.debug_level > 1 ) + mbedtls_printf( "mbedtls_ssl_check_record() detected unauthentic record.\n" ); + break; + + case MBEDTLS_ERR_SSL_UNEXPECTED_RECORD: + if( opt.debug_level > 1 ) + mbedtls_printf( "mbedtls_ssl_check_record() detected unexpected record.\n" ); + break; + + default: + mbedtls_printf( "mbedtls_ssl_check_record() failed fatally with -%#04x.\n", -ret ); + return( -1 ); + } + + /* Regardless of the outcome, forward the record to the stack. */ + } + + mbedtls_free( tmp_buf ); + + return( 0 ); +} +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + +static int recv_cb( void *ctx, unsigned char *buf, size_t len ) +{ + io_ctx_t *io_ctx = (io_ctx_t*) ctx; + size_t recv_len; + int ret; + + if( opt.nbio == 2 ) + ret = delayed_recv( io_ctx->net, buf, len ); + else + ret = mbedtls_net_recv( io_ctx->net, buf, len ); + if( ret < 0 ) + return( ret ); + recv_len = (size_t) ret; + + if( opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) + { + /* Here's the place to do any datagram/record checking + * in between receiving the packet from the underlying + * transport and passing it on to the TLS stack. */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) + if( ssl_check_record( io_ctx->ssl, buf, recv_len ) != 0 ) + return( -1 ); +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + } + + return( (int) recv_len ); +} + +static int recv_timeout_cb( void *ctx, unsigned char *buf, size_t len, + uint32_t timeout ) +{ + io_ctx_t *io_ctx = (io_ctx_t*) ctx; + int ret; + size_t recv_len; + + ret = mbedtls_net_recv_timeout( io_ctx->net, buf, len, timeout ); + if( ret < 0 ) + return( ret ); + recv_len = (size_t) ret; + + if( opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) + { + /* Here's the place to do any datagram/record checking + * in between receiving the packet from the underlying + * transport and passing it on to the TLS stack. */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) + if( ssl_check_record( io_ctx->ssl, buf, recv_len ) != 0 ) + return( -1 ); +#endif /* MBEDTLS_SSL_RECORD_CHECKING */ + } + + return( (int) recv_len ); +} + +static int send_cb( void *ctx, unsigned char const *buf, size_t len ) +{ + io_ctx_t *io_ctx = (io_ctx_t*) ctx; + + if( opt.nbio == 2 ) + return( delayed_send( io_ctx->net, buf, len ) ); + + return( mbedtls_net_send( io_ctx->net, buf, len ) ); +} + /* * Return authmode from string, or -1 on error */ @@ -1509,6 +1642,7 @@ int main( int argc, char *argv[] ) { int ret = 0, len, written, frags, exchanges_left; int version_suites[4][2]; + io_ctx_t io_ctx; unsigned char* buf = 0; #if defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) #if defined(MBEDTLS_USE_PSA_CRYPTO) @@ -3151,11 +3285,10 @@ int main( int argc, char *argv[] ) goto exit; } - if( opt.nbio == 2 ) - mbedtls_ssl_set_bio( &ssl, &client_fd, my_send, my_recv, NULL ); - else - mbedtls_ssl_set_bio( &ssl, &client_fd, mbedtls_net_send, mbedtls_net_recv, - opt.nbio == 0 ? mbedtls_net_recv_timeout : NULL ); + io_ctx.ssl = &ssl; + io_ctx.net = &client_fd; + mbedtls_ssl_set_bio( &ssl, &io_ctx, send_cb, recv_cb, + opt.nbio == 0 ? recv_timeout_cb : NULL ); #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) if( opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM )