[tls] Clear any saved state when saving a new session

If the server switches from providing stateless session tickets to
providing stateful session IDs, then we will currently end up with a
stored session that retains the old session ticket along with the new
session ID.  (This behaviour has been observed when connecting to an
Apache server that requires per-directory client authentication or
per-directory cipher suites: the server originally provides a
stateless session ticket but then, after renegotiation to request the
client certificate, provides a stateful session ID instead.)

Reset both the session ID and the session ticket whenever a new
session is saved (i.e. if the server provides either a new session ID
or a new session ticket), so that we do not retain stale credentials.

If no new session ID is provided, then create a new random session ID
(rather than leaving the session with an empty session ID) since we
rely on the echoed non-empty session ID to determine whether or not
the server is choosing to resume, even for stateless sessions.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-09-21 17:48:06 +01:00
parent b36e8472d4
commit ed4a9b2e67
+50 -20
View File
@@ -1553,6 +1553,26 @@ static struct secure_channel_operations tls_channel_ops = {
******************************************************************************
*/
/**
* Set a random session ID
*
* @v tls TLS connection
* @v label Label for ephemeral secret
*
* The session ID will be generated deterministically using the
* per-connection ephemeral secret and will therefore be guaranteed to
* differ between connections (including restarted connections).
*/
static void tls_set_session_id ( struct tls_connection *tls,
const char *label ) {
struct tls_session *session = tls->session;
/* Generate session ID */
channel_ephemeral_label ( &tls->channel, label, session->id.data,
sizeof ( session->id.data ) );
session->id.len = sizeof ( session->id.data );
}
/**
* Find or create session for TLS connection
*
@@ -1561,7 +1581,7 @@ static struct secure_channel_operations tls_channel_ops = {
* @ret rc Return status code
*/
static int tls_session ( struct tls_connection *tls, const char *name ) {
static const char label[] = "tls session id";
static const char label[] = "tls initial session id";
struct tls_session *session;
char *name_copy;
int rc;
@@ -1573,7 +1593,10 @@ static int tls_session ( struct tls_connection *tls, const char *name ) {
( tls->client.key == session->key ) ) {
ref_get ( &session->refcnt );
tls->session = session;
DBGC ( tls, "TLS %p joining session %s\n", tls, name );
DBGC ( tls, "TLS %p joining %s session %s:\n", tls,
( session->ticket.len ? "stateless" :
"stateful" ), name );
DBGC_HDA ( tls, 0, session->id.data, session->id.len );
return 0;
}
}
@@ -1595,14 +1618,11 @@ static int tls_session ( struct tls_connection *tls, const char *name ) {
list_add ( &session->list, &tls_sessions );
/* Generate random initial session ID */
channel_ephemeral_label ( &tls->channel, label, session->id.data,
sizeof ( session->id.data ) );
session->id.len = sizeof ( session->id.data );
/* Record session */
tls->session = session;
tls_set_session_id ( tls, label );
DBGC ( tls, "TLS %p created session %s:\n", tls, name );
DBGC_HDA ( tls, 0, session->id.data, session->id.len );
DBGC ( tls, "TLS %p created session %s\n", tls, name );
return 0;
ref_put ( &session->refcnt );
@@ -1617,9 +1637,20 @@ static int tls_session ( struct tls_connection *tls, const char *name ) {
* @ret rc Return status code
*/
static int tls_save ( struct tls_connection *tls ) {
static const char label[] = "tls reset session id";
struct tls_session *session = tls->session;
const char *name = session->name;
int rc;
/* Sanity check */
assert ( tls->new_id.len || tls->new_ticket.len );
/* Clear any existing session state */
tls_set_session_id ( tls, label );
zfree ( session->ticket.data );
session->ticket.data = NULL;
session->ticket.len = 0;
/* Save pre-shared key and peer identity */
if ( ( rc = channel_save ( &tls->channel, &session->psid ) ) != 0 ) {
DBGC ( tls, "TLS %p could not save: %s\n",
@@ -1628,19 +1659,22 @@ static int tls_save ( struct tls_connection *tls ) {
}
/* Record new session ID, if provided */
if ( tls->new_id.len ) {
if ( tls->new_id.len )
memcpy ( &session->id, &tls->new_id, sizeof ( session->id ) );
DBGC ( tls, "TLS %p saved session ID:\n", tls );
DBGC_HDA ( tls, 0, session->id.data, session->id.len );
}
DBGC ( tls, "TLS %p %s session %s:\n",
tls, ( tls->new_id.len ? "saved stateful" : "reset" ), name );
DBGC_HDA ( tls, 0, session->id.data, session->id.len );
/* Record (and consume) new session ticket, if provided */
if ( tls->new_ticket.len ) {
zfree ( session->ticket.data );
session->ticket.data = tls->new_ticket.data;
session->ticket.len = tls->new_ticket.len;
tls->new_ticket.data = NULL;
tls->new_ticket.len = 0;
DBGC ( tls, "TLS %p saved stateless session %s:\n",
tls, name );
DBGC_HDA ( tls, 0, session->ticket.data,
session->ticket.len );
}
return 0;
@@ -1656,8 +1690,9 @@ static int tls_resume ( struct tls_connection *tls ) {
struct tls_session *session = tls->session;
int rc;
DBGC ( tls, "TLS %p resuming session ID:\n", tls );
DBGC_HDA ( tls, 0, session->id.data, session->id.len );
DBGC ( tls, "TLS %p resuming %s session %s\n",
tls, ( session->ticket.len ? "stateless" : "stateful" ),
session->name );
/* Load pre-shared key and peer identity */
if ( ( rc = channel_load ( &tls->channel, &session->psid ) ) != 0 ) {
@@ -2675,9 +2710,6 @@ static int tls_new_server_hello ( struct tls_connection *tls,
tls->new_id.len = hello.session_id.len;
memcpy ( tls->new_id.data, hello.session_id.data,
hello.session_id.len );
DBGC ( tls, "TLS %p new session ID:\n", tls );
DBGC_HDA ( tls, 0, hello.session_id.data,
hello.session_id.len );
}
}
@@ -2746,8 +2778,6 @@ static int tls_new_session_ticket ( struct tls_connection *tls,
return -ENOMEM;
memcpy ( tls->new_ticket.data, ticket.ticket.data, ticket.ticket.len );
tls->new_ticket.len = ticket.ticket.len;
DBGC ( tls, "TLS %p new session ticket:\n", tls );
DBGC_HDA ( tls, 0, tls->new_ticket.data, tls->new_ticket.len );
return 0;
}