diff --git a/include/mbedtls/config.h b/include/mbedtls/config.h index f8a99f2e5..243678c62 100644 --- a/include/mbedtls/config.h +++ b/include/mbedtls/config.h @@ -1293,6 +1293,19 @@ */ #define MBEDTLS_SSL_ALL_ALERT_MESSAGES +/** + * \def MBEDTLS_SSL_RECORD_CHECKING + * + * Enable the API mbedtls_ssl_check_record() which allows to check the + * validity, freshness and authenticity of an incoming record 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 9e5fb2dbb..edb0a4699 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -1898,6 +1898,54 @@ void mbedtls_ssl_set_mtu( mbedtls_ssl_context *ssl, uint16_t mtu ); void mbedtls_ssl_conf_read_timeout( mbedtls_ssl_config *conf, uint32_t timeout ); #endif /* !MBEDTLS_SSL_CONF_READ_TIMEOUT */ +#if defined(MBEDTLS_SSL_RECORD_CHECKING) +/** + * \brief Check whether a buffer contains a valid, fresh + * and authentic record (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 an R/W 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, fresh and authentic record, 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, fresh and authentic. + * \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 */ + #if !defined(MBEDTLS_SSL_CONF_SET_TIMER) && \ !defined(MBEDTLS_SSL_CONF_GET_TIMER) /** diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 3e78e5d95..d04602027 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -122,6 +122,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 51ce7801d..102b52109 100644 --- a/library/version_features.c +++ b/library/version_features.c @@ -438,6 +438,9 @@ static const char *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 0f7251d45..cf10ba18d 100644 --- a/programs/ssl/query_config.c +++ b/programs/ssl/query_config.c @@ -1210,6 +1210,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 f0de38153..f6bdc567d 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -490,7 +490,8 @@ static void my_debug( void *ctx, int level, * 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; @@ -507,7 +508,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; @@ -527,6 +528,137 @@ static int my_send( void *ctx, const unsigned char *buf, size_t len ) MBEDTLS_SSL_CONF_SEND && MBEDTLS_SSL_CONF_RECV_TIMEOUT */ +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]; @@ -761,6 +893,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]; @@ -1928,12 +2061,10 @@ int main( int argc, char *argv[] ) #if !defined(MBEDTLS_SSL_CONF_RECV) && \ !defined(MBEDTLS_SSL_CONF_SEND) && \ !defined(MBEDTLS_SSL_CONF_RECV_TIMEOUT) - 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 ); #else mbedtls_ssl_set_bio_ctx( &ssl, &server_fd ); #endif @@ -2550,13 +2681,8 @@ send_request: goto exit; } - 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 ); + mbedtls_ssl_set_bio( &ssl, &io_ctx, send_cb, recv_cb, + opt.nbio == 0 ? recv_timeout_cb : NULL ); #if defined(MBEDTLS_TIMING_C) #if !defined(MBEDTLS_SSL_CONF_SET_TIMER) && \ diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index 747523b5f..2cd00fa39 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -621,7 +621,7 @@ static void my_debug( void *ctx, int level, * 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; @@ -638,7 +638,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; @@ -658,6 +658,139 @@ static int my_send( void *ctx, const unsigned char *buf, size_t len ) MBEDTLS_SSL_CONF_SEND && MBEDTLS_SSL_CONF_RECV_TIMEOUT */ +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 ) ); +} + #if !defined(MBEDTLS_SSL_CONF_AUTHMODE) /* * Return authmode from string, or -1 on error @@ -1376,6 +1509,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) unsigned char psk[MBEDTLS_PSK_MAX_LEN]; @@ -2918,12 +3052,10 @@ int main( int argc, char *argv[] ) #if !defined(MBEDTLS_SSL_CONF_RECV) && \ !defined(MBEDTLS_SSL_CONF_SEND) && \ !defined(MBEDTLS_SSL_CONF_RECV_TIMEOUT) - 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 ); #else mbedtls_ssl_set_bio_ctx( &ssl, &client_fd ); #endif @@ -3586,13 +3718,8 @@ data_exchange: * if you want to share your set up code between the case of * establishing a new connection and this case. */ - 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 ); + mbedtls_ssl_set_bio( &ssl, &io_ctx, send_cb, recv_cb, + opt.nbio == 0 ? recv_timeout_cb : NULL ); #if defined(MBEDTLS_TIMING_C) #if !defined(MBEDTLS_SSL_CONF_SET_TIMER) && \