Skip to content

Careful resume structs + encode/decode code #5131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions src/core/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,22 @@
PacketSpace->CurrentKeyPhaseBytesSent = 0;
}

BOOLEAN
IsQuicIncomingResumptionTicketSupported(
_In_ QUIC_VAR_INT TicketVersion
)
{
if (TicketVersion >= CXPLAT_TLS_RESUMPTION_TICKET_VERSION &&
TicketVersion <= CXPLAT_TLS_RESUMPTION_TICKET_MAX_VERSION) {
return TRUE;
}

return FALSE;

Check warning on line 2157 in src/core/crypto.c

View check run for this annotation

Codecov / codecov/patch

src/core/crypto.c#L2157

Added line #L2157 was not covered by tests
}

//
// Server calls this function to generate the resumption ticket for a specific client
//
QUIC_STATUS
QuicCryptoEncodeServerTicket(
_In_opt_ QUIC_CONNECTION* Connection,
Expand Down Expand Up @@ -2207,6 +2223,7 @@
QuicVarIntSize(AppDataLength) +
AlpnLength +
EncodedTPLength +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH +
AppDataLength);

TicketBuffer = CXPLAT_ALLOC_NONPAGED(TotalTicketLength, QUIC_POOL_SERVER_CRYPTO_TICKET);
Expand All @@ -2233,7 +2250,7 @@
//

_Analysis_assume_(sizeof(*TicketBuffer) >= 8);
uint8_t* TicketCursor = QuicVarIntEncode(CXPLAT_TLS_RESUMPTION_TICKET_VERSION, TicketBuffer);
uint8_t* TicketCursor = QuicVarIntEncode(CXPLAT_TLS_RESUMPTION_TICKET_MAX_VERSION, TicketBuffer);
CxPlatCopyMemory(TicketCursor, &QuicVersion, sizeof(QuicVersion));
TicketCursor += sizeof(QuicVersion);
TicketCursor = QuicVarIntEncode(AlpnLength, TicketCursor);
Expand All @@ -2243,6 +2260,12 @@
TicketCursor += AlpnLength;
CxPlatCopyMemory(TicketCursor, EncodedHSTP + CxPlatTlsTPHeaderSize, EncodedTPLength);
TicketCursor += EncodedTPLength;

//
// TODO: add processing of V2 extension here
//
TicketCursor += RESUMPTION_TICKET_V2_EXTENSION_LENGTH; // Skip the fixed length V2 extension

if (AppDataLength > 0) {
CxPlatCopyMemory(TicketCursor, AppResumptionData, AppDataLength);
TicketCursor += AppDataLength;
Expand All @@ -2263,6 +2286,9 @@
return Status;
}

//
// Server uses this function to decode the resumption ticket presented by the client
//
QUIC_STATUS
QuicCryptoDecodeServerTicket(
_In_ QUIC_CONNECTION* Connection,
Expand Down Expand Up @@ -2292,7 +2318,8 @@
"Resumption Ticket version failed to decode");
goto Error;
}
if (TicketVersion != CXPLAT_TLS_RESUMPTION_TICKET_VERSION) {

if (!IsQuicIncomingResumptionTicketSupported(TicketVersion)) {
QuicTraceEvent(
ConnError,
"[conn][%p] ERROR, %s.",
Expand Down Expand Up @@ -2392,6 +2419,25 @@
}
Offset += (uint16_t)TPLength;

if (TicketVersion == CXPLAT_TLS_RESUMPTION_TICKET_VERSION_V2) {
//
// V2 resumption ticket has a fixed length extension.
//
if (TicketLength < Offset + RESUMPTION_TICKET_V2_EXTENSION_LENGTH) {
QuicTraceEvent(

Check warning on line 2427 in src/core/crypto.c

View check run for this annotation

Codecov / codecov/patch

src/core/crypto.c#L2427

Added line #L2427 was not covered by tests
ConnError,
"[conn][%p] ERROR, %s.",
Connection,
"Resumption Ticket too small for V2 extensions");
goto Error;

Check warning on line 2432 in src/core/crypto.c

View check run for this annotation

Codecov / codecov/patch

src/core/crypto.c#L2432

Added line #L2432 was not covered by tests
}

//
// TODO: add processing of V2 extension here
//
Offset += RESUMPTION_TICKET_V2_EXTENSION_LENGTH;
}

if (TicketLength == Offset + AppTicketLength) {
Status = QUIC_STATUS_SUCCESS;
*AppDataLength = (uint32_t)AppTicketLength;
Expand Down
22 changes: 19 additions & 3 deletions src/core/quicdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,11 +444,27 @@ CXPLAT_STATIC_ASSERT(
//
#define QUIC_DEFAULT_SERVER_RESUMPTION_LEVEL QUIC_SERVER_NO_RESUME


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Extra empty line

//
// Version of the wire-format for resumption tickets.
// This needs to be incremented for each change in order or count of fields.
// Valid Resumption Ticket Versions - these must be contiguous
//
#define CXPLAT_TLS_RESUMPTION_TICKET_VERSION_V1 1
#define CXPLAT_TLS_RESUMPTION_TICKET_VERSION_V2 2

//
// Length in bytes of V2 extension to the resumption ticket
//
#define RESUMPTION_TICKET_V2_EXTENSION_LENGTH 64

//
// Min version of the wire-format for resumption tickets.
//
#define CXPLAT_TLS_RESUMPTION_TICKET_VERSION CXPLAT_TLS_RESUMPTION_TICKET_VERSION_V1

//
// Max version of the wire-format for resumption tickets.
//
#define CXPLAT_TLS_RESUMPTION_TICKET_VERSION 1
#define CXPLAT_TLS_RESUMPTION_TICKET_MAX_VERSION CXPLAT_TLS_RESUMPTION_TICKET_VERSION_V2

//
// Version of the blob for client resumption tickets.
Expand Down
72 changes: 65 additions & 7 deletions src/core/unittest/TicketTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,9 @@ TEST(ResumptionTicketTest, ServerDecFail)
CxPlatZeroMemory(&Connection, sizeof(Connection));
Connection.Stats.QuicVersion = QUIC_VERSION_1;

uint8_t InputTicketBuffer[8 + TransportParametersLength + sizeof(Alpn) + sizeof(AppData)] = {
CXPLAT_TLS_RESUMPTION_TICKET_VERSION,
uint8_t InputTicketBuffer[8 + TransportParametersLength + sizeof(Alpn) +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH + sizeof(AppData)] = {
CXPLAT_TLS_RESUMPTION_TICKET_MAX_VERSION,
0,0,0,1, // QUIC version
4, // ALPN length
0, // TP length, update after encoding
Expand Down Expand Up @@ -644,15 +645,45 @@ TEST(ResumptionTicketTest, ServerDecFail)
EncodedTPLength - CxPlatTlsTPHeaderSize);
InputTicketBuffer[6] = (uint8_t)(EncodedTPLength - CxPlatTlsTPHeaderSize);

ASSERT_GT(sizeof(InputTicketBuffer), EncodedTPLength + sizeof(AppData));
ASSERT_GT(sizeof(InputTicketBuffer),
(EncodedTPLength + RESUMPTION_TICKET_V2_EXTENSION_LENGTH + sizeof(AppData)));
CxPlatCopyMemory(
&InputTicketBuffer[8 + sizeof(Alpn) + (EncodedTPLength - CxPlatTlsTPHeaderSize)],
&InputTicketBuffer[(8 + sizeof(Alpn) + (EncodedTPLength - CxPlatTlsTPHeaderSize) +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH)],
AppData,
sizeof(AppData));

//
// Validate that the hand-crafted ticket is correct
//
TEST_QUIC_SUCCEEDED(
QuicCryptoDecodeServerTicket(
&Connection,
(8 + (uint16_t)sizeof(Alpn) + (uint16_t)(EncodedTPLength - CxPlatTlsTPHeaderSize) +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH + (uint16_t)sizeof(AppData)),
InputTicketBuffer,
AlpnList,
sizeof(AlpnList),
&DecodedTP,
&DecodedAppData,
&DecodedAppDataLength));
ASSERT_EQ(DecodedAppDataLength, sizeof(AppData));
CompareTransportParameters(&HandshakeTP, &DecodedTP);

//
// Validate decoding of hand-crafted v1 ticket
//
InputTicketBuffer[0] = CXPLAT_TLS_RESUMPTION_CLIENT_TICKET_VERSION;

//
// Without modifying the buffer size, simply move the AppData up the buffer and
// pass in a smaller input buffer length here to match V1 tickets
//
CxPlatCopyMemory(
&InputTicketBuffer[8 + sizeof(Alpn) + (EncodedTPLength - CxPlatTlsTPHeaderSize)],
AppData,
sizeof(AppData));

TEST_QUIC_SUCCEEDED(
QuicCryptoDecodeServerTicket(
&Connection,
Expand Down Expand Up @@ -794,7 +825,7 @@ TEST(ResumptionTicketTest, ServerDecFail)
&DecodedAppData,
&DecodedAppDataLength));

// Not enough room for App Data
// Not enough room for V2 extension
ASSERT_EQ(
QUIC_STATUS_INVALID_PARAMETER,
QuicCryptoDecodeServerTicket(
Expand All @@ -810,7 +841,34 @@ TEST(ResumptionTicketTest, ServerDecFail)
QUIC_STATUS_INVALID_PARAMETER,
QuicCryptoDecodeServerTicket(
&Connection,
8 + (uint16_t)sizeof(Alpn) + (uint16_t)(EncodedTPLength - CxPlatTlsTPHeaderSize) + (uint16_t)(sizeof(AppData) - 1),
(8 + (uint16_t)sizeof(Alpn) + (uint16_t)(EncodedTPLength - CxPlatTlsTPHeaderSize +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH -1)),
InputTicketBuffer,
AlpnList,
sizeof(AlpnList),
&DecodedTP,
&DecodedAppData,
&DecodedAppDataLength));

// Not enough room for App Data
ASSERT_EQ(
QUIC_STATUS_INVALID_PARAMETER,
QuicCryptoDecodeServerTicket(
&Connection,
(8 + (uint16_t)sizeof(Alpn) + (uint16_t)(EncodedTPLength - CxPlatTlsTPHeaderSize) +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH),
InputTicketBuffer,
AlpnList,
sizeof(AlpnList),
&DecodedTP,
&DecodedAppData,
&DecodedAppDataLength));
ASSERT_EQ(
QUIC_STATUS_INVALID_PARAMETER,
QuicCryptoDecodeServerTicket(
&Connection,
(8 + (uint16_t)sizeof(Alpn) + (uint16_t)(EncodedTPLength - CxPlatTlsTPHeaderSize) +
RESUMPTION_TICKET_V2_EXTENSION_LENGTH + (uint16_t)(sizeof(AppData) - 1)),
InputTicketBuffer,
AlpnList,
sizeof(AlpnList),
Expand All @@ -827,7 +885,7 @@ TEST(ResumptionTicketTest, ServerDecFail)
8 + (uint16_t)sizeof(Alpn) + (uint16_t)(EncodedTPLength - CxPlatTlsTPHeaderSize) + (uint16_t)sizeof(AppData);

// Incorrect ticket version
InputTicketBuffer[0] = CXPLAT_TLS_RESUMPTION_TICKET_VERSION + 1;
InputTicketBuffer[0] = CXPLAT_TLS_RESUMPTION_TICKET_MAX_VERSION + 1;
ASSERT_EQ(
QUIC_STATUS_INVALID_PARAMETER,
QuicCryptoDecodeServerTicket(
Expand Down
Loading