diff --git a/deps.sh b/deps.sh index 96e604c219..1a7d0325f3 100755 --- a/deps.sh +++ b/deps.sh @@ -428,7 +428,6 @@ install_openssl () { no-comp \ no-ct \ no-des \ - no-dh \ no-dsa \ no-dtls \ no-dtls1-method \ diff --git a/src/waltz/tls/Local.mk b/src/waltz/tls/Local.mk index d05612dede..28170c5e85 100644 --- a/src/waltz/tls/Local.mk +++ b/src/waltz/tls/Local.mk @@ -13,6 +13,6 @@ endif # Uncomment this to test against quictls. Upstream OpenSSL does not # support this test. #ifdef FD_HAS_OPENSSL -#$(call make-unit-test,test_tls_openssl,test_tls_openssl,fd_quic fd_tls fd_ballet fd_util) +#$(call make-unit-test,test_tls_openssl,test_tls_openssl,fd_quic fd_tls fd_ballet fd_util,-lssl -lcrypto) #$(call run-unit-test,test_tls_openssl) #endif diff --git a/src/waltz/tls/fd_tls.c b/src/waltz/tls/fd_tls.c index 53b0b1bca4..bfb933013c 100644 --- a/src/waltz/tls/fd_tls.c +++ b/src/waltz/tls/fd_tls.c @@ -306,6 +306,91 @@ fd_tls_server_handshake( fd_tls_t const * server, } } +static long +fd_tls_server_hs_retry( fd_tls_t const * server, + fd_tls_estate_srv_t * handshake, + fd_tls_client_hello_t const * ch, + uchar const ch1_hash[32] ) { + + if( FD_UNLIKELY( handshake->hello_retry ) ) { + /* Already retried but still no X25519 share */ + return fd_tls_alert( &handshake->base, FD_TLS_ALERT_ILLEGAL_PARAMETER, FD_TLS_REASON_SENDMSG_FAIL ); + } + handshake->hello_retry = 1; + + /* Message buffer */ +# define MSG_BUFSZ 512UL + uchar msg_buf[ MSG_BUFSZ ]; + + /* Transcript hasher (RetryHelloRequest variation) + https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.1 */ + fd_sha256_t transcript; fd_sha256_init( &transcript ); + uchar const transcript_prefix[] = { 254, 0x00, 0x00, 32 }; + fd_sha256_append( &transcript, transcript_prefix, sizeof(transcript_prefix) ); + fd_sha256_append( &transcript, ch1_hash, 32 ); + + /* Create HelloRetryRequest message */ + + ulong server_hello_sz; + + do { + uchar * wire = msg_buf; + uchar * const wire_end = msg_buf + MSG_BUFSZ; + + /* Leave space for message header */ + + void * hdr_ptr = wire; + wire += sizeof(fd_tls_msg_hdr_t); + fd_tls_msg_hdr_t hdr = { .type = FD_TLS_MSG_SERVER_HELLO }; + + /* Construct server hello */ + + fd_tls_server_hello_t sh = { + .cipher_suite = FD_TLS_CIPHER_SUITE_AES_128_GCM_SHA256, + .key_share = { .has_x25519 = 1 }, + .session_id = ch->session_id, + }; + memcpy( sh.key_share.x25519, server->kex_public_key, 32UL ); + + /* Encode server hello */ + + long encode_res = fd_tls_encode_hello_retry_request( &sh, wire, (ulong)(wire_end-wire) ); + if( FD_UNLIKELY( encode_res<0L ) ) + return fd_tls_alert( &handshake->base, (uint)(-encode_res), FD_TLS_REASON_SH_ENCODE ); + wire += (ulong)encode_res; + + hdr.sz = fd_uint_to_tls_u24( (uint)encode_res ); + fd_tls_encode_msg_hdr( &hdr, hdr_ptr, sizeof(fd_tls_msg_hdr_t) ); + server_hello_sz = (ulong)(wire - msg_buf); + } while(0); + + /* Call back with HelloRetryRequest */ + + if( FD_UNLIKELY( !server->sendmsg_fn( + handshake, + msg_buf, server_hello_sz, + FD_TLS_LEVEL_INITIAL, + /* flush */ 1 ) ) ) + return fd_tls_alert( &handshake->base, FD_TLS_ALERT_INTERNAL_ERROR, FD_TLS_REASON_SENDMSG_FAIL ); + + /* Record HelloRetryRequest in transcript hash */ + + fd_sha256_append( &transcript, msg_buf, server_hello_sz ); + + /* Finish up ********************************************************/ + + /* Store transcript hash state */ + + fd_tls_transcript_store( &handshake->transcript, &transcript ); + + /* Done */ + + handshake->base.state = FD_TLS_HS_START; + +# undef MSG_BUFSZ + return (long)0L; +} + /* fd_tls_server_hs_start is invoked in response to the initial ClientHello. We send back several messages in response, including - the ServerHello, completing cryptographic negotiation @@ -335,7 +420,12 @@ fd_tls_server_hs_start( fd_tls_t const * const server, uchar msg_buf[ MSG_BUFSZ ]; /* Transcript hasher */ - fd_sha256_t transcript; fd_sha256_init( &transcript ); + fd_sha256_t transcript; + if( handshake->hello_retry ) { + fd_tls_transcript_load( &handshake->transcript, &transcript ); + } else { + fd_sha256_init( &transcript ); + } /* Read client hello ************************************************/ @@ -405,6 +495,16 @@ fd_tls_server_hs_start( fd_tls_t const * const server, fd_sha256_append( &transcript, record, read_sz ); + /* Retry if key share is missing */ + + if( !ch.key_share.has_x25519 ) { + uchar ch1_hash[ 32 ]; + fd_sha256_fini( &transcript, ch1_hash ); + long rc = fd_tls_server_hs_retry( server, handshake, &ch, ch1_hash ); + /**/ rc = fd_long_if( rc>=0, (long)read_sz, rc ); + return rc; + } + /* Respond with server hello ****************************************/ /* Create server random */ @@ -1699,6 +1799,8 @@ fd_tls_reason_cstr( uint reason ) { return "unsupported cryptographic parameters (fd_tls only supports TLS 1.3, X25519, Ed25519, AES-128-GCM)"; case FD_TLS_REASON_CH_NO_QUIC: return "client does not support QUIC (missing QUIC transport params)"; + case FD_TLS_REASON_CH_RETRY_KS: + return "client didn't provide an X25519 key share even after a RetryHelloRequest"; case FD_TLS_REASON_X25519_FAIL: return "X25519 key exchange failed"; case FD_TLS_REASON_NO_X509: diff --git a/src/waltz/tls/fd_tls.h b/src/waltz/tls/fd_tls.h index db7e380f5d..9069b722ac 100644 --- a/src/waltz/tls/fd_tls.h +++ b/src/waltz/tls/fd_tls.h @@ -308,6 +308,7 @@ typedef struct fd_tls fd_tls_t; #define FD_TLS_REASON_CH_ENCODE (104) /* failed to encode ClientHello */ #define FD_TLS_REASON_CH_CRYPTO_NEG (105) /* ClientHello crypto negotiation failed */ #define FD_TLS_REASON_CH_NO_QUIC (106) /* Missing QUIC transport params in ClientHello */ +#define FD_TLS_REASON_CH_RETRY_KS (107) /* ClientHello still missing key share after a retry */ #define FD_TLS_REASON_SH_EXPECTED (201) /* wanted ServerHello, got another msg type */ #define FD_TLS_REASON_SH_PARSE (203) /* failed to parse ServerHello */ diff --git a/src/waltz/tls/fd_tls_estate.h b/src/waltz/tls/fd_tls_estate.h index ecd59dbf69..ae2a6eb584 100644 --- a/src/waltz/tls/fd_tls_estate.h +++ b/src/waltz/tls/fd_tls_estate.h @@ -134,6 +134,7 @@ struct fd_tls_estate_srv { uchar server_cert_rpk : 1; /* 0: X.509 1: raw public key */ uchar client_cert : 1; /* 0: no client auth 1: client cert */ uchar client_cert_rpk : 1; /* 0: X.509 1: raw public key */ + uchar hello_retry : 1; fd_tls_transcript_t transcript; uchar client_hs_secret[32]; diff --git a/src/waltz/tls/fd_tls_proto.c b/src/waltz/tls/fd_tls_proto.c index 362e353f10..40c0f5f52c 100644 --- a/src/waltz/tls/fd_tls_proto.c +++ b/src/waltz/tls/fd_tls_proto.c @@ -6,6 +6,13 @@ typedef struct fd_tls_u24 tls_u24; /* code generator helper */ +/* hello_retry_magic is the RFC 8446 hardcoded value of the 'random' field of a RetryHelloRequest */ +static uchar const hello_retry_magic[ 32 ] = + { 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, + 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, + 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, + 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C }; + #define FD_TLS_ENCODE_EXT_BEGIN( type ) \ do { \ int valid = 1; \ @@ -282,14 +289,9 @@ fd_tls_decode_server_hello( fd_tls_server_hello_t * out, if( FD_UNLIKELY( cipher_suite != FD_TLS_CIPHER_SUITE_AES_128_GCM_SHA256 ) ) return -(long)FD_TLS_ALERT_ILLEGAL_PARAMETER; - /* Middlebox compatibility for HelloRetryRequest */ + /* Reject HelloRetryRequest (we only support X25519) */ - static uchar const special_random[ 32 ] = - { 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, - 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, - 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, - 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C }; - if( FD_UNLIKELY( 0==memcmp( out->random, special_random, 32 ) ) ) + if( FD_UNLIKELY( 0==memcmp( out->random, hello_retry_magic, 32 ) ) ) return -(long)FD_TLS_ALERT_ILLEGAL_PARAMETER; /* Read extensions */ @@ -408,6 +410,55 @@ fd_tls_encode_server_hello( fd_tls_server_hello_t const * in, return (long)( wire_laddr - (ulong)wire ); } +long +fd_tls_encode_hello_retry_request( fd_tls_server_hello_t const * in, + uchar * wire, + ulong wire_sz ) { + + ulong wire_laddr = (ulong)wire; + + ushort legacy_version = FD_TLS_VERSION_TLS12; + uchar legacy_session_id_sz = (uchar)in->session_id.bufsz; + ushort cipher_suite = FD_TLS_CIPHER_SUITE_AES_128_GCM_SHA256; + uchar legacy_compression_method = 0; + +# define FIELDS( FIELD ) \ + FIELD( 0, &legacy_version, ushort, 1 ) \ + FIELD( 1, hello_retry_magic, uchar, 32UL ) \ + FIELD( 2, &legacy_session_id_sz, uchar, 1 ) \ + FIELD( 3, in->session_id.buf, uchar, legacy_session_id_sz ) \ + FIELD( 4, &cipher_suite, ushort, 1 ) \ + FIELD( 5, &legacy_compression_method, uchar, 1 ) + FD_TLS_ENCODE_STATIC_BATCH( FIELDS ) +# undef FIELDS + + /* Encode extensions */ + + ushort * extension_tot_sz = FD_TLS_SKIP_FIELD( ushort ); + ulong extension_start = wire_laddr; + + ushort ext_supported_versions_ext_type = FD_TLS_EXT_SUPPORTED_VERSIONS; + ushort ext_supported_versions[1] = { FD_TLS_VERSION_TLS13 }; + ushort ext_supported_versions_ext_sz = sizeof(ext_supported_versions); + + ushort ext_key_share_ext_type = FD_TLS_EXT_KEY_SHARE; + ushort ext_key_share_ext_sz = sizeof(ushort); + ushort ext_key_share_group = FD_TLS_GROUP_X25519; + +# define FIELDS( FIELD ) \ + FIELD( 0, &ext_supported_versions_ext_type, ushort, 1 ) \ + FIELD( 1, &ext_supported_versions_ext_sz, ushort, 1 ) \ + FIELD( 2, ext_supported_versions, ushort, 1 ) \ + FIELD( 3, &ext_key_share_ext_type, ushort, 1 ) \ + FIELD( 4, &ext_key_share_ext_sz, ushort, 1 ) \ + FIELD( 5, &ext_key_share_group, ushort, 1 ) + FD_TLS_ENCODE_STATIC_BATCH( FIELDS ) +# undef FIELDS + + *extension_tot_sz = fd_ushort_bswap( (ushort)( (ulong)wire_laddr - extension_start ) ); + return (long)( wire_laddr - (ulong)wire ); +} + long fd_tls_decode_enc_ext( fd_tls_enc_ext_t * const out, uchar const * const wire, diff --git a/src/waltz/tls/fd_tls_proto.h b/src/waltz/tls/fd_tls_proto.h index 56ce339244..fde79fb8e6 100644 --- a/src/waltz/tls/fd_tls_proto.h +++ b/src/waltz/tls/fd_tls_proto.h @@ -423,6 +423,11 @@ fd_tls_encode_server_hello( fd_tls_server_hello_t const * in, uchar * wire, ulong wire_sz ); +long +fd_tls_encode_hello_retry_request( fd_tls_server_hello_t const * in, + uchar * wire, + ulong wire_sz ); + long fd_tls_decode_enc_ext( fd_tls_enc_ext_t * out, uchar const * wire, diff --git a/src/waltz/tls/test_tls_helper.h b/src/waltz/tls/test_tls_helper.h index 9439c6e26f..4f2cda3c3b 100644 --- a/src/waltz/tls/test_tls_helper.h +++ b/src/waltz/tls/test_tls_helper.h @@ -70,7 +70,7 @@ fd_tls_test_sign( void * ctx ) { /* Test record transport */ -#define TEST_RECORD_BUFSZ (1024UL) +#define TEST_RECORD_BUFSZ (4096UL) struct test_record { uint level; uchar buf[ TEST_RECORD_BUFSZ ]; diff --git a/src/waltz/tls/test_tls_openssl.c b/src/waltz/tls/test_tls_openssl.c index 6ca74a2c3f..b8400f6d43 100644 --- a/src/waltz/tls/test_tls_openssl.c +++ b/src/waltz/tls/test_tls_openssl.c @@ -91,7 +91,7 @@ _fdtls_sendmsg( void const * handshake, ulong record_sz, uint encryption_level, int flush ) { - (void)handshake; (void)flush; + (void)handshake; (void)flush; test_record_log( record, record_sz, !!_is_ossl_to_fd ); test_record_send( &_fdtls_out, encryption_level, record, record_sz ); return 1; @@ -195,7 +195,7 @@ _ossl_info( SSL const * ssl, int type, int val ) { (void)ssl; (void)type; (void)val; - FD_LOG_DEBUG(( "OpenSSL info: type=%#x val=%d", type, val )); + FD_LOG_DEBUG(( "OpenSSL info: type=%#x val=%d", (uint)type, val )); if( (type&SSL_CB_LOOP)==SSL_CB_LOOP ) FD_LOG_INFO(( "OpenSSL state: %s", SSL_state_string_long( ssl ) )); } @@ -312,8 +312,13 @@ test_server( SSL_CTX * ctx ) { int res = SSL_do_handshake( ssl ); FD_TEST( SSL_get_error( ssl, res )==SSL_ERROR_WANT_READ ); - /* ServerHello, EncryptedExtensions, Certificate, CertificateVerify, server Finished */ + /* RetryHelloRequest OR ServerHello, EncryptedExtensions, Certificate, CertificateVerify, server Finished */ _fd_server_respond( server, hs ); + if( hs->base.state==FD_TLS_HS_START ) { + /* In case of RetryHelloRequest */ + _ossl_respond( ssl ); + _fd_server_respond( server, hs ); + } _ossl_respond( ssl ); @@ -475,6 +480,10 @@ main( int argc, SSL_CTX_set_alpn_protos( ctx, (uchar const *)"\xasolana-tpu", 11UL ); SSL_CTX_set_alpn_select_cb( ctx, _ossl_alpn_select, NULL ); + /* Test server with and without RetryHelloRequest */ + FD_TEST( 1==SSL_CTX_set1_groups_list( ctx, "ffdhe8192:X25519" ) ); + test_server( ctx ); + FD_TEST( 1==SSL_CTX_set1_groups_list( ctx, "X25519" ) ); test_server( ctx ); /* Test client with and without cert */