[tls] Add support for TLS version 1.3 HelloRetryRequest

When the server does not want to use the key exchange algorithm
offered in our ClientHello, it may respond with a HelloRetryRequest
that specifies a new key exchange algorithm.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-09-22 22:37:11 +01:00
parent f35f5977c6
commit 744cdb451e
5 changed files with 114 additions and 18 deletions
+20
View File
@@ -64,6 +64,10 @@ static const char * tls_map_name ( const uint8_t *map ) {
return "KeyShareClientHello";
} else if ( map == tls_key_share_entry_map ) {
return "KeyShareEntry";
} else if ( map == tls_key_share_hello_retry_request_map ) {
return "KeyShareHelloRetryRequest";
} else if ( map == tls_key_share_server_hello_map ) {
return "KeyShareServerHello";
} else if ( map == tls_max_fragment_length_map ) {
return "MaxFragmentLength";
} else if ( map == tls_named_group_list_map ) {
@@ -689,6 +693,7 @@ TLS_DESCR_MAPPING ( tls_client_hello ) = {
TLS_VAR16 ( tls_client_hello, TLS_VERSION_BASE, suites ),
TLS_VAR08 ( tls_client_hello, TLS_VERSION_BASE, compression ),
TLS_EXT16 ( tls_client_hello, TLS_VERSION_BASE, ext ),
TLS_EXTND ( tls_client_hello, TLS_COOKIE, ext.cookie ),
TLS_EXTND ( tls_client_hello, TLS_EXTENDED_MASTER_SECRET, ext.ems ),
TLS_EXTND ( tls_client_hello, TLS_MAX_FRAGMENT_LENGTH, ext.frag ),
TLS_EXTND ( tls_client_hello, TLS_NAMED_GROUP, ext.groups ),
@@ -754,6 +759,20 @@ TLS_DESCR_MAPPING ( tls_key_share_entry ) = {
TLS_EXTRA ( tls_key_share_entry, TLS_VERSION_BASE, next ),
};
/** KeyShareHelloRetryRequest descriptor mapping */
TLS_DESCR_MAPPING ( tls_key_share_hello_retry_request ) = {
TLS_MAPSZ ( tls_key_share_hello_retry_request ),
TLS_FIXED ( tls_key_share_hello_retry_request, TLS_VERSION_BASE,
group ),
};
/** KeyShareServerHello descriptor mapping */
TLS_DESCR_MAPPING ( tls_key_share_server_hello ) = {
TLS_MAPSZ ( tls_key_share_server_hello ),
TLS_FIXED ( tls_key_share_server_hello, TLS_VERSION_BASE, group ),
TLS_VAR16 ( tls_key_share_server_hello, TLS_VERSION_BASE, public ),
};
/** MaxFragmentLength descriptor mapping */
TLS_DESCR_MAPPING ( tls_max_fragment_length ) = {
TLS_MAPSZ ( tls_max_fragment_length ),
@@ -799,6 +818,7 @@ TLS_DESCR_MAPPING ( tls_server_hello ) = {
TLS_EXTND ( tls_server_hello, TLS_EXTENDED_MASTER_SECRET, ext.ems ),
TLS_EXTND ( tls_server_hello, TLS_SUPPORTED_VERSIONS, ext.supver ),
TLS_EXTND ( tls_server_hello, TLS_KEY_SHARE, ext.key ),
TLS_EXTND ( tls_server_hello, TLS_COOKIE, ext.cookie ),
};
/** ServerHello descriptor mapping */
+5
View File
@@ -188,6 +188,9 @@ union tls_server_random {
/* TLS supported versions extension */
#define TLS_SUPPORTED_VERSIONS 43
/* TLS cookie extension */
#define TLS_COOKIE 44
/* TLS pre-shared key modes extension */
#define TLS_PSK_MODES 45
@@ -504,6 +507,8 @@ struct tls_connection {
int extended_master_secret;
/** Verification data */
struct tls_verify_data verify;
/** Cookie */
struct tls_cursor cookie;
/** Secure channel */
struct secure_channel channel;
+32
View File
@@ -428,6 +428,8 @@ struct tls_client_hello {
struct {
/** All extensions */
struct tls_cursor all;
/** Cookie extension */
struct tls_cursor cookie;
/** Extended master secret extension */
struct tls_cursor ems;
/** Maximum fragment length extension */
@@ -522,6 +524,32 @@ struct tls_key_share_entry {
struct tls_cursor next;
};
/** KeyShareHelloRetryRequest descriptor */
struct tls_key_share_hello_retry_request {
/** Named group */
uint16_t __attribute__ (( aligned ( 1 ) )) *group;
};
/** KeyShareServerHello descriptor */
struct tls_key_share_server_hello {
/** Named group */
uint16_t __attribute__ (( aligned ( 1 ) )) *group;
/** Public key */
struct tls_cursor public;
};
/** KeyShareServerHello/KeyShareHelloRetryRequest combined descriptor */
union tls_key_share_server {
/** Named group (present in both) */
uint16_t __attribute__ (( aligned ( 1 ) )) *group;
/** KeyShareHelloRetryRequest descriptor */
struct tls_key_share_hello_retry_request hrr;
/** KeyShareServerHello descriptor */
struct tls_key_share_server_hello hello;
/** Raw pointer/length array */
union tls_ptr_len desc[0];
};
/** MaxFragmentLength descriptor */
struct tls_max_fragment_length {
/** Maximum fragment length */
@@ -593,6 +621,8 @@ struct tls_server_hello {
struct tls_cursor supver;
/** Key share extension */
struct tls_cursor key;
/** Cookie extension */
struct tls_cursor cookie;
} ext;
};
@@ -762,6 +792,8 @@ extern TLS_DESCR_MAPPING ( tls_extension );
extern TLS_DESCR_MAPPING ( tls_hello_request );
extern TLS_DESCR_MAPPING ( tls_key_share_client_hello );
extern TLS_DESCR_MAPPING ( tls_key_share_entry );
extern TLS_DESCR_MAPPING ( tls_key_share_hello_retry_request );
extern TLS_DESCR_MAPPING ( tls_key_share_server_hello );
extern TLS_DESCR_MAPPING ( tls_max_fragment_length );
extern TLS_DESCR_MAPPING ( tls_named_group_list );
extern TLS_DESCR_MAPPING ( tls_new_session_ticket );
+55 -18
View File
@@ -186,14 +186,14 @@ FILE_SECBOOT ( PERMITTED );
#define EINFO_EPROTO_CIPHER_CHANGE \
__einfo_uniqify ( EINFO_EPROTO, 0x02, \
"Illegal cipher change" )
#define EPROTO_KEY_SHARE __einfo_error ( EINFO_EPROTO_KEY_SHARE )
#define EINFO_EPROTO_KEY_SHARE \
__einfo_uniqify ( EINFO_EPROTO, 0x03, \
"Multiple key shares offered" )
#define EPROTO_VALIDATION __einfo_error ( EINFO_EPROTO_VALIDATION )
#define EINFO_EPROTO_VALIDATION \
__einfo_uniqify ( EINFO_EPROTO, 0x04, \
"Certificate validation already in progress" )
#define EPROTO_RETRY __einfo_error ( EINFO_EPROTO_RETRY )
#define EINFO_EPROTO_RETRY \
__einfo_uniqify ( EINFO_EPROTO, 0x05, \
"Illegal retry request" )
/* Avoid dragging in RSA support unconditionally */
struct pubkey_algorithm rsa_algorithm __attribute__ (( weak ));
@@ -201,6 +201,19 @@ struct pubkey_algorithm rsa_algorithm __attribute__ (( weak ));
/** List of TLS session */
static LIST_HEAD ( tls_sessions );
/** ServerHello downgrade magic value */
static const uint8_t tls_downgrade_magic[7] = TLS_SERVER_DOWNGRADE_MAGIC;
/** HelloRetryRequest magic value */
static const struct tls_random tls_hrr_magic = {
.bytes = {
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
},
};
static void tls_tx_resume_all ( struct tls_session *session );
static struct io_buffer * tls_alloc_iob ( struct tls_connection *tls,
size_t len );
@@ -407,6 +420,7 @@ static void free_tls ( struct refcnt *refcnt ) {
zfree ( tls->new_ticket.data );
tls_clear_digest ( tls );
zfree ( tls->verify.dynamic );
zfree ( tls->cookie.data );
tls_clear_cipher ( tls, &tls->tx.cipherspec );
tls_clear_cipher ( tls, &tls->rx.cipherspec );
list_for_each_entry_safe ( iobuf, tmp, &tls->rx.data, list ) {
@@ -2058,6 +2072,10 @@ static int tls_client_hello ( struct tls_connection *tls,
/* Prepare MaxFragmentLength extension */
ext->frag.len = sizeof ( *frag );
/* Prepare Cookie extension */
ext->cookie.data = tls->cookie.data;
ext->cookie.len = tls->cookie.len;
/* Prepare RenegotiationInfo extension */
reneg->verify.data = tls->verify.client;
reneg->verify.len = tls->verify.len;
@@ -2680,17 +2698,18 @@ static int tls_new_hello_request ( struct tls_connection *tls,
*/
static int tls_new_server_hello ( struct tls_connection *tls,
const struct tls_cursor *cursor ) {
static const uint8_t downgrade_magic[7] = TLS_SERVER_DOWNGRADE_MAGIC;
struct tls_session *session = tls->session;
struct tls_named_group *group;
struct tls_server_hello hello;
struct tls_renegotiation_info reneg;
struct tls_supported_version supver;
struct tls_key_share_entry key;
union tls_key_share_server key;
union tls_server_random *random;
const uint8_t *key_map;
uint16_t version;
uint16_t suite;
size_t verify_len;
int retry;
int rc;
/* Parse ServerHello structure */
@@ -2720,16 +2739,22 @@ static int tls_new_server_hello ( struct tls_connection *tls,
}
/* Parse KeyShareEntry, if present */
if ( ( rc = tls_parse_opt ( tls_key_share_entry, tls->version,
&hello.ext.key, &key ) ) != 0 ) {
DBGC ( tls, "TLS %p could not parse KeyShareEntry: %s\n",
tls, strerror ( rc ) );
retry = ( memcmp ( random, &tls_hrr_magic,
sizeof ( *random ) ) == 0 );
key_map = ( retry ? tls_key_share_hello_retry_request_map :
tls_key_share_server_hello_map );
if ( ( rc = tls_parse_opt_map ( key_map, tls->version,
&hello.ext.key, key.desc ) ) != 0 ) {
DBGC ( tls, "TLS %p could not parse KeyShare%s: %s\n",
tls, ( retry ? "HelloRetryRequest" : "ServerHello" ),
strerror ( rc ) );
return rc;
}
if ( key.next.len ) {
DBGC ( tls, "TLS %p has multiple KeyShareEntry structures\n",
tls );
return -EPROTO_KEY_SHARE;
/* Refuse repeated retries */
if ( retry && ( tls->suite != &tls_cipher_suite_null ) ) {
DBGC ( tls, "TLS %p refusing repeated retry request\n", tls );
return -EPROTO_RETRY;
}
/* Handle secure renegotiation */
@@ -2767,7 +2792,7 @@ static int tls_new_server_hello ( struct tls_connection *tls,
/* Check for downgrade attacks */
if ( ( TLS_VERSION_TLS_1_1 < TLS_VERSION_MAX ) &&
( tls->version < TLS_VERSION_MAX ) &&
( memcmp ( random->downgrade.magic, downgrade_magic,
( memcmp ( random->downgrade.magic, tls_downgrade_magic,
sizeof ( random->downgrade.magic ) ) == 0 ) &&
( ( random->downgrade.version + TLS_VERSION_TLS_1_1 ) <
TLS_VERSION_MAX ) ) {
@@ -2823,14 +2848,26 @@ static int tls_new_server_hello ( struct tls_connection *tls,
}
/* Agree shared key, if applicable */
if ( key.public.data &&
if ( ( ! retry ) && key.hello.public.data &&
( ( rc = tls_key_agree ( tls, tls->group,
&key.public ) ) != 0 ) ) {
&key.hello.public ) ) != 0 ) ) {
return rc;
}
/* Record cookie, if any */
if ( ( rc = tls_copy ( &hello.ext.cookie, &tls->cookie ) ) != 0 )
return rc;
/* Retry ClientHello , if applicable */
if ( is_pending ( &tls->client.negotiation ) && retry ) {
tlskey_message ( &tls->key );
tls->tx.pending = TLS_TX_CLIENT_HELLO;
tls_tx_resume ( tls );
DBGC ( tls, "TLS %p retrying hello\n", tls );
}
/* Schedule change to handshake traffic keys, if applicable */
if ( tls_version ( tls, TLS_VERSION_TLS_1_3 ) ) {
if ( tls_version ( tls, TLS_VERSION_TLS_1_3 ) && ( ! retry ) ) {
tls->tx.cipherspec.pending = &tls_handshake;
tls->rx.cipherspec.pending = &tls_handshake;
}
+2
View File
@@ -450,6 +450,8 @@ static void tlsfmt_test_exec ( void ) {
map_ok ( tls_hello_request );
map_ok ( tls_key_share_client_hello );
map_ok ( tls_key_share_entry );
map_ok ( tls_key_share_hello_retry_request );
map_ok ( tls_key_share_server_hello );
map_ok ( tls_max_fragment_length );
map_ok ( tls_named_group_list );
map_ok ( tls_new_session_ticket );