diff --git a/ChangeLog.d/mbedtls_ssl_ticket_rotate.txt b/ChangeLog.d/mbedtls_ssl_ticket_rotate.txt new file mode 100644 index 000000000..b843bfd5d --- /dev/null +++ b/ChangeLog.d/mbedtls_ssl_ticket_rotate.txt @@ -0,0 +1,2 @@ +Features + * Add mbedtls_ssl_ticket_rotate() for external ticket rotation. diff --git a/include/mbedtls/ssl_ticket.h b/include/mbedtls/ssl_ticket.h index 0f4117d34..855930953 100644 --- a/include/mbedtls/ssl_ticket.h +++ b/include/mbedtls/ssl_ticket.h @@ -42,12 +42,16 @@ extern "C" { #endif +#define MBEDTLS_SSL_TICKET_MAX_KEY_BYTES 32 /*!< Max supported key length in bytes */ +#define MBEDTLS_SSL_TICKET_KEY_NAME_BYTES 4 /*!< key name length in bytes */ + /** * \brief Information for session ticket protection */ typedef struct mbedtls_ssl_ticket_key { - unsigned char MBEDTLS_PRIVATE(name)[4]; /*!< random key identifier */ + unsigned char MBEDTLS_PRIVATE(name)[MBEDTLS_SSL_TICKET_KEY_NAME_BYTES]; + /*!< random key identifier */ uint32_t MBEDTLS_PRIVATE(generation_time); /*!< key generation timestamp (seconds) */ mbedtls_cipher_context_t MBEDTLS_PRIVATE(ctx); /*!< context for auth enc/decryption */ } @@ -98,7 +102,7 @@ void mbedtls_ssl_ticket_init( mbedtls_ssl_ticket_context *ctx ); * supported. Usually that means a 256-bit key. * * \note The lifetime of the keys is twice the lifetime of tickets. - * It is recommended to pick a reasonnable lifetime so as not + * It is recommended to pick a reasonable lifetime so as not * to negate the benefits of forward secrecy. * * \return 0 if successful, @@ -109,6 +113,43 @@ int mbedtls_ssl_ticket_setup( mbedtls_ssl_ticket_context *ctx, mbedtls_cipher_type_t cipher, uint32_t lifetime ); +/** + * \brief Rotate session ticket encryption key to new specified key. + * Provides for external control of session ticket encryption + * key rotation, e.g. for synchronization between different + * machines. If this function is not used, or if not called + * before ticket lifetime expires, then a new session ticket + * encryption key is generated internally in order to avoid + * unbounded session ticket encryption key lifetimes. + * + * \param ctx Context to be set up + * \param name Session ticket encryption key name + * \param nlength Session ticket encryption key name length in bytes + * \param k Session ticket encryption key + * \param klength Session ticket encryption key length in bytes + * \param lifetime Tickets lifetime in seconds + * Recommended value: 86400 (one day). + * + * \note \c name and \c k are recommended to be cryptographically + * random data. + * + * \note \c nlength must match sizeof( ctx->name ) + * + * \note \c klength must be sufficient for use by cipher specified + * to \c mbedtls_ssl_ticket_setup + * + * \note The lifetime of the keys is twice the lifetime of tickets. + * It is recommended to pick a reasonable lifetime so as not + * to negate the benefits of forward secrecy. + * + * \return 0 if successful, + * or a specific MBEDTLS_ERR_XXX error code + */ +int mbedtls_ssl_ticket_rotate( mbedtls_ssl_ticket_context *ctx, + const unsigned char *name, size_t nlength, + const unsigned char *k, size_t klength, + uint32_t lifetime ); + /** * \brief Implementation of the ticket write callback * diff --git a/library/ssl_ticket.c b/library/ssl_ticket.c index e998111d9..b04e18477 100644 --- a/library/ssl_ticket.c +++ b/library/ssl_ticket.c @@ -48,9 +48,9 @@ void mbedtls_ssl_ticket_init( mbedtls_ssl_ticket_context *ctx ) #endif } -#define MAX_KEY_BYTES 32 /* 256 bits */ +#define MAX_KEY_BYTES MBEDTLS_SSL_TICKET_MAX_KEY_BYTES -#define TICKET_KEY_NAME_BYTES 4 +#define TICKET_KEY_NAME_BYTES MBEDTLS_SSL_TICKET_KEY_NAME_BYTES #define TICKET_IV_BYTES 12 #define TICKET_CRYPT_LEN_BYTES 2 #define TICKET_AUTH_TAG_BYTES 16 @@ -121,6 +121,35 @@ static int ssl_ticket_update_keys( mbedtls_ssl_ticket_context *ctx ) return( 0 ); } +/* + * Rotate active session ticket encryption key + */ +int mbedtls_ssl_ticket_rotate( mbedtls_ssl_ticket_context *ctx, + const unsigned char *name, size_t nlength, + const unsigned char *k, size_t klength, + uint32_t lifetime ) +{ + const unsigned char idx = 1 - ctx->active; + mbedtls_ssl_ticket_key * const key = ctx->keys + idx; + const int bitlen = mbedtls_cipher_get_key_bitlen( &key->ctx ); + int ret; + if( nlength < TICKET_KEY_NAME_BYTES || klength * 8 < (size_t)bitlen ) + return( MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA ); + + /* With GCM and CCM, same context can encrypt & decrypt */ + ret = mbedtls_cipher_setkey( &key->ctx, k, bitlen, MBEDTLS_ENCRYPT ); + if( ret != 0 ) + return( ret ); + + ctx->active = idx; + ctx->ticket_lifetime = lifetime; + memcpy( key->name, name, TICKET_KEY_NAME_BYTES ); +#if defined(MBEDTLS_HAVE_TIME) + key->generation_time = (uint32_t) mbedtls_time( NULL ); +#endif + return 0; +} + /* * Setup context for actual use */ diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index c77119b02..595300e85 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -119,6 +119,7 @@ int main( void ) #define DFL_MFL_CODE MBEDTLS_SSL_MAX_FRAG_LEN_NONE #define DFL_TRUNC_HMAC -1 #define DFL_TICKETS MBEDTLS_SSL_SESSION_TICKETS_ENABLED +#define DFL_TICKET_ROTATE 0 #define DFL_TICKET_TIMEOUT 86400 #define DFL_TICKET_AEAD MBEDTLS_CIPHER_AES_256_GCM #define DFL_CACHE_MAX -1 @@ -286,6 +287,7 @@ int main( void ) #if defined(MBEDTLS_SSL_SESSION_TICKETS) #define USAGE_TICKETS \ " tickets=%%d default: 1 (enabled)\n" \ + " ticket_rotate=%%d default: 0 (disabled)\n" \ " ticket_timeout=%%d default: 86400 (one day)\n" \ " ticket_aead=%%s default: \"AES-256-GCM\"\n" #else @@ -613,6 +615,7 @@ struct options unsigned char mfl_code; /* code for maximum fragment length */ int trunc_hmac; /* accept truncated hmac? */ int tickets; /* enable / disable session tickets */ + int ticket_rotate; /* session ticket rotate (code coverage) */ int ticket_timeout; /* session ticket lifetime */ int ticket_aead; /* session ticket protection */ int cache_max; /* max number of session cache entries */ @@ -1542,6 +1545,7 @@ int main( int argc, char *argv[] ) opt.mfl_code = DFL_MFL_CODE; opt.trunc_hmac = DFL_TRUNC_HMAC; opt.tickets = DFL_TICKETS; + opt.ticket_rotate = DFL_TICKET_ROTATE; opt.ticket_timeout = DFL_TICKET_TIMEOUT; opt.ticket_aead = DFL_TICKET_AEAD; opt.cache_max = DFL_CACHE_MAX; @@ -1915,6 +1919,12 @@ int main( int argc, char *argv[] ) if( opt.tickets < 0 || opt.tickets > 1 ) goto usage; } + else if( strcmp( p, "ticket_rotate" ) == 0 ) + { + opt.ticket_rotate = atoi( q ); + if( opt.ticket_rotate < 0 || opt.ticket_rotate > 1 ) + goto usage; + } else if( strcmp( p, "ticket_timeout" ) == 0 ) { opt.ticket_timeout = atoi( q ); @@ -2737,6 +2747,23 @@ int main( int argc, char *argv[] ) mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse, &ticket_ctx ); + + /* exercise manual ticket rotation (not required for typical use) + * (used for external synchronization of session ticket encryption keys) + */ + if( opt.ticket_rotate ) { + unsigned char kbuf[MBEDTLS_SSL_TICKET_MAX_KEY_BYTES]; + unsigned char name[MBEDTLS_SSL_TICKET_KEY_NAME_BYTES]; + if( ( ret = rng_get( &rng, name, sizeof( name ) ) ) != 0 || + ( ret = rng_get( &rng, kbuf, sizeof( kbuf ) ) ) != 0 || + ( ret = mbedtls_ssl_ticket_rotate( &ticket_ctx, + name, sizeof(name), kbuf, sizeof(kbuf), + opt.ticket_timeout ) ) != 0 ) + { + mbedtls_printf( " failed\n ! mbedtls_ssl_ticket_rotate returned %d\n\n", ret ); + goto exit; + } + } } #endif diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 4ec62ae1a..2480713fd 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -2707,6 +2707,21 @@ run_test "Session resume using tickets: basic" \ -s "a session has been resumed" \ -c "a session has been resumed" +requires_config_disabled MBEDTLS_USE_PSA_CRYPTO +run_test "Session resume using tickets: manual rotation" \ + "$P_SRV debug_level=3 tickets=1 ticket_rotate=1" \ + "$P_CLI debug_level=3 tickets=1 reconnect=1" \ + 0 \ + -c "client hello, adding session ticket extension" \ + -s "found session ticket extension" \ + -s "server hello, adding session ticket extension" \ + -c "found session_ticket extension" \ + -c "parse new session ticket" \ + -S "session successfully restored from cache" \ + -s "session successfully restored from ticket" \ + -s "a session has been resumed" \ + -c "a session has been resumed" + run_test "Session resume using tickets: cache disabled" \ "$P_SRV debug_level=3 tickets=1 cache_max=0" \ "$P_CLI debug_level=3 tickets=1 reconnect=1" \