diff --git a/ChangeLog b/ChangeLog index f2cb93231..9ee82c685 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,14 @@ Features mbedtls_ecdh_compute_shared()) are supported for now. Contributed by Nicholas Wilson (#348). +API Changes + * Add function mbedtls_net_poll to public API allowing to wait for a + network context to become ready for reading or writing. + * Add function mbedtls_ssl_check_pending to public API allowing to check + if more data is pending to be processed in the internal message buffers. + This function is necessary to determine when it is safe to idle on the + underlying transport in case event-driven IO is used. + Bugfix * Fix spurious uninitialized variable warning in cmac.c. Fix independently contributed by Brian J Murray and David Brown. @@ -39,6 +47,14 @@ Bugfix the mbedtls_cipher_update() documentation. Contributed by Andy Leiserson. * Fix overriding and ignoring return values when parsing and writing to a file in pk_sign program. Found by kevlut in #1142. + * Restrict usage of error code MBEDTLS_ERR_SSL_WANT_READ to situations + where data needs to be fetched from the underlying transport in order + to make progress. Previously, this error code was also occasionally + returned when unexpected messages were being discarded, ignoring that + further messages could potentially already be pending to be processed + in the internal buffers; these cases lead to deadlocks in case + event-driven I/O was used. + Found and reported by Hubert Mis in #772. Changes * Remove some redundant code in bignum.c. Contributed by Alexey Skalozub. diff --git a/include/mbedtls/net_sockets.h b/include/mbedtls/net_sockets.h index 52bb8de7c..0f9b31ebc 100644 --- a/include/mbedtls/net_sockets.h +++ b/include/mbedtls/net_sockets.h @@ -46,12 +46,17 @@ #define MBEDTLS_ERR_NET_UNKNOWN_HOST -0x0052 /**< Failed to get an IP address for the given hostname. */ #define MBEDTLS_ERR_NET_BUFFER_TOO_SMALL -0x0043 /**< Buffer is too small to hold the data. */ #define MBEDTLS_ERR_NET_INVALID_CONTEXT -0x0045 /**< The context is invalid, eg because it was free()ed. */ +#define MBEDTLS_ERR_NET_POLL_FAILED -0x0047 /**< Polling the net context failed. */ +#define MBEDTLS_ERR_NET_BAD_INPUT_DATA -0x0049 /**< Input invalid. */ #define MBEDTLS_NET_LISTEN_BACKLOG 10 /**< The backlog that listen() should use. */ #define MBEDTLS_NET_PROTO_TCP 0 /**< The TCP transport protocol */ #define MBEDTLS_NET_PROTO_UDP 1 /**< The UDP transport protocol */ +#define MBEDTLS_NET_POLL_READ 1 /**< Used in \c mbedtls_net_poll to check for pending data */ +#define MBEDTLS_NET_POLL_WRITE 2 /**< Used in \c mbedtls_net_poll to check if write possible */ + #ifdef __cplusplus extern "C" { #endif @@ -133,6 +138,29 @@ int mbedtls_net_accept( mbedtls_net_context *bind_ctx, mbedtls_net_context *client_ctx, void *client_ip, size_t buf_size, size_t *ip_len ); +/** + * \brief Check and wait for the context to be ready for read/write + * + * \param ctx Socket to check + * \param rw Bitflag composed of MBEDTLS_NET_POLL_READ and + * MBEDTLS_NET_POLL_WRITE specifying the events + * to wait for: + * - If MBEDTLS_NET_POLL_READ is set, the function + * will return as soon as the net context is available + * for reading. + * - If MBEDTLS_NET_POLL_WRITE is set, the function + * will return as soon as the net context is available + * for writing. + * \param timeout Maximal amount of time to wait before returning, + * in milliseconds. If \c timeout is zero, the + * function returns immediately. If \c timeout is + * -1u, the function blocks potentially indefinitely. + * + * \return Bitmask composed of MBEDTLS_NET_POLL_READ/WRITE + * on success or timeout, or a negative return code otherwise. + */ +int mbedtls_net_poll( mbedtls_net_context *ctx, uint32_t rw, uint32_t timeout ); + /** * \brief Set the socket blocking * diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index e1d64b9ce..bb9c02dbf 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -112,13 +112,14 @@ #define MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED -0x6A80 /**< DTLS client must retry for hello verification */ #define MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL -0x6A00 /**< A buffer is too small to receive or write a message */ #define MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE -0x6980 /**< None of the common ciphersuites is usable (eg, no suitable certificate, see debug messages). */ -#define MBEDTLS_ERR_SSL_WANT_READ -0x6900 /**< Connection requires a read call. */ +#define MBEDTLS_ERR_SSL_WANT_READ -0x6900 /**< No data of requested type currently available on underlying transport. */ #define MBEDTLS_ERR_SSL_WANT_WRITE -0x6880 /**< Connection requires a write call. */ #define MBEDTLS_ERR_SSL_TIMEOUT -0x6800 /**< The operation timed out. */ #define MBEDTLS_ERR_SSL_CLIENT_RECONNECT -0x6780 /**< The client initiated a reconnect from the same port. */ #define MBEDTLS_ERR_SSL_UNEXPECTED_RECORD -0x6700 /**< Record header looks valid but is not expected. */ #define MBEDTLS_ERR_SSL_NON_FATAL -0x6680 /**< The alert message received indicates a non-fatal error. */ #define MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH -0x6600 /**< Couldn't set the hash for verifying CertificateVerify */ +#define MBEDTLS_ERR_SSL_CONTINUE_PROCESSING -0x6580 /**< Internal-only message signaling that further message-processing should be done */ /* * Various constants @@ -2301,11 +2302,59 @@ void mbedtls_ssl_conf_renegotiation_period( mbedtls_ssl_config *conf, #endif /* MBEDTLS_SSL_RENEGOTIATION */ /** - * \brief Return the number of data bytes available to read + * \brief Check if there is data already read from the + * underlying transport but not yet processed. * * \param ssl SSL context * - * \return how many bytes are available in the read buffer + * \return 0 if nothing's pending, 1 otherwise. + * + * \note This is different in purpose and behaviour from + * \c mbedtls_ssl_get_bytes_avail in that it considers + * any kind of unprocessed data, not only unread + * application data. If \c mbedtls_ssl_get_bytes + * returns a non-zero value, this function will + * also signal pending data, but the converse does + * not hold. For example, in DTLS there might be + * further records waiting to be processed from + * the current underlying transport's datagram. + * + * \note If this function returns 1 (data pending), this + * does not imply that a subsequent call to + * \c mbedtls_ssl_read will provide any data; + * e.g., the unprocessed data might turn out + * to be an alert or a handshake message. + * + * \note This function is useful in the following situation: + * If the SSL/TLS module successfully returns from an + * operation - e.g. a handshake or an application record + * read - and you're awaiting incoming data next, you + * must not immediately idle on the underlying transport + * to have data ready, but you need to check the value + * of this function first. The reason is that the desired + * data might already be read but not yet processed. + * If, in contrast, a previous call to the SSL/TLS module + * returned MBEDTLS_ERR_SSL_WANT_READ, it is not necessary + * to call this function, as the latter error code entails + * that all internal data has been processed. + * + */ +int mbedtls_ssl_check_pending( const mbedtls_ssl_context *ssl ); + +/** + * \brief Return the number of application data bytes + * remaining to be read from the current record. + * + * \param ssl SSL context + * + * \return How many bytes are available in the application + * data record read buffer. + * + * \note When working over a datagram transport, this is + * useful to detect the current datagram's boundary + * in case \c mbedtls_ssl_read has written the maximal + * amount of data fitting into the input buffer. + * */ size_t mbedtls_ssl_get_bytes_avail( const mbedtls_ssl_context *ssl ); @@ -2420,11 +2469,25 @@ int mbedtls_ssl_get_session( const mbedtls_ssl_context *ssl, mbedtls_ssl_session * MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED (see below), or * a specific SSL error code. * + * If this function returns MBEDTLS_ERR_SSL_WANT_READ, the + * handshake is unfinished and no further data is available + * from the underlying transport. In this case, you must call + * the function again at some later stage. + * + * \note Remarks regarding event-driven DTLS: + * If the function returns MBEDTLS_ERR_SSL_WANT_READ, no datagram + * from the underlying transport layer is currently being processed, + * and it is safe to idle until the timer or the underlying transport + * signal a new event. This is not true for a successful handshake, + * in which case the datagram of the underlying transport that is + * currently being processed might or might not contain further + * DTLS records. + * * \note If this function returns something other than 0 or - * MBEDTLS_ERR_SSL_WANT_READ/WRITE, then the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * MBEDTLS_ERR_SSL_WANT_READ/WRITE, you must stop using + * the SSL context for reading or writing, and either free it or + * call \c mbedtls_ssl_session_reset() on it before re-using it + * for a new connection; the current connection must be closed. * * \note If DTLS is in use, then you may choose to handle * MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED specially for logging @@ -2441,10 +2504,10 @@ int mbedtls_ssl_handshake( mbedtls_ssl_context *ssl ); * call this function if state is MBEDTLS_SSL_HANDSHAKE_OVER. * * \note If this function returns something other than 0 or - * MBEDTLS_ERR_SSL_WANT_READ/WRITE, then the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * MBEDTLS_ERR_SSL_WANT_READ/WRITE, you must stop using + * the SSL context for reading or writing, and either free it or + * call \c mbedtls_ssl_session_reset() on it before re-using it + * for a new connection; the current connection must be closed. * * \param ssl SSL context * @@ -2468,10 +2531,10 @@ int mbedtls_ssl_handshake_step( mbedtls_ssl_context *ssl ); * value. * * \note If this function returns something other than 0 or - * MBEDTLS_ERR_SSL_WANT_READ/WRITE, then the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * MBEDTLS_ERR_SSL_WANT_READ/WRITE, you must stop using + * the SSL context for reading or writing, and either free it or + * call \c mbedtls_ssl_session_reset() on it before re-using it + * for a new connection; the current connection must be closed. */ int mbedtls_ssl_renegotiate( mbedtls_ssl_context *ssl ); #endif /* MBEDTLS_SSL_RENEGOTIATION */ @@ -2483,20 +2546,20 @@ int mbedtls_ssl_renegotiate( mbedtls_ssl_context *ssl ); * \param buf buffer that will hold the data * \param len maximum number of bytes to read * - * \return the number of bytes read, or - * 0 for EOF, or - * MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE, or - * MBEDTLS_ERR_SSL_CLIENT_RECONNECT (see below), or - * another negative error code. + * \return One of the following: + * - 0 if the read end of the underlying transport was closed, + * - the (positive) number of bytes read, or + * - a negative error code on failure. * - * \note If this function returns something other than a positive - * value or MBEDTLS_ERR_SSL_WANT_READ/WRITE or - * MBEDTLS_ERR_SSL_CLIENT_RECONNECT, then the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * If MBEDTLS_ERR_SSL_WANT_READ is returned, no application data + * is available from the underlying transport. In this case, + * the function needs to be called again at some later stage. * - * \note When this function return MBEDTLS_ERR_SSL_CLIENT_RECONNECT + * If MBEDTLS_ERR_SSL_WANT_WRITE is returned, a write is pending + * but the underlying transport isn't available for writing. In this + * case, the function needs to be called again at some later stage. + * + * When this function return MBEDTLS_ERR_SSL_CLIENT_RECONNECT * (which can only happen server-side), it means that a client * is initiating a new connection using the same source port. * You can either treat that as a connection close and wait @@ -2509,6 +2572,28 @@ int mbedtls_ssl_renegotiate( mbedtls_ssl_context *ssl ); * again. WARNING: not validating the identity of the client * again, or not transmitting the new identity to the * application layer, would allow authentication bypass! + * + * \note If this function returns something other than a positive value + * or MBEDTLS_ERR_SSL_WANT_READ/WRITE or MBEDTLS_ERR_SSL_CLIENT_RECONNECT, + * you must stop using the SSL context for reading or writing, + * and either free it or call \c mbedtls_ssl_session_reset() on it + * before re-using it for a new connection; the current connection + * must be closed. + * + * \note Remarks regarding event-driven DTLS: + * - If the function returns MBEDTLS_ERR_SSL_WANT_READ, no datagram + * from the underlying transport layer is currently being processed, + * and it is safe to idle until the timer or the underlying transport + * signal a new event. + * - This function may return MBEDTLS_ERR_SSL_WANT_READ even if data was + * initially available on the underlying transport, as this data may have + * been only e.g. duplicated messages or a renegotiation request. + * Therefore, you must be prepared to receive MBEDTLS_ERR_SSL_WANT_READ even + * when reacting to an incoming-data event from the underlying transport. + * - On success, the datagram of the underlying transport that is currently + * being processed may contain further DTLS records. You should call + * \c mbedtls_ssl_check_pending to check for remaining records. + * */ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ); @@ -2529,11 +2614,11 @@ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ) * or MBEDTLS_ERR_SSL_WANT_WRITE or MBEDTLS_ERR_SSL_WANT_READ, * or another negative error code. * - * \note If this function returns something other than a positive - * value or MBEDTLS_ERR_SSL_WANT_READ/WRITE, the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * \note If this function returns something other than a positive value + * or MBEDTLS_ERR_SSL_WANT_READ/WRITE, you must stop using + * the SSL context for reading or writing, and either free it or + * call \c mbedtls_ssl_session_reset() on it before re-using it + * for a new connection; the current connection must be closed. * * \note When this function returns MBEDTLS_ERR_SSL_WANT_WRITE/READ, * it must be called later with the *same* arguments, @@ -2562,10 +2647,10 @@ int mbedtls_ssl_write( mbedtls_ssl_context *ssl, const unsigned char *buf, size_ * \return 0 if successful, or a specific SSL error code. * * \note If this function returns something other than 0 or - * MBEDTLS_ERR_SSL_WANT_READ/WRITE, then the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * MBEDTLS_ERR_SSL_WANT_READ/WRITE, you must stop using + * the SSL context for reading or writing, and either free it or + * call \c mbedtls_ssl_session_reset() on it before re-using it + * for a new connection; the current connection must be closed. */ int mbedtls_ssl_send_alert_message( mbedtls_ssl_context *ssl, unsigned char level, @@ -2578,10 +2663,10 @@ int mbedtls_ssl_send_alert_message( mbedtls_ssl_context *ssl, * \return 0 if successful, or a specific SSL error code. * * \note If this function returns something other than 0 or - * MBEDTLS_ERR_SSL_WANT_READ/WRITE, then the ssl context - * becomes unusable, and you should either free it or call - * \c mbedtls_ssl_session_reset() on it before re-using it for - * a new connection; the current connection must be closed. + * MBEDTLS_ERR_SSL_WANT_READ/WRITE, you must stop using + * the SSL context for reading or writing, and either free it or + * call \c mbedtls_ssl_session_reset() on it before re-using it + * for a new connection; the current connection must be closed. */ int mbedtls_ssl_close_notify( mbedtls_ssl_context *ssl ); diff --git a/library/error.c b/library/error.c index b173c7e8e..96ab20376 100644 --- a/library/error.c +++ b/library/error.c @@ -478,7 +478,7 @@ void mbedtls_strerror( int ret, char *buf, size_t buflen ) if( use_ret == -(MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE) ) mbedtls_snprintf( buf, buflen, "SSL - None of the common ciphersuites is usable (eg, no suitable certificate, see debug messages)" ); if( use_ret == -(MBEDTLS_ERR_SSL_WANT_READ) ) - mbedtls_snprintf( buf, buflen, "SSL - Connection requires a read call" ); + mbedtls_snprintf( buf, buflen, "SSL - No data of requested type currently available on underlying transport" ); if( use_ret == -(MBEDTLS_ERR_SSL_WANT_WRITE) ) mbedtls_snprintf( buf, buflen, "SSL - Connection requires a write call" ); if( use_ret == -(MBEDTLS_ERR_SSL_TIMEOUT) ) @@ -491,6 +491,8 @@ void mbedtls_strerror( int ret, char *buf, size_t buflen ) mbedtls_snprintf( buf, buflen, "SSL - The alert message received indicates a non-fatal error" ); if( use_ret == -(MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH) ) mbedtls_snprintf( buf, buflen, "SSL - Couldn't set the hash for verifying CertificateVerify" ); + if( use_ret == -(MBEDTLS_ERR_SSL_CONTINUE_PROCESSING) ) + mbedtls_snprintf( buf, buflen, "SSL - Internal-only message signaling that further message-processing should be done" ); #endif /* MBEDTLS_SSL_TLS_C */ #if defined(MBEDTLS_X509_USE_C) || defined(MBEDTLS_X509_CREATE_C) @@ -745,6 +747,10 @@ void mbedtls_strerror( int ret, char *buf, size_t buflen ) mbedtls_snprintf( buf, buflen, "NET - Buffer is too small to hold the data" ); if( use_ret == -(MBEDTLS_ERR_NET_INVALID_CONTEXT) ) mbedtls_snprintf( buf, buflen, "NET - The context is invalid, eg because it was free()ed" ); + if( use_ret == -(MBEDTLS_ERR_NET_POLL_FAILED) ) + mbedtls_snprintf( buf, buflen, "NET - Polling the net context failed" ); + if( use_ret == -(MBEDTLS_ERR_NET_BAD_INPUT_DATA) ) + mbedtls_snprintf( buf, buflen, "NET - Input invalid" ); #endif /* MBEDTLS_NET_C */ #if defined(MBEDTLS_OID_C) diff --git a/library/net_sockets.c b/library/net_sockets.c index 631efad27..f99d339ff 100644 --- a/library/net_sockets.c +++ b/library/net_sockets.c @@ -45,6 +45,8 @@ #if (defined(_WIN32) || defined(_WIN32_WCE)) && !defined(EFIX64) && \ !defined(EFI32) +#define IS_EINTR( ret ) ( ( ret ) == WSAEINTR ) + #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif @@ -82,6 +84,8 @@ static int wsa_init_done = 0; #include #include +#define IS_EINTR( ret ) ( ( ret ) == EINTR ) + #endif /* ( _WIN32 || _WIN32_WCE ) && !EFIX64 && !EFI32 */ /* Some MS functions want int and MSVC warns if we pass size_t, @@ -271,7 +275,7 @@ static int net_would_block( const mbedtls_net_context *ctx ) static int net_would_block( const mbedtls_net_context *ctx ) { int err = errno; - + /* * Never return 'WOULD BLOCK' on a non-blocking socket */ @@ -438,6 +442,68 @@ int mbedtls_net_set_nonblock( mbedtls_net_context *ctx ) #endif } +/* + * Check if data is available on the socket + */ + +int mbedtls_net_poll( mbedtls_net_context *ctx, uint32_t rw, uint32_t timeout ) +{ + int ret; + struct timeval tv; + + fd_set read_fds; + fd_set write_fds; + + int fd = ctx->fd; + + if( fd < 0 ) + return( MBEDTLS_ERR_NET_INVALID_CONTEXT ); + + /* Ensure that memory sanitizers consider + * read_fds and write_fds as initialized even + * if FD_ZERO is implemented in assembly. */ + memset( &read_fds, 0, sizeof( read_fds ) ); + memset( &write_fds, 0, sizeof( write_fds ) ); + + FD_ZERO( &read_fds ); + if( rw & MBEDTLS_NET_POLL_READ ) + { + rw &= ~MBEDTLS_NET_POLL_READ; + FD_SET( fd, &read_fds ); + } + + FD_ZERO( &write_fds ); + if( rw & MBEDTLS_NET_POLL_WRITE ) + { + rw &= ~MBEDTLS_NET_POLL_WRITE; + FD_SET( fd, &write_fds ); + } + + if( rw != 0 ) + return( MBEDTLS_ERR_NET_BAD_INPUT_DATA ); + + tv.tv_sec = timeout / 1000; + tv.tv_usec = ( timeout % 1000 ) * 1000; + + do + { + ret = select( fd + 1, &read_fds, &write_fds, NULL, + timeout == (uint32_t) -1 ? NULL : &tv ); + } + while( IS_EINTR( ret ) ); + + if( ret < 0 ) + return( MBEDTLS_ERR_NET_POLL_FAILED ); + + ret = 0; + if( FD_ISSET( fd, &read_fds ) ) + ret |= MBEDTLS_NET_POLL_READ; + if( FD_ISSET( fd, &write_fds ) ) + ret |= MBEDTLS_NET_POLL_WRITE; + + return( ret ); +} + /* * Portable usleep helper */ @@ -497,8 +563,8 @@ int mbedtls_net_recv( void *ctx, unsigned char *buf, size_t len ) /* * Read at most 'len' characters, blocking for at most 'timeout' ms */ -int mbedtls_net_recv_timeout( void *ctx, unsigned char *buf, size_t len, - uint32_t timeout ) +int mbedtls_net_recv_timeout( void *ctx, unsigned char *buf, + size_t len, uint32_t timeout ) { int ret; struct timeval tv; diff --git a/library/ssl_srv.c b/library/ssl_srv.c index aca4235e6..2c180f13f 100644 --- a/library/ssl_srv.c +++ b/library/ssl_srv.c @@ -793,7 +793,7 @@ static int ssl_ciphersuite_match( mbedtls_ssl_context *ssl, int suite_id, const mbedtls_ssl_ciphersuite_t *suite_info; #if defined(MBEDTLS_SSL_PROTO_TLS1_2) && \ - defined(MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED) + defined(MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED) mbedtls_pk_type_t sig_type; #endif @@ -2961,7 +2961,7 @@ static int ssl_write_server_key_exchange( mbedtls_ssl_context *ssl ) return( ret ); } -#if defined(MBEDTLS_KEY_EXCHANGE__WITH_SERVER_SIGNATURE__ENABLED) +#if defined(MBEDTLS_KEY_EXCHANGE__WITH_SERVER_SIGNATURE__ENABLED) dig_signed = p; dig_signed_len = len; #endif @@ -3050,7 +3050,7 @@ curve_matching_done: /* * 3.1: Choose hash algorithm: - * A: For TLS 1.2, obey signature-hash-algorithm extension + * A: For TLS 1.2, obey signature-hash-algorithm extension * to choose appropriate hash. * B: For SSL3, TLS1.0, TLS1.1 and ECDHE_ECDSA, use SHA1 * (RFC 4492, Sec. 5.4) @@ -3071,7 +3071,7 @@ curve_matching_done: sig_alg ) ) == MBEDTLS_MD_NONE ) { MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) ); - /* (... because we choose a cipher suite + /* (... because we choose a cipher suite * only if there is a matching hash.) */ return( MBEDTLS_ERR_SSL_INTERNAL_ERROR ); } @@ -3750,7 +3750,10 @@ static int ssl_parse_certificate_verify( mbedtls_ssl_context *ssl ) /* Read the message without adding it to the checksum */ do { - if( ( ret = mbedtls_ssl_read_record_layer( ssl ) ) != 0 ) + do ret = mbedtls_ssl_read_record_layer( ssl ); + while( ret == MBEDTLS_ERR_SSL_CONTINUE_PROCESSING ); + + if( ret != 0 ) { MBEDTLS_SSL_DEBUG_RET( 1, ( "mbedtls_ssl_read_record_layer" ), ret ); return( ret ); @@ -3758,7 +3761,8 @@ static int ssl_parse_certificate_verify( mbedtls_ssl_context *ssl ) ret = mbedtls_ssl_handle_message_type( ssl ); - } while( MBEDTLS_ERR_SSL_NON_FATAL == ret ); + } while( MBEDTLS_ERR_SSL_NON_FATAL == ret || + MBEDTLS_ERR_SSL_CONTINUE_PROCESSING == ret ); if( 0 != ret ) { diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 4ca74fb1d..e8063d2c1 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -2337,7 +2337,10 @@ int mbedtls_ssl_fetch_input( mbedtls_ssl_context *ssl, size_t nb_want ) * that will end up being dropped. */ if( ssl_check_timer( ssl ) != 0 ) + { + MBEDTLS_SSL_DEBUG_MSG( 2, ( "timer has expired" ) ); ret = MBEDTLS_ERR_SSL_TIMEOUT; + } else { len = MBEDTLS_SSL_BUFFER_LEN - ( ssl->in_hdr - ssl->in_buf ); @@ -3085,7 +3088,7 @@ static int ssl_reassemble_dtls_handshake( mbedtls_ssl_context *ssl ) if( ssl_bitmask_check( bitmask, msg_len ) != 0 ) { MBEDTLS_SSL_DEBUG_MSG( 2, ( "message is not complete yet" ) ); - return( MBEDTLS_ERR_SSL_WANT_READ ); + return( MBEDTLS_ERR_SSL_CONTINUE_PROCESSING ); } MBEDTLS_SSL_DEBUG_MSG( 2, ( "handshake message completed" ) ); @@ -3162,9 +3165,11 @@ int mbedtls_ssl_prepare_handshake_record( mbedtls_ssl_context *ssl ) int ret; unsigned int recv_msg_seq = ( ssl->in_msg[4] << 8 ) | ssl->in_msg[5]; - /* ssl->handshake is NULL when receiving ClientHello for renego */ if( ssl->handshake != NULL && - recv_msg_seq != ssl->handshake->in_msg_seq ) + ( ( ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER && + recv_msg_seq != ssl->handshake->in_msg_seq ) || + ( ssl->state == MBEDTLS_SSL_HANDSHAKE_OVER && + ssl->in_msg[0] != MBEDTLS_SSL_HS_CLIENT_HELLO ) ) ) { /* Retransmit only on last message from previous flight, to avoid * too many retransmissions. @@ -3191,7 +3196,7 @@ int mbedtls_ssl_prepare_handshake_record( mbedtls_ssl_context *ssl ) ssl->handshake->in_msg_seq ) ); } - return( MBEDTLS_ERR_SSL_WANT_READ ); + return( MBEDTLS_ERR_SSL_CONTINUE_PROCESSING ); } /* Wait until message completion to increment in_msg_seq */ @@ -3594,81 +3599,23 @@ static int ssl_parse_record_header( mbedtls_ssl_context *ssl ) return( MBEDTLS_ERR_SSL_INVALID_RECORD ); } - /* Check length against bounds of the current transform and version */ - if( ssl->transform_in == NULL ) - { - if( ssl->in_msglen < 1 || - ssl->in_msglen > MBEDTLS_SSL_MAX_CONTENT_LEN ) - { - MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); - return( MBEDTLS_ERR_SSL_INVALID_RECORD ); - } - } - else - { - if( ssl->in_msglen < ssl->transform_in->minlen ) - { - MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); - return( MBEDTLS_ERR_SSL_INVALID_RECORD ); - } - -#if defined(MBEDTLS_SSL_PROTO_SSL3) - if( ssl->minor_ver == MBEDTLS_SSL_MINOR_VERSION_0 && - ssl->in_msglen > ssl->transform_in->minlen + MBEDTLS_SSL_MAX_CONTENT_LEN ) - { - MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); - return( MBEDTLS_ERR_SSL_INVALID_RECORD ); - } -#endif -#if defined(MBEDTLS_SSL_PROTO_TLS1) || defined(MBEDTLS_SSL_PROTO_TLS1_1) || \ - defined(MBEDTLS_SSL_PROTO_TLS1_2) - /* - * TLS encrypted messages can have up to 256 bytes of padding - */ - if( ssl->minor_ver >= MBEDTLS_SSL_MINOR_VERSION_1 && - ssl->in_msglen > ssl->transform_in->minlen + - MBEDTLS_SSL_MAX_CONTENT_LEN + 256 ) - { - MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); - return( MBEDTLS_ERR_SSL_INVALID_RECORD ); - } -#endif - } - /* - * DTLS-related tests done last, because most of them may result in - * silently dropping the record (but not the whole datagram), and we only - * want to consider that after ensuring that the "basic" fields (type, - * version, length) are sane. + * DTLS-related tests. + * Check epoch before checking length constraint because + * the latter varies with the epoch. E.g., if a ChangeCipherSpec + * message gets duplicated before the corresponding Finished message, + * the second ChangeCipherSpec should be discarded because it belongs + * to an old epoch, but not because its length is shorter than + * the minimum record length for packets using the new record transform. + * Note that these two kinds of failures are handled differently, + * as an unexpected record is silently skipped but an invalid + * record leads to the entire datagram being dropped. */ #if defined(MBEDTLS_SSL_PROTO_DTLS) if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) { unsigned int rec_epoch = ( ssl->in_ctr[0] << 8 ) | ssl->in_ctr[1]; - /* Drop unexpected ChangeCipherSpec messages */ - if( ssl->in_msgtype == MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC && - ssl->state != MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC && - ssl->state != MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC ) - { - MBEDTLS_SSL_DEBUG_MSG( 1, ( "dropping unexpected ChangeCipherSpec" ) ); - return( MBEDTLS_ERR_SSL_UNEXPECTED_RECORD ); - } - - /* Drop unexpected ApplicationData records, - * except at the beginning of renegotiations */ - if( ssl->in_msgtype == MBEDTLS_SSL_MSG_APPLICATION_DATA && - ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER -#if defined(MBEDTLS_SSL_RENEGOTIATION) - && ! ( ssl->renego_status == MBEDTLS_SSL_RENEGOTIATION_IN_PROGRESS && - ssl->state == MBEDTLS_SSL_SERVER_HELLO ) -#endif - ) - { - MBEDTLS_SSL_DEBUG_MSG( 1, ( "dropping unexpected ApplicationData" ) ); - return( MBEDTLS_ERR_SSL_UNEXPECTED_RECORD ); - } - /* Check epoch (and sequence number) with DTLS */ if( rec_epoch != ssl->in_epoch ) { @@ -3708,9 +3655,74 @@ static int ssl_parse_record_header( mbedtls_ssl_context *ssl ) return( MBEDTLS_ERR_SSL_UNEXPECTED_RECORD ); } #endif + + /* Drop unexpected ChangeCipherSpec messages */ + if( ssl->in_msgtype == MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC && + ssl->state != MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC && + ssl->state != MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC ) + { + MBEDTLS_SSL_DEBUG_MSG( 1, ( "dropping unexpected ChangeCipherSpec" ) ); + return( MBEDTLS_ERR_SSL_UNEXPECTED_RECORD ); + } + + /* Drop unexpected ApplicationData records, + * except at the beginning of renegotiations */ + if( ssl->in_msgtype == MBEDTLS_SSL_MSG_APPLICATION_DATA && + ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER +#if defined(MBEDTLS_SSL_RENEGOTIATION) + && ! ( ssl->renego_status == MBEDTLS_SSL_RENEGOTIATION_IN_PROGRESS && + ssl->state == MBEDTLS_SSL_SERVER_HELLO ) +#endif + ) + { + MBEDTLS_SSL_DEBUG_MSG( 1, ( "dropping unexpected ApplicationData" ) ); + return( MBEDTLS_ERR_SSL_UNEXPECTED_RECORD ); + } } #endif /* MBEDTLS_SSL_PROTO_DTLS */ + + /* Check length against bounds of the current transform and version */ + if( ssl->transform_in == NULL ) + { + if( ssl->in_msglen < 1 || + ssl->in_msglen > MBEDTLS_SSL_MAX_CONTENT_LEN ) + { + MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); + return( MBEDTLS_ERR_SSL_INVALID_RECORD ); + } + } + else + { + if( ssl->in_msglen < ssl->transform_in->minlen ) + { + MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); + return( MBEDTLS_ERR_SSL_INVALID_RECORD ); + } + +#if defined(MBEDTLS_SSL_PROTO_SSL3) + if( ssl->minor_ver == MBEDTLS_SSL_MINOR_VERSION_0 && + ssl->in_msglen > ssl->transform_in->minlen + MBEDTLS_SSL_MAX_CONTENT_LEN ) + { + MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); + return( MBEDTLS_ERR_SSL_INVALID_RECORD ); + } +#endif +#if defined(MBEDTLS_SSL_PROTO_TLS1) || defined(MBEDTLS_SSL_PROTO_TLS1_1) || \ + defined(MBEDTLS_SSL_PROTO_TLS1_2) + /* + * TLS encrypted messages can have up to 256 bytes of padding + */ + if( ssl->minor_ver >= MBEDTLS_SSL_MINOR_VERSION_1 && + ssl->in_msglen > ssl->transform_in->minlen + + MBEDTLS_SSL_MAX_CONTENT_LEN + 256 ) + { + MBEDTLS_SSL_DEBUG_MSG( 1, ( "bad message length" ) ); + return( MBEDTLS_ERR_SSL_INVALID_RECORD ); + } +#endif + } + return( 0 ); } @@ -3799,7 +3811,10 @@ int mbedtls_ssl_read_record( mbedtls_ssl_context *ssl ) { do { - if( ( ret = mbedtls_ssl_read_record_layer( ssl ) ) != 0 ) + do ret = mbedtls_ssl_read_record_layer( ssl ); + while( ret == MBEDTLS_ERR_SSL_CONTINUE_PROCESSING ); + + if( ret != 0 ) { MBEDTLS_SSL_DEBUG_RET( 1, ( "mbedtls_ssl_read_record_layer" ), ret ); return( ret ); @@ -3807,11 +3822,12 @@ int mbedtls_ssl_read_record( mbedtls_ssl_context *ssl ) ret = mbedtls_ssl_handle_message_type( ssl ); - } while( MBEDTLS_ERR_SSL_NON_FATAL == ret ); + } while( MBEDTLS_ERR_SSL_NON_FATAL == ret || + MBEDTLS_ERR_SSL_CONTINUE_PROCESSING == ret ); if( 0 != ret ) { - MBEDTLS_SSL_DEBUG_RET( 1, ( "mbedtls_ssl_read_record_layer" ), ret ); + MBEDTLS_SSL_DEBUG_RET( 1, ( "mbedtls_ssl_handle_message_type" ), ret ); return( ret ); } @@ -3849,11 +3865,6 @@ int mbedtls_ssl_read_record_layer( mbedtls_ssl_context *ssl ) * (2) Alert messages: * Consume whole record content, in_msglen = 0. * - * NOTE: This needs to be fixed, since like for - * handshake messages it is allowed to have - * multiple alerts witin a single record. - * Internal reference IOTSSL-1321. - * * (3) Change cipher spec: * Consume whole record content, in_msglen = 0. * @@ -3881,12 +3892,12 @@ int mbedtls_ssl_read_record_layer( mbedtls_ssl_context *ssl ) */ /* Notes: - * (1) in_hslen is *NOT* necessarily the size of the + * (1) in_hslen is not necessarily the size of the * current handshake content: If DTLS handshake * fragmentation is used, that's the fragment * size instead. Using the total handshake message - * size here is FAULTY and should be changed at - * some point. Internal reference IOTSSL-1414. + * size here is faulty and should be changed at + * some point. * (2) While it doesn't seem to cause problems, one * has to be very careful not to assume that in_hslen * is always <= in_msglen in a sensible communication. @@ -3937,12 +3948,6 @@ int mbedtls_ssl_read_record_layer( mbedtls_ssl_context *ssl ) return( 0 ); } - /* Need to fetch a new record */ - -#if defined(MBEDTLS_SSL_PROTO_DTLS) -read_record_header: -#endif - /* Current record either fully processed or to be discarded. */ if( ( ret = mbedtls_ssl_fetch_input( ssl, mbedtls_ssl_hdr_len( ssl ) ) ) != 0 ) @@ -3977,7 +3982,7 @@ read_record_header: } /* Get next record */ - goto read_record_header; + return( MBEDTLS_ERR_SSL_CONTINUE_PROCESSING ); } #endif return( ret ); @@ -3996,7 +4001,13 @@ read_record_header: /* Done reading this record, get ready for the next one */ #if defined(MBEDTLS_SSL_PROTO_DTLS) if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) + { ssl->next_record_offset = ssl->in_msglen + mbedtls_ssl_hdr_len( ssl ); + if( ssl->next_record_offset < ssl->in_left ) + { + MBEDTLS_SSL_DEBUG_MSG( 3, ( "more than one record within datagram" ) ); + } + } else #endif ssl->in_left = 0; @@ -4043,7 +4054,7 @@ read_record_header: ssl->in_left = 0; MBEDTLS_SSL_DEBUG_MSG( 1, ( "discarding invalid record (mac)" ) ); - goto read_record_header; + return( MBEDTLS_ERR_SSL_CONTINUE_PROCESSING ); } return( ret ); @@ -4064,46 +4075,6 @@ read_record_header: } } - /* - * When we sent the last flight of the handshake, we MUST respond to a - * retransmit of the peer's previous flight with a retransmit. (In - * practice, only the Finished message will make it, other messages - * including CCS use the old transform so they're dropped as invalid.) - * - * If the record we received is not a handshake message, however, it - * means the peer received our last flight so we can clean up - * handshake info. - * - * This check needs to be done before prepare_handshake() due to an edge - * case: if the client immediately requests renegotiation, this - * finishes the current handshake first, avoiding the new ClientHello - * being mistaken for an ancient message in the current handshake. - */ -#if defined(MBEDTLS_SSL_PROTO_DTLS) - if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM && - ssl->handshake != NULL && - ssl->state == MBEDTLS_SSL_HANDSHAKE_OVER ) - { - if( ssl->in_msgtype == MBEDTLS_SSL_MSG_HANDSHAKE && - ssl->in_msg[0] == MBEDTLS_SSL_HS_FINISHED ) - { - MBEDTLS_SSL_DEBUG_MSG( 2, ( "received retransmit of last flight" ) ); - - if( ( ret = mbedtls_ssl_resend( ssl ) ) != 0 ) - { - MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ssl_resend", ret ); - return( ret ); - } - - return( MBEDTLS_ERR_SSL_WANT_READ ); - } - else - { - ssl_handshake_wrapup_free_hs_transform( ssl ); - } - } -#endif - return( 0 ); } @@ -4148,7 +4119,7 @@ int mbedtls_ssl_handle_message_type( mbedtls_ssl_context *ssl ) if( ssl->in_msg[0] == MBEDTLS_SSL_ALERT_LEVEL_WARNING && ssl->in_msg[1] == MBEDTLS_SSL_ALERT_MSG_NO_RENEGOTIATION ) { - MBEDTLS_SSL_DEBUG_MSG( 2, ( "is a SSLv3 no_cert" ) ); + MBEDTLS_SSL_DEBUG_MSG( 2, ( "is a SSLv3 no renegotiation alert" ) ); /* Will be handled when trying to parse ServerHello */ return( 0 ); } @@ -4170,6 +4141,15 @@ int mbedtls_ssl_handle_message_type( mbedtls_ssl_context *ssl ) return MBEDTLS_ERR_SSL_NON_FATAL; } +#if defined(MBEDTLS_SSL_PROTO_DTLS) + if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM && + ssl->handshake != NULL && + ssl->state == MBEDTLS_SSL_HANDSHAKE_OVER ) + { + ssl_handshake_wrapup_free_hs_transform( ssl ); + } +#endif + return( 0 ); } @@ -6506,6 +6486,61 @@ size_t mbedtls_ssl_get_bytes_avail( const mbedtls_ssl_context *ssl ) return( ssl->in_offt == NULL ? 0 : ssl->in_msglen ); } +int mbedtls_ssl_check_pending( const mbedtls_ssl_context *ssl ) +{ + /* + * Case A: We're currently holding back + * a message for further processing. + */ + + if( ssl->keep_current_message == 1 ) + { + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ssl_check_pending: record held back for processing" ) ); + return( 1 ); + } + + /* + * Case B: Further records are pending in the current datagram. + */ + +#if defined(MBEDTLS_SSL_PROTO_DTLS) + if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM && + ssl->in_left > ssl->next_record_offset ) + { + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ssl_check_pending: more records within current datagram" ) ); + return( 1 ); + } +#endif /* MBEDTLS_SSL_PROTO_DTLS */ + + /* + * Case C: A handshake message is being processed. + */ + + if( ssl->in_hslen > 0 && ssl->in_hslen < ssl->in_msglen ) + { + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ssl_check_pending: more handshake messages within current record" ) ); + return( 1 ); + } + + /* + * Case D: An application data message is being processed + */ + if( ssl->in_offt != NULL ) + { + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ssl_check_pending: application data record is being processed" ) ); + return( 1 ); + } + + /* + * In all other cases, the rest of the message can be dropped. + * As in ssl_read_record_layer, this needs to be adapted if + * we implement support for multiple alerts in single records. + */ + + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ssl_check_pending: nothing pending" ) ); + return( 0 ); +} + uint32_t mbedtls_ssl_get_verify_result( const mbedtls_ssl_context *ssl ) { if( ssl->session != NULL ) @@ -6914,25 +6949,16 @@ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ) } /* - * TODO - * - * The logic should be streamlined here: - * - * Instead of - * + * The logic could be streamlined here. Instead of * - Manually checking whether ssl->in_offt is NULL * - Fetching a new record if yes * - Setting ssl->in_offt if one finds an application record * - Resetting keep_current_message after handling the application data - * * one should - * * - Adapt read_record to set ssl->in_offt automatically * when a new application data record is processed. * - Always call mbedtls_ssl_read_record here. - * * This way, the logic of ssl_read would be much clearer: - * * (1) Always call record layer and see what kind of record is on * and have it ready for consumption (in particular, in_offt * properly set for application data records). @@ -6942,13 +6968,12 @@ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ) * (3) If it's something different from application data, * handle it accordingly, e.g. potentially start a * renegotiation. - * * This will also remove the need to manually reset * ssl->keep_current_message = 0 below. - * */ - if( ssl->in_offt == NULL ) + /* Loop as long as no application data record is available */ + while( ssl->in_offt == NULL ) { /* Start timer if not already running */ if( ssl->f_get_timer != NULL && @@ -7002,7 +7027,9 @@ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ) /* With DTLS, drop the packet (probably from last handshake) */ #if defined(MBEDTLS_SSL_PROTO_DTLS) if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) - return( MBEDTLS_ERR_SSL_WANT_READ ); + { + continue; + } #endif return( MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE ); } @@ -7017,7 +7044,9 @@ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ) /* With DTLS, drop the packet (probably from last handshake) */ #if defined(MBEDTLS_SSL_PROTO_DTLS) if( ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM ) - return( MBEDTLS_ERR_SSL_WANT_READ ); + { + continue; + } #endif return( MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE ); } @@ -7090,7 +7119,25 @@ int mbedtls_ssl_read( mbedtls_ssl_context *ssl, unsigned char *buf, size_t len ) } } - return( MBEDTLS_ERR_SSL_WANT_READ ); + /* At this point, we don't know whether the renegotiation has been + * completed or not. The cases to consider are the following: + * 1) The renegotiation is complete. In this case, no new record + * has been read yet. + * 2) The renegotiation is incomplete because the client received + * an application data record while awaiting the ServerHello. + * 3) The renegotiation is incomplete because the client received + * a non-handshake, non-application data message while awaiting + * the ServerHello. + * In each of these case, looping will be the proper action: + * - For 1), the next iteration will read a new record and check + * if it's application data. + * - For 2), the loop condition isn't satisfied as application data + * is present, hence continue is the same as break + * - For 3), the loop condition is satisfied and read_record + * will re-deliver the message that was held back by the client + * when expecting the ServerHello. + */ + continue; } #if defined(MBEDTLS_SSL_RENEGOTIATION) else if( ssl->renego_status == MBEDTLS_SSL_RENEGOTIATION_PENDING ) diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index 8e2feb1a1..5d8969dbc 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -73,6 +73,7 @@ int main( void ) #define DFL_REQUEST_SIZE -1 #define DFL_DEBUG_LEVEL 0 #define DFL_NBIO 0 +#define DFL_EVENT 0 #define DFL_READ_TIMEOUT 0 #define DFL_MAX_RESEND 0 #define DFL_CA_FILE "" @@ -245,24 +246,26 @@ int main( void ) " server_addr=%%s default: given by name\n" \ " server_port=%%d default: 4433\n" \ " request_page=%%s default: \".\"\n" \ - " request_size=%%d default: about 34 (basic request)\n" \ + " request_size=%%d default: about 34 (basic request)\n" \ " (minimum: 0, max: " MAX_REQUEST_SIZE_STR " )\n" \ - " debug_level=%%d default: 0 (disabled)\n" \ - " nbio=%%d default: 0 (blocking I/O)\n" \ - " options: 1 (non-blocking), 2 (added delays)\n" \ - " read_timeout=%%d default: 0 ms (no timeout)\n" \ + " debug_level=%%d default: 0 (disabled)\n" \ + " nbio=%%d default: 0 (blocking I/O)\n" \ + " options: 1 (non-blocking), 2 (added delays)\n" \ + " event=%%d default: 0 (loop)\n" \ + " options: 1 (level-triggered, implies nbio=1),\n" \ + " read_timeout=%%d default: 0 ms (no timeout)\n" \ " max_resend=%%d default: 0 (no resend on timeout)\n" \ "\n" \ USAGE_DTLS \ "\n" \ - " auth_mode=%%s default: (library default: none)\n" \ + " auth_mode=%%s default: (library default: none)\n" \ " options: none, optional, required\n" \ USAGE_IO \ "\n" \ USAGE_PSK \ USAGE_ECJPAKE \ "\n" \ - " allow_legacy=%%d default: (library default: no)\n" \ + " allow_legacy=%%d default: (library default: no)\n" \ USAGE_RENEGO \ " exchanges=%%d default: 1\n" \ " reconnect=%%d default: 0 (disabled)\n" \ @@ -302,7 +305,8 @@ struct options const char *server_port; /* port on which the ssl service runs */ int debug_level; /* level of debugging */ int nbio; /* should I/O be blocking? */ - uint32_t read_timeout; /* timeout on mbedtls_ssl_read() in milliseconds */ + int event; /* loop or event-driven IO? level or edge triggered? */ + uint32_t read_timeout; /* timeout on mbedtls_ssl_read() in milliseconds */ int max_resend; /* DTLS times to resend on read timeout */ const char *request_page; /* page on server to request */ int request_size; /* pad request with header to requested size */ @@ -353,7 +357,8 @@ static void my_debug( void *ctx, int level, if( *p == '/' || *p == '\\' ) basename = p + 1; - mbedtls_fprintf( (FILE *) ctx, "%s:%04d: |%d| %s", basename, line, level, str ); + mbedtls_fprintf( (FILE *) ctx, "%s:%04d: |%d| %s", + basename, line, level, str ); fflush( (FILE *) ctx ); } @@ -399,7 +404,8 @@ static int my_send( void *ctx, const unsigned char *buf, size_t len ) /* * Enabled if debug_level > 1 in code below */ -static int my_verify( void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags ) +static int my_verify( void *data, mbedtls_x509_crt *crt, + int depth, uint32_t *flags ) { char buf[1024]; ((void) data); @@ -436,6 +442,57 @@ static int ssl_sig_hashes_for_test[] = { }; #endif /* MBEDTLS_X509_CRT_PARSE_C */ +/* + * Wait for an event from the underlying transport or the timer + * (Used in event-driven IO mode). + */ +#if !defined(MBEDTLS_TIMING_C) +int idle( mbedtls_net_context *fd, + int idle_reason ) +#else +int idle( mbedtls_net_context *fd, + mbedtls_timing_delay_context *timer, + int idle_reason ) +#endif +{ + + int ret; + int poll_type = 0; + + if( idle_reason == MBEDTLS_ERR_SSL_WANT_WRITE ) + poll_type = MBEDTLS_NET_POLL_WRITE; + else if( idle_reason == MBEDTLS_ERR_SSL_WANT_READ ) + poll_type = MBEDTLS_NET_POLL_READ; +#if !defined(MBEDTLS_TIMING_C) + else + return( 0 ); +#endif + + while( 1 ) + { + /* Check if timer has expired */ +#if defined(MBEDTLS_TIMING_C) + if( timer != NULL && + mbedtls_timing_get_delay( timer ) == 2 ) + { + break; + } +#endif /* MBEDTLS_TIMING_C */ + + /* Check if underlying transport became available */ + if( poll_type != 0 ) + { + ret = mbedtls_net_poll( fd, poll_type, 0 ); + if( ret < 0 ) + return( ret ); + if( ret == poll_type ) + break; + } + } + + return( 0 ); +} + int main( int argc, char *argv[] ) { int ret = 0, len, tail_len, i, written, frags, retry_left; @@ -521,6 +578,7 @@ int main( int argc, char *argv[] ) opt.server_port = DFL_SERVER_PORT; opt.debug_level = DFL_DEBUG_LEVEL; opt.nbio = DFL_NBIO; + opt.event = DFL_EVENT; opt.read_timeout = DFL_READ_TIMEOUT; opt.max_resend = DFL_MAX_RESEND; opt.request_page = DFL_REQUEST_PAGE; @@ -594,6 +652,12 @@ int main( int argc, char *argv[] ) if( opt.nbio < 0 || opt.nbio > 2 ) goto usage; } + else if( strcmp( p, "event" ) == 0 ) + { + opt.event = atoi( q ); + if( opt.event < 0 || opt.event > 2 ) + goto usage; + } else if( strcmp( p, "read_timeout" ) == 0 ) opt.read_timeout = atoi( q ); else if( strcmp( p, "max_resend" ) == 0 ) @@ -638,16 +702,23 @@ int main( int argc, char *argv[] ) } else if( strcmp( p, "renegotiation" ) == 0 ) { - opt.renegotiation = (atoi( q )) ? MBEDTLS_SSL_RENEGOTIATION_ENABLED : - MBEDTLS_SSL_RENEGOTIATION_DISABLED; + opt.renegotiation = (atoi( q )) ? + MBEDTLS_SSL_RENEGOTIATION_ENABLED : + MBEDTLS_SSL_RENEGOTIATION_DISABLED; } else if( strcmp( p, "allow_legacy" ) == 0 ) { switch( atoi( q ) ) { - case -1: opt.allow_legacy = MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE; break; - case 0: opt.allow_legacy = MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION; break; - case 1: opt.allow_legacy = MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION; break; + case -1: + opt.allow_legacy = MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE; + break; + case 0: + opt.allow_legacy = MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION; + break; + case 1: + opt.allow_legacy = MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION; + break; default: goto usage; } } @@ -704,8 +775,12 @@ int main( int argc, char *argv[] ) { switch( atoi( q ) ) { - case 0: opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_DISABLED; break; - case 1: opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_ENABLED; break; + case 0: + opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_DISABLED; + break; + case 1: + opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_ENABLED; + break; default: goto usage; } } @@ -864,6 +939,15 @@ int main( int argc, char *argv[] ) goto usage; } + /* Event-driven IO is incompatible with the above custom + * receive and send functions, as the polling builds on + * refers to the underlying net_context. */ + if( opt.event == 1 && opt.nbio != 1 ) + { + mbedtls_printf( "Warning: event-driven IO mandates nbio=1 - overwrite\n" ); + opt.nbio = 1; + } + #if defined(MBEDTLS_DEBUG_C) mbedtls_debug_set_threshold( opt.debug_level ); #endif @@ -871,19 +955,20 @@ int main( int argc, char *argv[] ) if( opt.force_ciphersuite[0] > 0 ) { const mbedtls_ssl_ciphersuite_t *ciphersuite_info; - ciphersuite_info = mbedtls_ssl_ciphersuite_from_id( opt.force_ciphersuite[0] ); + ciphersuite_info = + mbedtls_ssl_ciphersuite_from_id( opt.force_ciphersuite[0] ); if( opt.max_version != -1 && ciphersuite_info->min_minor_ver > opt.max_version ) { - mbedtls_printf("forced ciphersuite not allowed with this protocol version\n"); + mbedtls_printf( "forced ciphersuite not allowed with this protocol version\n" ); ret = 2; goto usage; } if( opt.min_version != -1 && ciphersuite_info->max_minor_ver < opt.min_version ) { - mbedtls_printf("forced ciphersuite not allowed with this protocol version\n"); + mbedtls_printf( "forced ciphersuite not allowed with this protocol version\n" ); ret = 2; goto usage; } @@ -909,7 +994,7 @@ int main( int argc, char *argv[] ) { if( opt.arc4 == MBEDTLS_SSL_ARC4_DISABLED ) { - mbedtls_printf("forced RC4 ciphersuite with RC4 disabled\n"); + mbedtls_printf( "forced RC4 ciphersuite with RC4 disabled\n" ); ret = 2; goto usage; } @@ -929,7 +1014,7 @@ int main( int argc, char *argv[] ) if( strlen( opt.psk ) % 2 != 0 ) { - mbedtls_printf("pre-shared key not valid hex\n"); + mbedtls_printf( "pre-shared key not valid hex\n" ); goto exit; } @@ -946,7 +1031,7 @@ int main( int argc, char *argv[] ) c -= 'A' - 10; else { - mbedtls_printf("pre-shared key not valid hex\n"); + mbedtls_printf( "pre-shared key not valid hex\n" ); goto exit; } psk[ j / 2 ] = c << 4; @@ -960,7 +1045,7 @@ int main( int argc, char *argv[] ) c -= 'A' - 10; else { - mbedtls_printf("pre-shared key not valid hex\n"); + mbedtls_printf( "pre-shared key not valid hex\n" ); goto exit; } psk[ j / 2 ] |= c; @@ -1051,11 +1136,12 @@ int main( int argc, char *argv[] ) fflush( stdout ); mbedtls_entropy_init( &entropy ); - if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, - (const unsigned char *) pers, - strlen( pers ) ) ) != 0 ) + if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, + &entropy, (const unsigned char *) pers, + strlen( pers ) ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ctr_drbg_seed returned -0x%x\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ctr_drbg_seed returned -0x%x\n", + -ret ); goto exit; } @@ -1093,12 +1179,13 @@ int main( int argc, char *argv[] ) #else { ret = 1; - mbedtls_printf("MBEDTLS_CERTS_C not defined."); + mbedtls_printf( "MBEDTLS_CERTS_C not defined." ); } #endif if( ret < 0 ) { - mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", + -ret ); goto exit; } @@ -1121,7 +1208,8 @@ int main( int argc, char *argv[] ) else #endif #if defined(MBEDTLS_CERTS_C) - ret = mbedtls_x509_crt_parse( &clicert, (const unsigned char *) mbedtls_test_cli_crt, + ret = mbedtls_x509_crt_parse( &clicert, + (const unsigned char *) mbedtls_test_cli_crt, mbedtls_test_cli_crt_len ); #else { @@ -1131,7 +1219,8 @@ int main( int argc, char *argv[] ) #endif if( ret != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", + -ret ); goto exit; } @@ -1144,7 +1233,8 @@ int main( int argc, char *argv[] ) else #endif #if defined(MBEDTLS_CERTS_C) - ret = mbedtls_pk_parse_key( &pkey, (const unsigned char *) mbedtls_test_cli_key, + ret = mbedtls_pk_parse_key( &pkey, + (const unsigned char *) mbedtls_test_cli_key, mbedtls_test_cli_key_len, NULL, 0 ); #else { @@ -1154,7 +1244,8 @@ int main( int argc, char *argv[] ) #endif if( ret != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_pk_parse_key returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_pk_parse_key returned -0x%x\n\n", + -ret ); goto exit; } @@ -1172,11 +1263,13 @@ int main( int argc, char *argv[] ) opt.server_addr, opt.server_port ); fflush( stdout ); - if( ( ret = mbedtls_net_connect( &server_fd, opt.server_addr, opt.server_port, - opt.transport == MBEDTLS_SSL_TRANSPORT_STREAM ? - MBEDTLS_NET_PROTO_TCP : MBEDTLS_NET_PROTO_UDP ) ) != 0 ) + if( ( ret = mbedtls_net_connect( &server_fd, + opt.server_addr, opt.server_port, + opt.transport == MBEDTLS_SSL_TRANSPORT_STREAM ? + MBEDTLS_NET_PROTO_TCP : MBEDTLS_NET_PROTO_UDP ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_net_connect returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_net_connect returned -0x%x\n\n", + -ret ); goto exit; } @@ -1186,7 +1279,8 @@ int main( int argc, char *argv[] ) ret = mbedtls_net_set_block( &server_fd ); if( ret != 0 ) { - mbedtls_printf( " failed\n ! net_set_(non)block() returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! net_set_(non)block() returned -0x%x\n\n", + -ret ); goto exit; } @@ -1203,7 +1297,8 @@ int main( int argc, char *argv[] ) opt.transport, MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_config_defaults returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_config_defaults returned -0x%x\n\n", + -ret ); goto exit; } @@ -1226,13 +1321,15 @@ int main( int argc, char *argv[] ) #if defined(MBEDTLS_SSL_PROTO_DTLS) if( opt.hs_to_min != DFL_HS_TO_MIN || opt.hs_to_max != DFL_HS_TO_MAX ) - mbedtls_ssl_conf_handshake_timeout( &conf, opt.hs_to_min, opt.hs_to_max ); + mbedtls_ssl_conf_handshake_timeout( &conf, opt.hs_to_min, + opt.hs_to_max ); #endif /* MBEDTLS_SSL_PROTO_DTLS */ #if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) if( ( ret = mbedtls_ssl_conf_max_frag_len( &conf, opt.mfl_code ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_conf_max_frag_len returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_conf_max_frag_len returned %d\n\n", + ret ); goto exit; } #endif @@ -1255,8 +1352,8 @@ int main( int argc, char *argv[] ) #if defined(MBEDTLS_SSL_CBC_RECORD_SPLITTING) if( opt.recsplit != DFL_RECSPLIT ) mbedtls_ssl_conf_cbc_record_splitting( &conf, opt.recsplit - ? MBEDTLS_SSL_CBC_RECORD_SPLITTING_ENABLED - : MBEDTLS_SSL_CBC_RECORD_SPLITTING_DISABLED ); + ? MBEDTLS_SSL_CBC_RECORD_SPLITTING_ENABLED + : MBEDTLS_SSL_CBC_RECORD_SPLITTING_DISABLED ); #endif #if defined(MBEDTLS_DHM_C) @@ -1268,7 +1365,8 @@ int main( int argc, char *argv[] ) if( opt.alpn_string != NULL ) if( ( ret = mbedtls_ssl_conf_alpn_protocols( &conf, alpn_list ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_conf_alpn_protocols returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_conf_alpn_protocols returned %d\n\n", + ret ); goto exit; } #endif @@ -1307,7 +1405,8 @@ int main( int argc, char *argv[] ) { if( ( ret = mbedtls_ssl_conf_own_cert( &conf, &clicert, &pkey ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_conf_own_cert returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_conf_own_cert returned %d\n\n", + ret ); goto exit; } } @@ -1326,16 +1425,19 @@ int main( int argc, char *argv[] ) (const unsigned char *) opt.psk_identity, strlen( opt.psk_identity ) ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_conf_psk returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_conf_psk returned %d\n\n", + ret ); goto exit; } #endif if( opt.min_version != DFL_MIN_VERSION ) - mbedtls_ssl_conf_min_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, opt.min_version ); + mbedtls_ssl_conf_min_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, + opt.min_version ); if( opt.max_version != DFL_MAX_VERSION ) - mbedtls_ssl_conf_max_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, opt.max_version ); + mbedtls_ssl_conf_max_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, + opt.max_version ); #if defined(MBEDTLS_SSL_FALLBACK_SCSV) if( opt.fallback != DFL_FALLBACK ) @@ -1344,14 +1446,16 @@ int main( int argc, char *argv[] ) if( ( ret = mbedtls_ssl_setup( &ssl, &conf ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_setup returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_setup returned -0x%x\n\n", + -ret ); goto exit; } #if defined(MBEDTLS_X509_CRT_PARSE_C) if( ( ret = mbedtls_ssl_set_hostname( &ssl, opt.server_name ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", + ret ); goto exit; } #endif @@ -1363,7 +1467,8 @@ int main( int argc, char *argv[] ) (const unsigned char *) opt.ecjpake_pw, strlen( opt.ecjpake_pw ) ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_set_hs_ecjpake_password returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_set_hs_ecjpake_password returned %d\n\n", + ret ); goto exit; } } @@ -1372,7 +1477,8 @@ int main( int argc, char *argv[] ) 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, + mbedtls_ssl_set_bio( &ssl, &server_fd, + mbedtls_net_send, mbedtls_net_recv, opt.nbio == 0 ? mbedtls_net_recv_timeout : NULL ); #if defined(MBEDTLS_TIMING_C) @@ -1390,9 +1496,11 @@ int main( int argc, char *argv[] ) while( ( ret = mbedtls_ssl_handshake( &ssl ) ) != 0 ) { - if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n", + -ret ); if( ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ) mbedtls_printf( " Unable to verify the server's certificate. " @@ -1404,10 +1512,23 @@ int main( int argc, char *argv[] ) mbedtls_printf( "\n" ); goto exit; } + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + ret = idle( &server_fd, &timer, ret ); +#else + ret = idle( &server_fd, ret ); +#endif + if( ret != 0 ) + goto exit; + } } mbedtls_printf( " ok\n [ Protocol is %s ]\n [ Ciphersuite is %s ]\n", - mbedtls_ssl_get_version( &ssl ), mbedtls_ssl_get_ciphersuite( &ssl ) ); + mbedtls_ssl_get_version( &ssl ), + mbedtls_ssl_get_ciphersuite( &ssl ) ); if( ( ret = mbedtls_ssl_get_record_expansion( &ssl ) ) >= 0 ) mbedtls_printf( " [ Record expansion is %d ]\n", ret ); @@ -1435,7 +1556,8 @@ int main( int argc, char *argv[] ) if( ( ret = mbedtls_ssl_get_session( &ssl, &saved_session ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_get_session returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_get_session returned -0x%x\n\n", + -ret ); goto exit; } @@ -1454,7 +1576,8 @@ int main( int argc, char *argv[] ) mbedtls_printf( " failed\n" ); - mbedtls_x509_crt_verify_info( vrfy_buf, sizeof( vrfy_buf ), " ! ", flags ); + mbedtls_x509_crt_verify_info( vrfy_buf, sizeof( vrfy_buf ), + " ! ", flags ); mbedtls_printf( "%s\n", vrfy_buf ); } @@ -1484,9 +1607,21 @@ int main( int argc, char *argv[] ) if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_renegotiate returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_renegotiate returned %d\n\n", + ret ); goto exit; } + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &server_fd, &timer, ret ); +#else + idle( &server_fd, ret ); +#endif + } + } mbedtls_printf( " ok\n" ); } @@ -1530,27 +1665,54 @@ send_request: { for( written = 0, frags = 0; written < len; written += ret, frags++ ) { - while( ( ret = mbedtls_ssl_write( &ssl, buf + written, len - written ) ) - <= 0 ) + while( ( ret = mbedtls_ssl_write( &ssl, buf + written, + len - written ) ) <= 0 ) { if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_write returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_write returned -0x%x\n\n", + -ret ); goto exit; } + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &server_fd, &timer, ret ); +#else + idle( &server_fd, ret ); +#endif + } } } } else /* Not stream, so datagram */ { - do ret = mbedtls_ssl_write( &ssl, buf, len ); - while( ret == MBEDTLS_ERR_SSL_WANT_READ || - ret == MBEDTLS_ERR_SSL_WANT_WRITE ); + while( 1 ) + { + ret = mbedtls_ssl_write( &ssl, buf, len ); + + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + break; + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &server_fd, &timer, ret ); +#else + idle( &server_fd, ret ); +#endif + } + } if( ret < 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_write returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_write returned %d\n\n", + ret ); goto exit; } @@ -1565,7 +1727,8 @@ send_request: } buf[written] = '\0'; - mbedtls_printf( " %d bytes written in %d fragments\n\n%s\n", written, frags, (char *) buf ); + mbedtls_printf( " %d bytes written in %d fragments\n\n%s\n", + written, frags, (char *) buf ); /* * 7. Read the HTTP response @@ -1586,7 +1749,18 @@ send_request: if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ) + { + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &server_fd, &timer, ret ); +#else + idle( &server_fd, ret ); +#endif + } continue; + } if( ret <= 0 ) { @@ -1604,7 +1778,8 @@ send_request: goto reconnect; default: - mbedtls_printf( " mbedtls_ssl_read returned -0x%x\n", -ret ); + mbedtls_printf( " mbedtls_ssl_read returned -0x%x\n", + -ret ); goto exit; } } @@ -1628,9 +1803,24 @@ send_request: len = sizeof( buf ) - 1; memset( buf, 0, sizeof( buf ) ); - do ret = mbedtls_ssl_read( &ssl, buf, len ); - while( ret == MBEDTLS_ERR_SSL_WANT_READ || - ret == MBEDTLS_ERR_SSL_WANT_WRITE ); + while( 1 ) + { + ret = mbedtls_ssl_read( &ssl, buf, len ); + + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + break; + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &server_fd, &timer, ret ); +#else + idle( &server_fd, ret ); +#endif + } + } if( ret <= 0 ) { @@ -1671,7 +1861,8 @@ send_request: if( ( ret = mbedtls_ssl_session_reset( &ssl ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_session_reset returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_session_reset returned -0x%x\n\n", + -ret ); goto exit; } @@ -1680,9 +1871,20 @@ send_request: if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n\n", + -ret ); goto exit; } + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &server_fd, &timer, ret ); +#else + idle( &server_fd, ret ); +#endif + } } mbedtls_printf( " ok\n" ); @@ -1729,21 +1931,25 @@ reconnect: if( ( ret = mbedtls_ssl_session_reset( &ssl ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_session_reset returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_session_reset returned -0x%x\n\n", + -ret ); goto exit; } if( ( ret = mbedtls_ssl_set_session( &ssl, &saved_session ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_conf_session returned %d\n\n", ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_conf_session returned %d\n\n", + ret ); goto exit; } - if( ( ret = mbedtls_net_connect( &server_fd, opt.server_addr, opt.server_port, - opt.transport == MBEDTLS_SSL_TRANSPORT_STREAM ? - MBEDTLS_NET_PROTO_TCP : MBEDTLS_NET_PROTO_UDP ) ) != 0 ) + if( ( ret = mbedtls_net_connect( &server_fd, + opt.server_addr, opt.server_port, + opt.transport == MBEDTLS_SSL_TRANSPORT_STREAM ? + MBEDTLS_NET_PROTO_TCP : MBEDTLS_NET_PROTO_UDP ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_net_connect returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_net_connect returned -0x%x\n\n", + -ret ); goto exit; } @@ -1754,7 +1960,7 @@ reconnect: if( ret != 0 ) { mbedtls_printf( " failed\n ! net_set_(non)block() returned -0x%x\n\n", - -ret ); + -ret ); goto exit; } @@ -1763,7 +1969,8 @@ reconnect: if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { - mbedtls_printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_handshake returned -0x%x\n\n", + -ret ); goto exit; } } diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index cc29b493f..6bfb210f2 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -101,6 +101,7 @@ int main( void ) #define DFL_SERVER_PORT "4433" #define DFL_DEBUG_LEVEL 0 #define DFL_NBIO 0 +#define DFL_EVENT 0 #define DFL_READ_TIMEOUT 0 #define DFL_CA_FILE "" #define DFL_CA_PATH "" @@ -331,6 +332,8 @@ int main( void ) " debug_level=%%d default: 0 (disabled)\n" \ " nbio=%%d default: 0 (blocking I/O)\n" \ " options: 1 (non-blocking), 2 (added delays)\n" \ + " event=%%d default: 0 (loop)\n" \ + " options: 1 (level-triggered, implies nbio=1),\n" \ " read_timeout=%%d default: 0 ms (no timeout)\n" \ "\n" \ USAGE_DTLS \ @@ -399,6 +402,7 @@ struct options const char *server_port; /* port on which the ssl service runs */ int debug_level; /* level of debugging */ int nbio; /* should I/O be blocking? */ + int event; /* loop or event-driven IO? level or edge triggered? */ uint32_t read_timeout; /* timeout on mbedtls_ssl_read() in milliseconds */ const char *ca_file; /* the file with the CA certificate(s) */ const char *ca_path; /* the path with the CA certificate(s) reside */ @@ -837,6 +841,56 @@ static int ssl_sig_hashes_for_test[] = { }; #endif /* MBEDTLS_X509_CRT_PARSE_C */ +/* + * Wait for an event from the underlying transport or the timer + * (Used in event-driven IO mode). + */ +#if !defined(MBEDTLS_TIMING_C) +int idle( mbedtls_net_context *fd, + int idle_reason ) +#else +int idle( mbedtls_net_context *fd, + mbedtls_timing_delay_context *timer, + int idle_reason ) +#endif +{ + int ret; + int poll_type = 0; + + if( idle_reason == MBEDTLS_ERR_SSL_WANT_WRITE ) + poll_type = MBEDTLS_NET_POLL_WRITE; + else if( idle_reason == MBEDTLS_ERR_SSL_WANT_READ ) + poll_type = MBEDTLS_NET_POLL_READ; +#if !defined(MBEDTLS_TIMING_C) + else + return( 0 ); +#endif + + while( 1 ) + { + /* Check if timer has expired */ +#if defined(MBEDTLS_TIMING_C) + if( timer != NULL && + mbedtls_timing_get_delay( timer ) == 2 ) + { + break; + } +#endif /* MBEDTLS_TIMING_C */ + + /* Check if underlying transport became available */ + if( poll_type != 0 ) + { + ret = mbedtls_net_poll( fd, poll_type, 0 ); + if( ret < 0 ) + return( ret ); + if( ret == poll_type ) + break; + } + } + + return( 0 ); +} + int main( int argc, char *argv[] ) { int ret = 0, len, written, frags, exchanges_left; @@ -969,6 +1023,7 @@ int main( int argc, char *argv[] ) opt.server_addr = DFL_SERVER_ADDR; opt.server_port = DFL_SERVER_PORT; opt.debug_level = DFL_DEBUG_LEVEL; + opt.event = DFL_EVENT; opt.nbio = DFL_NBIO; opt.read_timeout = DFL_READ_TIMEOUT; opt.ca_file = DFL_CA_FILE; @@ -1047,6 +1102,12 @@ int main( int argc, char *argv[] ) if( opt.nbio < 0 || opt.nbio > 2 ) goto usage; } + else if( strcmp( p, "event" ) == 0 ) + { + opt.event = atoi( q ); + if( opt.event < 0 || opt.event > 2 ) + goto usage; + } else if( strcmp( p, "read_timeout" ) == 0 ) opt.read_timeout = atoi( q ); else if( strcmp( p, "ca_file" ) == 0 ) @@ -1088,16 +1149,23 @@ int main( int argc, char *argv[] ) opt.version_suites = q; else if( strcmp( p, "renegotiation" ) == 0 ) { - opt.renegotiation = (atoi( q )) ? MBEDTLS_SSL_RENEGOTIATION_ENABLED : - MBEDTLS_SSL_RENEGOTIATION_DISABLED; + opt.renegotiation = (atoi( q )) ? + MBEDTLS_SSL_RENEGOTIATION_ENABLED : + MBEDTLS_SSL_RENEGOTIATION_DISABLED; } else if( strcmp( p, "allow_legacy" ) == 0 ) { switch( atoi( q ) ) { - case -1: opt.allow_legacy = MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE; break; - case 0: opt.allow_legacy = MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION; break; - case 1: opt.allow_legacy = MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION; break; + case -1: + opt.allow_legacy = MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE; + break; + case 0: + opt.allow_legacy = MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION; + break; + case 1: + opt.allow_legacy = MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION; + break; default: goto usage; } } @@ -1254,8 +1322,12 @@ int main( int argc, char *argv[] ) { switch( atoi( q ) ) { - case 0: opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_DISABLED; break; - case 1: opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_ENABLED; break; + case 0: + opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_DISABLED; + break; + case 1: + opt.extended_ms = MBEDTLS_SSL_EXTENDED_MS_ENABLED; + break; default: goto usage; } } @@ -1328,6 +1400,15 @@ int main( int argc, char *argv[] ) goto usage; } + /* Event-driven IO is incompatible with the above custom + * receive and send functions, as the polling builds on + * refers to the underlying net_context. */ + if( opt.event == 1 && opt.nbio != 1 ) + { + mbedtls_printf( "Warning: event-driven IO mandates nbio=1 - overwrite\n" ); + opt.nbio = 1; + } + #if defined(MBEDTLS_DEBUG_C) mbedtls_debug_set_threshold( opt.debug_level ); #endif @@ -1335,19 +1416,20 @@ int main( int argc, char *argv[] ) if( opt.force_ciphersuite[0] > 0 ) { const mbedtls_ssl_ciphersuite_t *ciphersuite_info; - ciphersuite_info = mbedtls_ssl_ciphersuite_from_id( opt.force_ciphersuite[0] ); + ciphersuite_info = + mbedtls_ssl_ciphersuite_from_id( opt.force_ciphersuite[0] ); if( opt.max_version != -1 && ciphersuite_info->min_minor_ver > opt.max_version ) { - mbedtls_printf("forced ciphersuite not allowed with this protocol version\n"); + mbedtls_printf( "forced ciphersuite not allowed with this protocol version\n" ); ret = 2; goto usage; } if( opt.min_version != -1 && ciphersuite_info->max_minor_ver < opt.min_version ) { - mbedtls_printf("forced ciphersuite not allowed with this protocol version\n"); + mbedtls_printf( "forced ciphersuite not allowed with this protocol version\n" ); ret = 2; goto usage; } @@ -1526,11 +1608,12 @@ int main( int argc, char *argv[] ) fflush( stdout ); mbedtls_entropy_init( &entropy ); - if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, - (const unsigned char *) pers, - strlen( pers ) ) ) != 0 ) + if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, + &entropy, (const unsigned char *) pers, + strlen( pers ) ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_ctr_drbg_seed returned -0x%x\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ctr_drbg_seed returned -0x%x\n", + -ret ); goto exit; } @@ -1627,7 +1710,7 @@ int main( int argc, char *argv[] ) if( ( ret = mbedtls_pk_parse_keyfile( &pkey2, opt.key_file2, "" ) ) != 0 ) { mbedtls_printf( " failed\n ! mbedtls_pk_parse_keyfile(2) returned -0x%x\n\n", - -ret ); + -ret ); goto exit; } } @@ -1645,8 +1728,7 @@ int main( int argc, char *argv[] ) strcmp( opt.key_file2, "none" ) != 0 ) { #if !defined(MBEDTLS_CERTS_C) - mbedtls_printf( "Not certificated or key provided, and \n" - "MBEDTLS_CERTS_C not defined!\n" ); + mbedtls_printf( "Not certificated or key provided, and \nMBEDTLS_CERTS_C not defined!\n" ); goto exit; #else #if defined(MBEDTLS_RSA_C) @@ -1654,14 +1736,16 @@ int main( int argc, char *argv[] ) (const unsigned char *) mbedtls_test_srv_crt_rsa, mbedtls_test_srv_crt_rsa_len ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", + -ret ); goto exit; } if( ( ret = mbedtls_pk_parse_key( &pkey, (const unsigned char *) mbedtls_test_srv_key_rsa, mbedtls_test_srv_key_rsa_len, NULL, 0 ) ) != 0 ) { - mbedtls_printf( " failed\n ! mbedtls_pk_parse_key returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_pk_parse_key returned -0x%x\n\n", + -ret ); goto exit; } key_cert_init = 2; @@ -1671,14 +1755,16 @@ int main( int argc, char *argv[] ) (const unsigned char *) mbedtls_test_srv_crt_ec, mbedtls_test_srv_crt_ec_len ) ) != 0 ) { - mbedtls_printf( " failed\n ! x509_crt_parse2 returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! x509_crt_parse2 returned -0x%x\n\n", + -ret ); goto exit; } if( ( ret = mbedtls_pk_parse_key( &pkey2, (const unsigned char *) mbedtls_test_srv_key_ec, mbedtls_test_srv_key_ec_len, NULL, 0 ) ) != 0 ) { - mbedtls_printf( " failed\n ! pk_parse_key2 returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! pk_parse_key2 returned -0x%x\n\n", + -ret ); goto exit; } key_cert_init2 = 2; @@ -2088,8 +2174,8 @@ reset: if( ( ret = mbedtls_ssl_set_client_transport_id( &ssl, client_ip, cliip_len ) ) != 0 ) { - mbedtls_printf( " failed\n ! " - "mbedtls_ssl_set_client_transport_id() returned -0x%x\n\n", -ret ); + mbedtls_printf( " failed\n ! mbedtls_ssl_set_client_transport_id() returned -0x%x\n\n", + -ret ); goto exit; } } @@ -2117,9 +2203,24 @@ handshake: mbedtls_printf( " . Performing the SSL/TLS handshake..." ); fflush( stdout ); - do ret = mbedtls_ssl_handshake( &ssl ); - while( ret == MBEDTLS_ERR_SSL_WANT_READ || - ret == MBEDTLS_ERR_SSL_WANT_WRITE ); + while( ( ret = mbedtls_ssl_handshake( &ssl ) ) != 0 ) + { + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + break; + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + ret = idle( &client_fd, &timer, ret ); +#else + ret = idle( &client_fd, ret ); +#endif + if( ret != 0 ) + goto reset; + } + } if( ret == MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED ) { @@ -2225,7 +2326,18 @@ data_exchange: if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ) + { + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &client_fd, &timer, ret ); +#else + idle( &client_fd, ret ); +#endif + } + continue; + } if( ret <= 0 ) { @@ -2313,9 +2425,40 @@ data_exchange: len = sizeof( buf ) - 1; memset( buf, 0, sizeof( buf ) ); - do ret = mbedtls_ssl_read( &ssl, buf, len ); - while( ret == MBEDTLS_ERR_SSL_WANT_READ || - ret == MBEDTLS_ERR_SSL_WANT_WRITE ); + while( 1 ) + { + /* Without the call to `mbedtls_ssl_check_pending`, it might + * happen that the client sends application data in the same + * datagram as the Finished message concluding the handshake. + * In this case, the application data would be ready to be + * processed while the underlying transport wouldn't signal + * any further incoming data. + * + * See the test 'Event-driven I/O: session-id resume, UDP packing' + * in tests/ssl-opt.sh. + */ + + /* For event-driven IO, wait for socket to become available */ + if( mbedtls_ssl_check_pending( &ssl ) == 0 && + opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &client_fd, &timer, MBEDTLS_ERR_SSL_WANT_READ ); +#else + idle( &client_fd, MBEDTLS_ERR_SSL_WANT_READ ); +#endif + } + + ret = mbedtls_ssl_read( &ssl, buf, len ); + + /* Note that even if `mbedtls_ssl_check_pending` returns true, + * it can happen that the subsequent call to `mbedtls_ssl_read` + * returns `MBEDTLS_ERR_SSL_WANT_READ`, because the pending messages + * might be discarded (e.g. because they are retransmissions). */ + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + break; + } if( ret <= 0 ) { @@ -2356,6 +2499,16 @@ data_exchange: mbedtls_printf( " failed\n ! mbedtls_ssl_renegotiate returned %d\n\n", ret ); goto reset; } + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &client_fd, &timer, ret ); +#else + idle( &client_fd, ret ); +#endif + } } mbedtls_printf( " ok\n" ); @@ -2390,14 +2543,39 @@ data_exchange: mbedtls_printf( " failed\n ! mbedtls_ssl_write returned %d\n\n", ret ); goto reset; } + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &client_fd, &timer, ret ); +#else + idle( &client_fd, ret ); +#endif + } } } } else /* Not stream, so datagram */ { - do ret = mbedtls_ssl_write( &ssl, buf, len ); - while( ret == MBEDTLS_ERR_SSL_WANT_READ || - ret == MBEDTLS_ERR_SSL_WANT_WRITE ); + while( 1 ) + { + ret = mbedtls_ssl_write( &ssl, buf, len ); + + if( ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + break; + + /* For event-driven IO, wait for socket to become available */ + if( opt.event == 1 /* level triggered IO */ ) + { +#if defined(MBEDTLS_TIMING_C) + idle( &client_fd, &timer, ret ); +#else + idle( &client_fd, ret ); +#endif + } + } if( ret < 0 ) { diff --git a/programs/test/udp_proxy.c b/programs/test/udp_proxy.c index 20624d227..5797f3d69 100644 --- a/programs/test/udp_proxy.c +++ b/programs/test/udp_proxy.c @@ -53,6 +53,7 @@ int main( void ) #include "mbedtls/net_sockets.h" #include "mbedtls/error.h" #include "mbedtls/ssl.h" +#include "mbedtls/timing.h" #include @@ -74,17 +75,21 @@ int main( void ) #include #endif /* ( _WIN32 || _WIN32_WCE ) && !EFIX64 && !EFI32 */ -/* For gettimeofday() */ -#if !defined(_WIN32) -#include -#endif - #define MAX_MSG_SIZE 16384 + 2048 /* max record/datagram size */ #define DFL_SERVER_ADDR "localhost" #define DFL_SERVER_PORT "4433" #define DFL_LISTEN_ADDR "localhost" #define DFL_LISTEN_PORT "5556" +#define DFL_PACK 0 + +#if defined(MBEDTLS_TIMING_C) +#define USAGE_PACK \ + " pack=%%d default: 0 (don't pack)\n" \ + " options: t > 0 (pack for t milliseconds)\n" +#else +#define USAGE_PACK +#endif #define USAGE \ "\n usage: udp_proxy param=<>...\n" \ @@ -105,9 +110,10 @@ int main( void ) " drop packets larger than N bytes\n" \ " bad_ad=0/1 default: 0 (don't add bad ApplicationData)\n" \ " protect_hvr=0/1 default: 0 (don't protect HelloVerifyRequest)\n" \ - " protect_len=%%d default: (don't protect packets of this size)\n" \ + " protect_len=%%d default: (don't protect packets of this size)\n" \ "\n" \ " seed=%%d default: (use current time)\n" \ + USAGE_PACK \ "\n" /* @@ -128,7 +134,8 @@ static struct options int bad_ad; /* inject corrupted ApplicationData record */ int protect_hvr; /* never drop or delay HelloVerifyRequest */ int protect_len; /* never drop/delay packet of the given size*/ - + unsigned pack; /* merge packets into single datagram for + * at most \c merge milliseconds if > 0 */ unsigned int seed; /* seed for "random" events */ } opt; @@ -152,6 +159,7 @@ static void get_options( int argc, char *argv[] ) opt.server_port = DFL_SERVER_PORT; opt.listen_addr = DFL_LISTEN_ADDR; opt.listen_port = DFL_LISTEN_PORT; + opt.pack = DFL_PACK; /* Other members default to 0 */ for( i = 1; i < argc; i++ ) @@ -193,6 +201,15 @@ static void get_options( int argc, char *argv[] ) if( opt.drop < 0 || opt.drop > 20 || opt.drop == 1 ) exit_usage( p, q ); } + else if( strcmp( p, "pack" ) == 0 ) + { +#if defined(MBEDTLS_TIMING_C) + opt.pack = (unsigned) atoi( q ); +#else + mbedtls_printf( " option pack only defined if MBEDTLS_TIMING_C is enabled\n" ); + exit( 1 ); +#endif + } else if( strcmp( p, "mtu" ) == 0 ) { opt.mtu = atoi( q ); @@ -267,25 +284,122 @@ static const char *msg_type( unsigned char *msg, size_t len ) } } +#if defined(MBEDTLS_TIMING_C) /* Return elapsed time in milliseconds since the first call */ -static unsigned long ellapsed_time( void ) +static unsigned ellapsed_time( void ) { -#if defined(_WIN32) - return( 0 ); -#else - static struct timeval ref = { 0, 0 }; - struct timeval now; + static int initialized = 0; + static struct mbedtls_timing_hr_time hires; - if( ref.tv_sec == 0 && ref.tv_usec == 0 ) + if( initialized == 0 ) { - gettimeofday( &ref, NULL ); + (void) mbedtls_timing_get_timer( &hires, 1 ); + initialized = 1; return( 0 ); } - gettimeofday( &now, NULL ); - return( 1000 * ( now.tv_sec - ref.tv_sec ) - + ( now.tv_usec - ref.tv_usec ) / 1000 ); -#endif + return( mbedtls_timing_get_timer( &hires, 0 ) ); +} + +typedef struct +{ + mbedtls_net_context *ctx; + + const char *description; + + unsigned packet_lifetime; + unsigned num_datagrams; + + unsigned char data[MAX_MSG_SIZE]; + size_t len; + +} ctx_buffer; + +static ctx_buffer outbuf[2]; + +static int ctx_buffer_flush( ctx_buffer *buf ) +{ + int ret; + + mbedtls_printf( " %05u flush %s: %u bytes, %u datagrams, last %u ms\n", + ellapsed_time(), buf->description, + (unsigned) buf->len, buf->num_datagrams, + ellapsed_time() - buf->packet_lifetime ); + + ret = mbedtls_net_send( buf->ctx, buf->data, buf->len ); + + buf->len = 0; + buf->num_datagrams = 0; + + return( ret ); +} + +static unsigned ctx_buffer_time_remaining( ctx_buffer *buf ) +{ + unsigned const cur_time = ellapsed_time(); + + if( buf->num_datagrams == 0 ) + return( (unsigned) -1 ); + + if( cur_time - buf->packet_lifetime >= opt.pack ) + return( 0 ); + + return( opt.pack - ( cur_time - buf->packet_lifetime ) ); +} + +static int ctx_buffer_append( ctx_buffer *buf, + const unsigned char * data, + size_t len ) +{ + int ret; + + if( len > (size_t) INT_MAX ) + return( -1 ); + + if( len > sizeof( buf->data ) ) + { + mbedtls_printf( " ! buffer size %u too large (max %u)\n", + (unsigned) len, (unsigned) sizeof( buf->data ) ); + return( -1 ); + } + + if( sizeof( buf->data ) - buf->len < len ) + { + if( ( ret = ctx_buffer_flush( buf ) ) <= 0 ) + return( ret ); + } + + memcpy( buf->data + buf->len, data, len ); + + buf->len += len; + if( ++buf->num_datagrams == 1 ) + buf->packet_lifetime = ellapsed_time(); + + return( (int) len ); +} +#endif /* MBEDTLS_TIMING_C */ + +static int dispatch_data( mbedtls_net_context *ctx, + const unsigned char * data, + size_t len ) +{ +#if defined(MBEDTLS_TIMING_C) + ctx_buffer *buf = NULL; + if( opt.pack > 0 ) + { + if( outbuf[0].ctx == ctx ) + buf = &outbuf[0]; + else if( outbuf[1].ctx == ctx ) + buf = &outbuf[1]; + + if( buf == NULL ) + return( -1 ); + + return( ctx_buffer_append( buf, data, len ) ); + } +#endif /* MBEDTLS_TIMING_C */ + + return( mbedtls_net_send( ctx, data, len ) ); } typedef struct @@ -300,12 +414,22 @@ typedef struct /* Print packet. Outgoing packets come with a reason (forward, dupl, etc.) */ void print_packet( const packet *p, const char *why ) { +#if defined(MBEDTLS_TIMING_C) if( why == NULL ) - mbedtls_printf( " %05lu %s %s (%u bytes)\n", + mbedtls_printf( " %05u dispatch %s %s (%u bytes)\n", ellapsed_time(), p->way, p->type, p->len ); else - mbedtls_printf( " %s %s (%u bytes): %s\n", + mbedtls_printf( " %05u dispatch %s %s (%u bytes): %s\n", + ellapsed_time(), p->way, p->type, p->len, why ); +#else + if( why == NULL ) + mbedtls_printf( " dispatch %s %s (%u bytes)\n", + p->way, p->type, p->len ); + else + mbedtls_printf( " dispatch %s %s (%u bytes): %s\n", p->way, p->type, p->len, why ); +#endif + fflush( stdout ); } @@ -320,20 +444,28 @@ int send_packet( const packet *p, const char *why ) { unsigned char buf[MAX_MSG_SIZE]; memcpy( buf, p->buf, p->len ); - ++buf[p->len - 1]; - print_packet( p, "corrupted" ); - if( ( ret = mbedtls_net_send( dst, buf, p->len ) ) <= 0 ) + if( p->len <= 13 ) { - mbedtls_printf( " ! mbedtls_net_send returned %d\n", ret ); + mbedtls_printf( " ! can't corrupt empty AD record" ); + } + else + { + ++buf[13]; + print_packet( p, "corrupted" ); + } + + if( ( ret = dispatch_data( dst, buf, p->len ) ) <= 0 ) + { + mbedtls_printf( " ! dispatch returned %d\n", ret ); return( ret ); } } print_packet( p, why ); - if( ( ret = mbedtls_net_send( dst, p->buf, p->len ) ) <= 0 ) + if( ( ret = dispatch_data( dst, p->buf, p->len ) ) <= 0 ) { - mbedtls_printf( " ! mbedtls_net_send returned %d\n", ret ); + mbedtls_printf( " ! dispatch returned %d\n", ret ); return( ret ); } @@ -344,9 +476,9 @@ int send_packet( const packet *p, const char *why ) { print_packet( p, "duplicated" ); - if( ( ret = mbedtls_net_send( dst, p->buf, p->len ) ) <= 0 ) + if( ( ret = dispatch_data( dst, p->buf, p->len ) ) <= 0 ) { - mbedtls_printf( " ! mbedtls_net_send returned %d\n", ret ); + mbedtls_printf( " ! dispatch returned %d\n", ret ); return( ret ); } } @@ -472,6 +604,12 @@ int main( int argc, char *argv[] ) mbedtls_net_context listen_fd, client_fd, server_fd; +#if defined( MBEDTLS_TIMING_C ) + struct timeval tm; +#endif + + struct timeval *tm_ptr = NULL; + int nb_fds; fd_set read_fds; @@ -560,14 +698,65 @@ accept: nb_fds = listen_fd.fd; ++nb_fds; +#if defined(MBEDTLS_TIMING_C) + if( opt.pack > 0 ) + { + outbuf[0].ctx = &server_fd; + outbuf[0].description = "S <- C"; + outbuf[0].num_datagrams = 0; + outbuf[0].len = 0; + + outbuf[1].ctx = &client_fd; + outbuf[1].description = "S -> C"; + outbuf[1].num_datagrams = 0; + outbuf[1].len = 0; + } +#endif /* MBEDTLS_TIMING_C */ + while( 1 ) { +#if defined(MBEDTLS_TIMING_C) + if( opt.pack > 0 ) + { + unsigned max_wait_server, max_wait_client, max_wait; + max_wait_server = ctx_buffer_time_remaining( &outbuf[0] ); + max_wait_client = ctx_buffer_time_remaining( &outbuf[1] ); + + max_wait = (unsigned) -1; + + if( max_wait_server == 0 ) + ctx_buffer_flush( &outbuf[0] ); + else + max_wait = max_wait_server; + + if( max_wait_client == 0 ) + ctx_buffer_flush( &outbuf[1] ); + else + { + if( max_wait_client < max_wait ) + max_wait = max_wait_client; + } + + if( max_wait != (unsigned) -1 ) + { + tm.tv_sec = max_wait / 1000; + tm.tv_usec = ( max_wait % 1000 ) * 1000; + + tm_ptr = &tm; + } + else + { + tm_ptr = NULL; + } + } +#endif /* MBEDTLS_TIMING_C */ + FD_ZERO( &read_fds ); FD_SET( server_fd.fd, &read_fds ); FD_SET( client_fd.fd, &read_fds ); FD_SET( listen_fd.fd, &read_fds ); - if( ( ret = select( nb_fds, &read_fds, NULL, NULL, NULL ) ) <= 0 ) + if( ( ret = select( nb_fds, &read_fds, NULL, NULL, tm_ptr ) ) < 0 ) { perror( "select" ); goto exit; @@ -589,6 +778,7 @@ accept: &client_fd, &server_fd ) ) != 0 ) goto accept; } + } exit: diff --git a/programs/test/udp_proxy_wrapper.sh b/programs/test/udp_proxy_wrapper.sh new file mode 100755 index 000000000..29033d5d1 --- /dev/null +++ b/programs/test/udp_proxy_wrapper.sh @@ -0,0 +1,117 @@ +#!/bin/sh +# -*-sh-basic-offset: 4-*- +# Usage: udp_proxy_wrapper.sh [PROXY_PARAM...] -- [SERVER_PARAM...] + +set -u + +MBEDTLS_BASE="$(dirname -- "$0")/../.." +TPXY_BIN="$MBEDTLS_BASE/programs/test/udp_proxy" +SRV_BIN="$MBEDTLS_BASE/programs/ssl/ssl_server2" + +: ${VERBOSE:=0} + +stop_proxy() { + if [ -n "${tpxy_pid:-}" ]; then + echo + echo " * Killing proxy (pid $tpxy_pid) ..." + kill $tpxy_pid + fi +} + +stop_server() { + if [ -n "${srv_pid:-}" ]; then + echo + echo " * Killing server (pid $srv_pid) ..." + kill $srv_pid >/dev/null 2>/dev/null + fi +} + +cleanup() { + stop_server + stop_proxy + exit 129 +} + +trap cleanup INT TERM HUP + +# Extract the proxy parameters +tpxy_cmd_snippet='"$TPXY_BIN"' +while [ $# -ne 0 ] && [ "$1" != "--" ]; do + tail="$1" quoted="" + while [ -n "$tail" ]; do + case "$tail" in + *\'*) quoted="${quoted}${tail%%\'*}'\\''" tail="${tail#*\'}";; + *) quoted="${quoted}${tail}"; tail=; false;; + esac + done + tpxy_cmd_snippet="$tpxy_cmd_snippet '$quoted'" + shift +done +unset tail quoted +if [ $# -eq 0 ]; then + echo " * No server arguments (must be preceded by \" -- \") - exit" + exit 3 +fi +shift + +dtls_enabled= +ipv6_in_use= +server_port_orig= +server_addr_orig= +for param; do + case "$param" in + server_port=*) server_port_orig="${param#*=}";; + server_addr=*:*) server_addr_orig="${param#*=}"; ipv6_in_use=1;; + server_addr=*) server_addr_orig="${param#*=}";; + dtls=[!0]*) dtls_enabled=1;; + esac +done + +if [ -z "$dtls_enabled" ] || [ -n "$ipv6_in_use" ]; then + echo >&2 "$0: Couldn't find DTLS enabling, or IPv6 is in use - immediate fallback to server application..." + if [ $VERBOSE -gt 0 ]; then + echo "[ $SRV_BIN $* ]" + fi + exec "$SRV_BIN" "$@" +fi + +if [ -z "$server_port_orig" ]; then + server_port_orig=4433 +fi +echo " * Server port: $server_port_orig" +tpxy_cmd_snippet="$tpxy_cmd_snippet \"listen_port=\$server_port_orig\"" +tpxy_cmd_snippet="$tpxy_cmd_snippet \"server_port=\$server_port\"" + +if [ -n "$server_addr_orig" ]; then + echo " * Server address: $server_addr_orig" + tpxy_cmd_snippet="$tpxy_cmd_snippet \"server_addr=\$server_addr_orig\"" + tpxy_cmd_snippet="$tpxy_cmd_snippet \"listen_addr=\$server_addr_orig\"" +fi + +server_port=$(( server_port_orig + 1 )) +set -- "$@" "server_port=$server_port" +echo " * Intermediate port: $server_port" + +echo " * Start proxy in background ..." +if [ $VERBOSE -gt 0 ]; then + echo "[ $tpxy_cmd_snippet ]" +fi +eval exec "$tpxy_cmd_snippet" >/dev/null 2>&1 & +tpxy_pid=$! + +if [ $VERBOSE -gt 0 ]; then + echo " * Proxy ID: $TPXY_PID" +fi + +echo " * Starting server ..." +if [ $VERBOSE -gt 0 ]; then + echo "[ $SRV_BIN $* ]" +fi + +exec "$SRV_BIN" "$@" >&2 & +srv_pid=$! + +wait $srv_pid + +stop_proxy +return 0 diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 9fde54a6b..c4a10a2fa 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -465,9 +465,12 @@ run_test() { eval "$CLI_CMD" >> $CLI_OUT 2>&1 & wait_client_done + sleep 0.05 + # terminate the server (and the proxy) kill $SRV_PID wait $SRV_PID + if [ -n "$PXY_CMD" ]; then kill $PXY_PID >/dev/null 2>&1 wait $PXY_PID @@ -631,16 +634,19 @@ fi get_options "$@" # sanity checks, avoid an avalanche of errors -if [ ! -x "$P_SRV" ]; then - echo "Command '$P_SRV' is not an executable file" +P_SRV_BIN="${P_SRV%%[ ]*}" +P_CLI_BIN="${P_CLI%%[ ]*}" +P_PXY_BIN="${P_PXY%%[ ]*}" +if [ ! -x "$P_SRV_BIN" ]; then + echo "Command '$P_SRV_BIN' is not an executable file" exit 1 fi -if [ ! -x "$P_CLI" ]; then - echo "Command '$P_CLI' is not an executable file" +if [ ! -x "$P_CLI_BIN" ]; then + echo "Command '$P_CLI_BIN' is not an executable file" exit 1 fi -if [ ! -x "$P_PXY" ]; then - echo "Command '$P_PXY' is not an executable file" +if [ ! -x "$P_PXY_BIN" ]; then + echo "Command '$P_PXY_BIN' is not an executable file" exit 1 fi if [ "$MEMCHECK" -gt 0 ]; then @@ -2704,6 +2710,118 @@ run_test "Non-blocking I/O: session-id resume" \ -C "mbedtls_ssl_handshake returned" \ -c "Read from server: .* bytes read" +# Tests for event-driven I/O: exercise a variety of handshake flows + +run_test "Event-driven I/O: basic handshake" \ + "$P_SRV event=1 tickets=0 auth_mode=none" \ + "$P_CLI event=1 tickets=0" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O: client auth" \ + "$P_SRV event=1 tickets=0 auth_mode=required" \ + "$P_CLI event=1 tickets=0" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O: ticket" \ + "$P_SRV event=1 tickets=1 auth_mode=none" \ + "$P_CLI event=1 tickets=1" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O: ticket + client auth" \ + "$P_SRV event=1 tickets=1 auth_mode=required" \ + "$P_CLI event=1 tickets=1" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O: ticket + client auth + resume" \ + "$P_SRV event=1 tickets=1 auth_mode=required" \ + "$P_CLI event=1 tickets=1 reconnect=1" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O: ticket + resume" \ + "$P_SRV event=1 tickets=1 auth_mode=none" \ + "$P_CLI event=1 tickets=1 reconnect=1" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O: session-id resume" \ + "$P_SRV event=1 tickets=0 auth_mode=none" \ + "$P_CLI event=1 tickets=0 reconnect=1" \ + 0 \ + -S "mbedtls_ssl_handshake returned" \ + -C "mbedtls_ssl_handshake returned" \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: basic handshake" \ + "$P_SRV dtls=1 event=1 tickets=0 auth_mode=none" \ + "$P_CLI dtls=1 event=1 tickets=0" \ + 0 \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: client auth" \ + "$P_SRV dtls=1 event=1 tickets=0 auth_mode=required" \ + "$P_CLI dtls=1 event=1 tickets=0" \ + 0 \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: ticket" \ + "$P_SRV dtls=1 event=1 tickets=1 auth_mode=none" \ + "$P_CLI dtls=1 event=1 tickets=1" \ + 0 \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: ticket + client auth" \ + "$P_SRV dtls=1 event=1 tickets=1 auth_mode=required" \ + "$P_CLI dtls=1 event=1 tickets=1" \ + 0 \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: ticket + client auth + resume" \ + "$P_SRV dtls=1 event=1 tickets=1 auth_mode=required" \ + "$P_CLI dtls=1 event=1 tickets=1 reconnect=1" \ + 0 \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: ticket + resume" \ + "$P_SRV dtls=1 event=1 tickets=1 auth_mode=none" \ + "$P_CLI dtls=1 event=1 tickets=1 reconnect=1" \ + 0 \ + -c "Read from server: .* bytes read" + +run_test "Event-driven I/O, DTLS: session-id resume" \ + "$P_SRV dtls=1 event=1 tickets=0 auth_mode=none" \ + "$P_CLI dtls=1 event=1 tickets=0 reconnect=1" \ + 0 \ + -c "Read from server: .* bytes read" + +# This test demonstrates the need for the mbedtls_ssl_check_pending function. +# During session resumption, the client will send its ApplicationData record +# within the same datagram as the Finished messages. In this situation, the +# server MUST NOT idle on the underlying transport after handshake completion, +# because the ApplicationData request has already been queued internally. +run_test "Event-driven I/O, DTLS: session-id resume, UDP packing" \ + -p "$P_PXY pack=50" \ + "$P_SRV dtls=1 event=1 tickets=0 auth_mode=required" \ + "$P_CLI dtls=1 event=1 tickets=0 reconnect=1" \ + 0 \ + -c "Read from server: .* bytes read" + # Tests for version negotiation run_test "Version check: all -> 1.2" \ @@ -4195,8 +4313,8 @@ run_test "DTLS proxy: duplicate every packet" \ 0 \ -c "replayed record" \ -s "replayed record" \ - -c "discarding invalid record" \ - -s "discarding invalid record" \ + -c "record from another epoch" \ + -s "record from another epoch" \ -S "resend" \ -s "Extra-header:" \ -c "HTTP/1.0 200 OK" @@ -4208,13 +4326,29 @@ run_test "DTLS proxy: duplicate every packet, server anti-replay off" \ 0 \ -c "replayed record" \ -S "replayed record" \ - -c "discarding invalid record" \ - -s "discarding invalid record" \ + -c "record from another epoch" \ + -s "record from another epoch" \ -c "resend" \ -s "resend" \ -s "Extra-header:" \ -c "HTTP/1.0 200 OK" +run_test "DTLS proxy: multiple records in same datagram" \ + -p "$P_PXY pack=50" \ + "$P_SRV dtls=1 debug_level=2" \ + "$P_CLI dtls=1 debug_level=2" \ + 0 \ + -c "next record in same datagram" \ + -s "next record in same datagram" + +run_test "DTLS proxy: multiple records in same datagram, duplicate every packet" \ + -p "$P_PXY pack=50 duplicate=1" \ + "$P_SRV dtls=1 debug_level=2" \ + "$P_CLI dtls=1 debug_level=2" \ + 0 \ + -c "next record in same datagram" \ + -s "next record in same datagram" + run_test "DTLS proxy: inject invalid AD record, default badmac_limit" \ -p "$P_PXY bad_ad=1" \ "$P_SRV dtls=1 debug_level=1" \ @@ -4270,8 +4404,6 @@ run_test "DTLS proxy: delay ChangeCipherSpec" \ 0 \ -c "record from another epoch" \ -s "record from another epoch" \ - -c "discarding invalid record" \ - -s "discarding invalid record" \ -s "Extra-header:" \ -c "HTTP/1.0 200 OK"