Stateless Reset Token Uses Unkeyed FNV Derivation
Summary
RFC 9000 requires a stateless reset token to be difficult to guess. In this quiche source tree, the server-side token used for transport parameters, NEW_CONNECTION_ID frames, and time-wait stateless reset packets is derived by applying unkeyed FNV1a_128_Hash to the connection ID.
The reviewed production paths do not show a per-connection random secret, a server static secret, an HMAC/HKDF-based keyed PRF, or another configurable token-generation hook for the default stateless reset token path.
Standard Requirement
Official reference: https://www.rfc-editor.org/rfc/rfc9000.html#section-10.3.2
RFC 9000 Section 10.3.2, "Calculating a Stateless Reset Token":
The stateless reset token MUST be difficult to guess.
A single static key can be used across all connections to the same endpoint by generating the proof using a pseudorandom function that takes a static key and the connection ID chosen by the endpoint as input.
The standard allows different designs, but the token still needs to be unpredictable to an observer. If the token is derived from a connection ID, the derivation needs secret material, such as an endpoint static key used with a pseudorandom function.
Relevant Source Code
Token Generation Helper
quiche-main/quiche-main/quiche/quic/core/quic_utils.cc:105
absl::uint128 QuicUtils::FNV1a_128_Hash(absl::string_view data) {
return FNV1a_128_Hash_Three(data, absl::string_view(), absl::string_view());
}
quiche-main/quiche-main/quiche/quic/core/quic_utils.cc:513
StatelessResetToken QuicUtils::GenerateStatelessResetToken(
const QuicConnectionId& connection_id) {
static_assert(sizeof(absl::uint128) == sizeof(StatelessResetToken),
"bad size");
static_assert(alignof(absl::uint128) >= alignof(StatelessResetToken),
"bad alignment");
absl::uint128 hash = FNV1a_128_Hash(
absl::string_view(connection_id.data(), connection_id.length()));
return *reinterpret_cast<StatelessResetToken*>(&hash);
}
The only input is connection_id. There is no secret or key parameter, and FNV-1a is not a keyed pseudorandom function.
Transport Parameter Path
quiche-main/quiche-main/quiche/quic/core/quic_session.cc:244
if (perspective() == Perspective::IS_SERVER &&
connection_->version().IsIetfQuic()) {
config()->SetStatelessResetTokenToSend(GetStatelessResetToken());
}
quiche-main/quiche-main/quiche/quic/core/quic_session.cc:2748
StatelessResetToken QuicSession::GetStatelessResetToken() const {
return QuicUtils::GenerateStatelessResetToken(connection_->connection_id());
}
quiche-main/quiche-main/quiche/quic/core/quic_config.cc:1215
if (stateless_reset_token_.HasSendValue()) {
StatelessResetToken stateless_reset_token =
stateless_reset_token_.GetSendValue();
params->stateless_reset_token.assign(
reinterpret_cast<const char*>(&stateless_reset_token),
reinterpret_cast<const char*>(&stateless_reset_token) +
sizeof(stateless_reset_token));
}
The token generated from connection_id is serialized into the IETF stateless_reset_token transport parameter.
NEW_CONNECTION_ID Path
quiche-main/quiche-main/quiche/quic/core/quic_connection_id_manager.cc:317
QuicNewConnectionIdFrame frame;
frame.connection_id = *new_cid;
frame.sequence_number = next_connection_id_sequence_number_++;
frame.stateless_reset_token =
QuicUtils::GenerateStatelessResetToken(frame.connection_id);
The stateless reset token sent in NEW_CONNECTION_ID is also derived from the connection ID through the same helper.
Time-Wait Stateless Reset Packet Path
quiche-main/quiche-main/quiche/quic/core/quic_time_wait_list_manager.cc:323
return QuicFramer::BuildIetfStatelessResetPacket(
connection_id, received_packet_length,
GetStatelessResetToken(connection_id));
quiche-main/quiche-main/quiche/quic/core/quic_time_wait_list_manager.cc:472
StatelessResetToken QuicTimeWaitListManager::GetStatelessResetToken(
QuicConnectionId connection_id) const {
return QuicUtils::GenerateStatelessResetToken(connection_id);
}
The stateless reset packet builder receives a token produced by the same unkeyed derivation.
Review Notes
The function is not an unused helper. Static call-chain review shows it feeds the server transport parameter, NEW_CONNECTION_ID frames, and stateless reset packet construction.
The issue is also separate from duplicate-token history tracking. RFC 9000 says endpoints are not required to compare every new token against all previous values. The relevant point here is that the token value is derived from public input using a public, unkeyed algorithm.
Additional checks:
- No production
GetStatelessResetToken() const override was found that replaces QuicSession::GetStatelessResetToken().
SetStatelessResetTokenToSend() only stores the token in config; the production call site passes GetStatelessResetToken().
SetPreferredAddressConnectionIdAndTokenToSend() is limited to preferred address handling. Its token comes from a QuicNewConnectionIdFrame, which is populated with QuicUtils::GenerateStatelessResetToken(frame.connection_id).
- A random connection ID does not make this derivation secret. The connection ID is visible on the wire, so an observer who knows the algorithm can compute
FNV1a_128_Hash(connection_id).
- Address-validation token code using
source_address_token_secret, HKDF, and CryptoSecretBoxer protects NEW_TOKEN/Retry-style address validation tokens. It is not used by the stateless reset token paths above.
Security Impact
An observer that knows the connection ID and the public quiche derivation algorithm can compute the stateless reset token for that connection ID. This weakens the intended protection that only an endpoint with the relevant secret material can produce a valid stateless reset token.
Fix Direction
Use endpoint-private secret material when deriving stateless reset tokens. A typical design is HMAC-SHA256 or HKDF with a server static secret and the connection ID as input, truncated to 16 bytes. The transport parameter path, NEW_CONNECTION_ID path, and time-wait stateless reset packet path should share the same keyed derivation.
Stateless Reset Token Uses Unkeyed FNV Derivation
Summary
RFC 9000 requires a stateless reset token to be difficult to guess. In this quiche source tree, the server-side token used for transport parameters, NEW_CONNECTION_ID frames, and time-wait stateless reset packets is derived by applying unkeyed
FNV1a_128_Hashto the connection ID.The reviewed production paths do not show a per-connection random secret, a server static secret, an HMAC/HKDF-based keyed PRF, or another configurable token-generation hook for the default stateless reset token path.
Standard Requirement
Official reference: https://www.rfc-editor.org/rfc/rfc9000.html#section-10.3.2
RFC 9000 Section 10.3.2, "Calculating a Stateless Reset Token":
The standard allows different designs, but the token still needs to be unpredictable to an observer. If the token is derived from a connection ID, the derivation needs secret material, such as an endpoint static key used with a pseudorandom function.
Relevant Source Code
Token Generation Helper
quiche-main/quiche-main/quiche/quic/core/quic_utils.cc:105quiche-main/quiche-main/quiche/quic/core/quic_utils.cc:513The only input is
connection_id. There is no secret or key parameter, and FNV-1a is not a keyed pseudorandom function.Transport Parameter Path
quiche-main/quiche-main/quiche/quic/core/quic_session.cc:244quiche-main/quiche-main/quiche/quic/core/quic_session.cc:2748quiche-main/quiche-main/quiche/quic/core/quic_config.cc:1215The token generated from
connection_idis serialized into the IETFstateless_reset_tokentransport parameter.NEW_CONNECTION_ID Path
quiche-main/quiche-main/quiche/quic/core/quic_connection_id_manager.cc:317QuicNewConnectionIdFrame frame; frame.connection_id = *new_cid; frame.sequence_number = next_connection_id_sequence_number_++; frame.stateless_reset_token = QuicUtils::GenerateStatelessResetToken(frame.connection_id);The stateless reset token sent in NEW_CONNECTION_ID is also derived from the connection ID through the same helper.
Time-Wait Stateless Reset Packet Path
quiche-main/quiche-main/quiche/quic/core/quic_time_wait_list_manager.cc:323quiche-main/quiche-main/quiche/quic/core/quic_time_wait_list_manager.cc:472The stateless reset packet builder receives a token produced by the same unkeyed derivation.
Review Notes
The function is not an unused helper. Static call-chain review shows it feeds the server transport parameter, NEW_CONNECTION_ID frames, and stateless reset packet construction.
The issue is also separate from duplicate-token history tracking. RFC 9000 says endpoints are not required to compare every new token against all previous values. The relevant point here is that the token value is derived from public input using a public, unkeyed algorithm.
Additional checks:
GetStatelessResetToken() const overridewas found that replacesQuicSession::GetStatelessResetToken().SetStatelessResetTokenToSend()only stores the token in config; the production call site passesGetStatelessResetToken().SetPreferredAddressConnectionIdAndTokenToSend()is limited to preferred address handling. Its token comes from aQuicNewConnectionIdFrame, which is populated withQuicUtils::GenerateStatelessResetToken(frame.connection_id).FNV1a_128_Hash(connection_id).source_address_token_secret, HKDF, andCryptoSecretBoxerprotects NEW_TOKEN/Retry-style address validation tokens. It is not used by the stateless reset token paths above.Security Impact
An observer that knows the connection ID and the public quiche derivation algorithm can compute the stateless reset token for that connection ID. This weakens the intended protection that only an endpoint with the relevant secret material can produce a valid stateless reset token.
Fix Direction
Use endpoint-private secret material when deriving stateless reset tokens. A typical design is HMAC-SHA256 or HKDF with a server static secret and the connection ID as input, truncated to 16 bytes. The transport parameter path, NEW_CONNECTION_ID path, and time-wait stateless reset packet path should share the same keyed derivation.