Interop of draft-ietf-quic-version-negotiation-05 for IETF 112
diff --git a/quic/core/crypto/crypto_utils.cc b/quic/core/crypto/crypto_utils.cc index 2f1fa34..5a94b03 100644 --- a/quic/core/crypto/crypto_utils.cc +++ b/quic/core/crypto/crypto_utils.cc
@@ -641,6 +641,69 @@ return QUIC_NO_ERROR; } +// static +bool CryptoUtils::ValidateChosenVersion( + const QuicVersionLabel& version_information_chosen_version, + const ParsedQuicVersion& session_version, std::string* error_details) { + if (version_information_chosen_version != + CreateQuicVersionLabel(session_version)) { + *error_details = absl::StrCat( + "Detected version mismatch: version_information contained ", + QuicVersionLabelToString(version_information_chosen_version), + " instead of ", ParsedQuicVersionToString(session_version)); + return false; + } + return true; +} + +// static +bool CryptoUtils::ValidateServerVersions( + const QuicVersionLabelVector& version_information_other_versions, + const ParsedQuicVersion& session_version, + const ParsedQuicVersionVector& client_original_supported_versions, + std::string* error_details) { + if (client_original_supported_versions.empty()) { + // We did not receive a version negotiation packet. + return true; + } + // Parse the server's other versions. + ParsedQuicVersionVector parsed_other_versions; + for (const QuicVersionLabel& other_version : + version_information_other_versions) { + ParsedQuicVersion parsed_other_version = + ParseQuicVersionLabel(other_version); + if (parsed_other_version.IsKnown()) { + parsed_other_versions.push_back(parsed_other_version); + } + } + // Find the first version that we originally supported that is listed in the + // server's other versions. + ParsedQuicVersion expected_version = ParsedQuicVersion::Unsupported(); + for (const ParsedQuicVersion& client_version : + client_original_supported_versions) { + if (std::find(parsed_other_versions.begin(), parsed_other_versions.end(), + client_version) != parsed_other_versions.end()) { + expected_version = client_version; + break; + } + } + if (expected_version != session_version) { + *error_details = absl::StrCat( + "Downgrade attack detected: used ", + ParsedQuicVersionToString(session_version), " but ServerVersions(", + version_information_other_versions.size(), ")[", + QuicVersionLabelVectorToString(version_information_other_versions, ",", + 30), + "] ClientOriginalVersions(", client_original_supported_versions.size(), + ")[", + ParsedQuicVersionVectorToString(client_original_supported_versions, ",", + 30), + "]"); + return false; + } + return true; +} + #define RETURN_STRING_LITERAL(x) \ case x: \ return #x
diff --git a/quic/core/crypto/crypto_utils.h b/quic/core/crypto/crypto_utils.h index 15ffe06..2c61a90 100644 --- a/quic/core/crypto/crypto_utils.h +++ b/quic/core/crypto/crypto_utils.h
@@ -217,6 +217,27 @@ const ParsedQuicVersionVector& supported_versions, std::string* error_details); + // Validates that the chosen version from the version_information matches the + // version from the session. Returns true if they match, otherwise returns + // false and fills in |error_details|. + static bool ValidateChosenVersion( + const QuicVersionLabel& version_information_chosen_version, + const ParsedQuicVersion& session_version, std::string* error_details); + + // Validates that there was no downgrade attack involving a version + // negotiation packet. This verifies that if the client was initially + // configured with |client_original_supported_versions| and it had received a + // version negotiation packet with |version_information_other_versions|, then + // it would have selected |session_version|. Returns true if they match (or if + // |version_information_other_versions| is empty indicating no version + // negotiation packet was received), otherwise returns + // false and fills in |error_details|. + static bool ValidateServerVersions( + const QuicVersionLabelVector& version_information_other_versions, + const ParsedQuicVersion& session_version, + const ParsedQuicVersionVector& client_original_supported_versions, + std::string* error_details); + // Returns the name of the HandshakeFailureReason as a char* static const char* HandshakeFailureReasonToString( HandshakeFailureReason reason);
diff --git a/quic/core/crypto/crypto_utils_test.cc b/quic/core/crypto/crypto_utils_test.cc index 6c3d384..251f136 100644 --- a/quic/core/crypto/crypto_utils_test.cc +++ b/quic/core/crypto/crypto_utils_test.cc
@@ -108,6 +108,63 @@ } } +TEST_F(CryptoUtilsTest, ValidateChosenVersion) { + for (const ParsedQuicVersion& v1 : AllSupportedVersions()) { + for (const ParsedQuicVersion& v2 : AllSupportedVersions()) { + std::string error_details; + bool success = CryptoUtils::ValidateChosenVersion( + CreateQuicVersionLabel(v1), v2, &error_details); + EXPECT_EQ(success, v1 == v2); + EXPECT_EQ(success, error_details.empty()); + } + } +} + +TEST_F(CryptoUtilsTest, ValidateServerVersionsNoVersionNegotiation) { + QuicVersionLabelVector version_information_other_versions; + ParsedQuicVersionVector client_original_supported_versions; + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + std::string error_details; + EXPECT_TRUE(CryptoUtils::ValidateServerVersions( + version_information_other_versions, version, + client_original_supported_versions, &error_details)); + EXPECT_TRUE(error_details.empty()); + } +} + +TEST_F(CryptoUtilsTest, ValidateServerVersionsWithVersionNegotiation) { + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + QuicVersionLabelVector version_information_other_versions{ + CreateQuicVersionLabel(version)}; + ParsedQuicVersionVector client_original_supported_versions{ + ParsedQuicVersion::ReservedForNegotiation(), version}; + std::string error_details; + EXPECT_TRUE(CryptoUtils::ValidateServerVersions( + version_information_other_versions, version, + client_original_supported_versions, &error_details)); + EXPECT_TRUE(error_details.empty()); + } +} + +TEST_F(CryptoUtilsTest, ValidateServerVersionsWithDowngrade) { + if (AllSupportedVersions().size() <= 1) { + // We are not vulnerable to downgrade if we only support one version. + return; + } + ParsedQuicVersion client_version = AllSupportedVersions().front(); + ParsedQuicVersion server_version = AllSupportedVersions().back(); + ASSERT_NE(client_version, server_version); + QuicVersionLabelVector version_information_other_versions{ + CreateQuicVersionLabel(client_version)}; + ParsedQuicVersionVector client_original_supported_versions{ + ParsedQuicVersion::ReservedForNegotiation(), server_version}; + std::string error_details; + EXPECT_FALSE(CryptoUtils::ValidateServerVersions( + version_information_other_versions, server_version, + client_original_supported_versions, &error_details)); + EXPECT_FALSE(error_details.empty()); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc index fec6cc5..8449372 100644 --- a/quic/core/crypto/transport_parameters.cc +++ b/quic/core/crypto/transport_parameters.cc
@@ -62,7 +62,8 @@ kGoogleQuicVersion = 0x4752, // Used to transmit version and supported_versions. - kMinAckDelay = 0xDE1A, // draft-iyengar-quic-delayed-ack. + kMinAckDelay = 0xDE1A, // draft-iyengar-quic-delayed-ack. + kVersionInformation = 0xFF73DB, // draft-ietf-quic-version-negotiation. }; namespace { @@ -129,6 +130,8 @@ return "google-version"; case TransportParameters::kMinAckDelay: return "min_ack_delay_us"; + case TransportParameters::kVersionInformation: + return "version_information"; } return absl::StrCat("Unknown(", param_id, ")"); } @@ -158,6 +161,7 @@ case TransportParameters::kGoogleConnectionOptions: case TransportParameters::kGoogleQuicVersion: case TransportParameters::kMinAckDelay: + case TransportParameters::kVersionInformation: return true; case TransportParameters::kGoogleUserAgentId: return !GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter); @@ -309,6 +313,70 @@ "]"; } +TransportParameters::LegacyVersionInformation::LegacyVersionInformation() + : version(0) {} + +bool TransportParameters::LegacyVersionInformation::operator==( + const LegacyVersionInformation& rhs) const { + return version == rhs.version && supported_versions == rhs.supported_versions; +} + +bool TransportParameters::LegacyVersionInformation::operator!=( + const LegacyVersionInformation& rhs) const { + return !(*this == rhs); +} + +std::string TransportParameters::LegacyVersionInformation::ToString() const { + std::string rv = + absl::StrCat("legacy[version ", QuicVersionLabelToString(version)); + if (!supported_versions.empty()) { + absl::StrAppend(&rv, + " supported_versions " + + QuicVersionLabelVectorToString(supported_versions)); + } + absl::StrAppend(&rv, "]"); + return rv; +} + +std::ostream& operator<<(std::ostream& os, + const TransportParameters::LegacyVersionInformation& + legacy_version_information) { + os << legacy_version_information.ToString(); + return os; +} + +TransportParameters::VersionInformation::VersionInformation() + : chosen_version(0) {} + +bool TransportParameters::VersionInformation::operator==( + const VersionInformation& rhs) const { + return chosen_version == rhs.chosen_version && + other_versions == rhs.other_versions; +} + +bool TransportParameters::VersionInformation::operator!=( + const VersionInformation& rhs) const { + return !(*this == rhs); +} + +std::string TransportParameters::VersionInformation::ToString() const { + std::string rv = absl::StrCat("[chosen_version ", + QuicVersionLabelToString(chosen_version)); + if (!other_versions.empty()) { + absl::StrAppend(&rv, " other_versions " + + QuicVersionLabelVectorToString(other_versions)); + } + absl::StrAppend(&rv, "]"); + return rv; +} + +std::ostream& operator<<( + std::ostream& os, + const TransportParameters::VersionInformation& version_information) { + os << version_information.ToString(); + return os; +} + std::ostream& operator<<(std::ostream& os, const TransportParameters& params) { os << params.ToString(); return os; @@ -321,12 +389,11 @@ } else { rv += "Client"; } - if (version != 0) { - rv += " version " + QuicVersionLabelToString(version); + if (legacy_version_information.has_value()) { + rv += " " + legacy_version_information.value().ToString(); } - if (!supported_versions.empty()) { - rv += " supported_versions " + - QuicVersionLabelVectorToString(supported_versions); + if (version_information.has_value()) { + rv += " " + version_information.value().ToString(); } if (original_destination_connection_id.has_value()) { rv += " " + TransportParameterIdToString(kOriginalDestinationConnectionId) + @@ -403,12 +470,9 @@ } TransportParameters::TransportParameters() - : version(0), - max_idle_timeout_ms(kMaxIdleTimeout), - max_udp_payload_size(kMaxPacketSize, - kDefaultMaxPacketSizeTransportParam, - kMinMaxPacketSizeTransportParam, - kVarInt62MaxValue), + : max_idle_timeout_ms(kMaxIdleTimeout), + max_udp_payload_size(kMaxPacketSize, kDefaultMaxPacketSizeTransportParam, + kMinMaxPacketSizeTransportParam, kVarInt62MaxValue), initial_max_data(kInitialMaxData), initial_max_stream_data_bidi_local(kInitialMaxStreamDataBidiLocal), initial_max_stream_data_bidi_remote(kInitialMaxStreamDataBidiRemote), @@ -416,16 +480,11 @@ initial_max_streams_bidi(kInitialMaxStreamsBidi), initial_max_streams_uni(kInitialMaxStreamsUni), ack_delay_exponent(kAckDelayExponent, - kDefaultAckDelayExponentTransportParam, - 0, + kDefaultAckDelayExponentTransportParam, 0, kMaxAckDelayExponentTransportParam), - max_ack_delay(kMaxAckDelay, - kDefaultMaxAckDelayTransportParam, - 0, + max_ack_delay(kMaxAckDelay, kDefaultMaxAckDelayTransportParam, 0, kMaxMaxAckDelayTransportParam), - min_ack_delay_us(kMinAckDelay, - 0, - 0, + min_ack_delay_us(kMinAckDelay, 0, 0, kMaxMaxAckDelayTransportParam * kNumMicrosPerMilli), disable_active_migration(false), active_connection_id_limit(kActiveConnectionIdLimit, @@ -443,8 +502,8 @@ TransportParameters::TransportParameters(const TransportParameters& other) : perspective(other.perspective), - version(other.version), - supported_versions(other.supported_versions), + legacy_version_information(other.legacy_version_information), + version_information(other.version_information), original_destination_connection_id( other.original_destination_connection_id), max_idle_timeout_ms(other.max_idle_timeout_ms), @@ -478,8 +537,9 @@ } bool TransportParameters::operator==(const TransportParameters& rhs) const { - if (!(perspective == rhs.perspective && version == rhs.version && - supported_versions == rhs.supported_versions && + if (!(perspective == rhs.perspective && + legacy_version_information == rhs.legacy_version_information && + version_information == rhs.version_information && original_destination_connection_id == rhs.original_destination_connection_id && max_idle_timeout_ms.value() == rhs.max_idle_timeout_ms.value() && @@ -589,6 +649,22 @@ *error_details = "Server cannot send user agent ID"; return false; } + if (version_information.has_value()) { + const QuicVersionLabel& chosen_version = + version_information.value().chosen_version; + const QuicVersionLabelVector& other_versions = + version_information.value().other_versions; + if (chosen_version == 0) { + *error_details = "Invalid chosen version"; + return false; + } + if (perspective == Perspective::IS_CLIENT && + std::find(other_versions.begin(), other_versions.end(), + chosen_version) == other_versions.end()) { + *error_details = "Client chosen version not in other versions"; + return false; + } + } const bool ok = max_idle_timeout_ms.IsValid() && max_udp_payload_size.IsValid() && initial_max_data.IsValid() && @@ -616,8 +692,10 @@ << "Not serializing invalid transport parameters: " << error_details; return false; } - if (in.version == 0 || (in.perspective == Perspective::IS_SERVER && - in.supported_versions.empty())) { + if (!in.legacy_version_information.has_value() || + in.legacy_version_information.value().version == 0 || + (in.perspective == Perspective::IS_SERVER && + in.legacy_version_information.value().supported_versions.empty())) { QUIC_BUG(missing versions) << "Refusing to serialize without versions"; return false; } @@ -701,6 +779,7 @@ TransportParameters::kGoogleUserAgentId, TransportParameters::kGoogleKeyUpdateNotYetSupported, TransportParameters::kGoogleQuicVersion, + TransportParameters::kVersionInformation, }; size_t max_transport_param_length = kKnownTransportParamLength; @@ -714,9 +793,21 @@ max_transport_param_length += in.user_agent_id.value().length(); } // Google-specific version extension. - max_transport_param_length += - sizeof(in.version) + 1 /* versions length */ + - in.supported_versions.size() * sizeof(QuicVersionLabel); + if (in.legacy_version_information.has_value()) { + max_transport_param_length += + sizeof(in.legacy_version_information.value().version) + + 1 /* versions length */ + + in.legacy_version_information.value().supported_versions.size() * + sizeof(QuicVersionLabel); + } + // version_information. + if (in.version_information.has_value()) { + max_transport_param_length += + sizeof(in.version_information.value().chosen_version) + + // Add one for the added GREASE version. + (in.version_information.value().other_versions.size() + 1) * + sizeof(QuicVersionLabel); + } // Add a random GREASE transport parameter, as defined in the // "Reserved Transport Parameters" section of RFC 9000. @@ -1051,30 +1142,38 @@ } break; // Google-specific version extension. case TransportParameters::kGoogleQuicVersion: { + if (!in.legacy_version_information.has_value()) { + break; + } static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t), "bad length"); - uint64_t google_version_length = sizeof(in.version); + uint64_t google_version_length = + sizeof(in.legacy_version_information.value().version); if (in.perspective == Perspective::IS_SERVER) { google_version_length += /* versions length */ sizeof(uint8_t) + - sizeof(QuicVersionLabel) * in.supported_versions.size(); + sizeof(QuicVersionLabel) * in.legacy_version_information.value() + .supported_versions.size(); } if (!writer.WriteVarInt62(TransportParameters::kGoogleQuicVersion) || !writer.WriteVarInt62( /* transport parameter length */ google_version_length) || - !writer.WriteUInt32(in.version)) { + !writer.WriteUInt32( + in.legacy_version_information.value().version)) { QUIC_BUG(Failed to write Google version extension) << "Failed to write Google version extension for " << in; return false; } if (in.perspective == Perspective::IS_SERVER) { if (!writer.WriteUInt8(sizeof(QuicVersionLabel) * - in.supported_versions.size())) { + in.legacy_version_information.value() + .supported_versions.size())) { QUIC_BUG(Failed to write versions length) << "Failed to write versions length for " << in; return false; } - for (QuicVersionLabel version_label : in.supported_versions) { + for (QuicVersionLabel version_label : + in.legacy_version_information.value().supported_versions) { if (!writer.WriteUInt32(version_label)) { QUIC_BUG(Failed to write supported version) << "Failed to write supported version for " << in; @@ -1083,6 +1182,41 @@ } } } break; + // version_information. + case TransportParameters::kVersionInformation: { + if (!in.version_information.has_value()) { + break; + } + static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t), + "bad length"); + QuicVersionLabelVector other_versions = + in.version_information.value().other_versions; + // Insert one GREASE version at a random index. + const size_t grease_index = + random->InsecureRandUint64() % (other_versions.size() + 1); + other_versions.insert( + other_versions.begin() + grease_index, + CreateQuicVersionLabel(QuicVersionReservedForNegotiation())); + const uint64_t version_information_length = + sizeof(in.version_information.value().chosen_version) + + sizeof(QuicVersionLabel) * other_versions.size(); + if (!writer.WriteVarInt62(TransportParameters::kVersionInformation) || + !writer.WriteVarInt62( + /* transport parameter length */ version_information_length) || + !writer.WriteUInt32( + in.version_information.value().chosen_version)) { + QUIC_BUG(Failed to write chosen version) + << "Failed to write chosen version for " << in; + return false; + } + for (QuicVersionLabel version_label : other_versions) { + if (!writer.WriteUInt32(version_label)) { + QUIC_BUG(Failed to write other version) + << "Failed to write other version for " << in; + return false; + } + } + } break; // Custom parameters and GREASE. default: { auto it = custom_parameters.find(parameter_id); @@ -1377,7 +1511,12 @@ out->key_update_not_yet_supported = true; break; case TransportParameters::kGoogleQuicVersion: { - if (!value_reader.ReadUInt32(&out->version)) { + if (!out->legacy_version_information.has_value()) { + out->legacy_version_information = + TransportParameters::LegacyVersionInformation(); + } + if (!value_reader.ReadUInt32( + &out->legacy_version_information.value().version)) { *error_details = "Failed to read Google version extension version"; return false; } @@ -1394,10 +1533,32 @@ *error_details = "Failed to parse Google supported version"; return false; } - out->supported_versions.push_back(version); + out->legacy_version_information.value() + .supported_versions.push_back(version); } } } break; + case TransportParameters::kVersionInformation: { + if (out->version_information.has_value()) { + *error_details = "Received a second version_information"; + return false; + } + out->version_information = TransportParameters::VersionInformation(); + if (!value_reader.ReadUInt32( + &out->version_information.value().chosen_version)) { + *error_details = "Failed to read chosen version"; + return false; + } + while (!value_reader.IsDoneReading()) { + QuicVersionLabel other_version; + if (!value_reader.ReadUInt32(&other_version)) { + *error_details = "Failed to parse other version"; + return false; + } + out->version_information.value().other_versions.push_back( + other_version); + } + } break; case TransportParameters::kMinAckDelay: parse_success = out->min_ack_delay_us.Read(&value_reader, error_details);
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h index b6ecb69..fa34935 100644 --- a/quic/core/crypto/transport_parameters.h +++ b/quic/core/crypto/transport_parameters.h
@@ -110,6 +110,64 @@ const TransportParameters& params); }; + // LegacyVersionInformation represents the Google QUIC downgrade prevention + // mechanism ported to QUIC+TLS. It is exchanged using transport parameter ID + // 0x4752 and will eventually be deprecated in favor of + // draft-ietf-quic-version-negotiation. + struct QUIC_EXPORT_PRIVATE LegacyVersionInformation { + LegacyVersionInformation(); + LegacyVersionInformation(const LegacyVersionInformation& other) = default; + LegacyVersionInformation& operator=(const LegacyVersionInformation& other) = + default; + LegacyVersionInformation& operator=(LegacyVersionInformation&& other) = + default; + LegacyVersionInformation(LegacyVersionInformation&& other) = default; + ~LegacyVersionInformation() = default; + bool operator==(const LegacyVersionInformation& rhs) const; + bool operator!=(const LegacyVersionInformation& rhs) const; + // When sent by the client, |version| is the initial version offered by the + // client (before any version negotiation packets) for this connection. When + // sent by the server, |version| is the version that is in use. + QuicVersionLabel version; + + // When sent by the server, |supported_versions| contains a list of all + // versions that the server would send in a version negotiation packet. When + // sent by the client, this is empty. + QuicVersionLabelVector supported_versions; + + // Allows easily logging. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, + const LegacyVersionInformation& legacy_version_information); + }; + + // Version information used for version downgrade prevention and compatible + // version negotiation. See draft-ietf-quic-version-negotiation-05. + struct QUIC_EXPORT_PRIVATE VersionInformation { + VersionInformation(); + VersionInformation(const VersionInformation& other) = default; + VersionInformation& operator=(const VersionInformation& other) = default; + VersionInformation& operator=(VersionInformation&& other) = default; + VersionInformation(VersionInformation&& other) = default; + ~VersionInformation() = default; + bool operator==(const VersionInformation& rhs) const; + bool operator!=(const VersionInformation& rhs) const; + + // Version that the sender has chosen to use on this connection. + QuicVersionLabel chosen_version; + + // When sent by the client, |other_versions| contains all the versions that + // this first flight is compatible with. When sent by the server, + // |other_versions| contains all of the versions supported by the server. + QuicVersionLabelVector other_versions; + + // Allows easily logging. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, const VersionInformation& version_information); + }; + TransportParameters(); TransportParameters(const TransportParameters& other); ~TransportParameters(); @@ -122,15 +180,12 @@ // the encrypted_extensions handshake message. Perspective perspective; - // When Perspective::IS_CLIENT, |version| is the initial version offered by - // the client (before any version negotiation packets) for this connection. - // When Perspective::IS_SERVER, |version| is the version that is in use. - QuicVersionLabel version; + // Google QUIC downgrade prevention mechanism sent over QUIC+TLS. + absl::optional<LegacyVersionInformation> legacy_version_information; - // |supported_versions| contains a list of all versions that the server would - // send in a version negotiation packet. It is not used if |perspective == - // Perspective::IS_CLIENT|. - QuicVersionLabelVector supported_versions; + // IETF downgrade prevention and compatible version negotiation, see + // draft-ietf-quic-version-negotiation. + absl::optional<VersionInformation> version_information; // The value of the Destination Connection ID field from the first // Initial packet sent by the client.
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc index 05ada68..c4070dd 100644 --- a/quic/core/crypto/transport_parameters_test.cc +++ b/quic/core/crypto/transport_parameters_test.cc
@@ -99,6 +99,30 @@ preferred_address); } +TransportParameters::LegacyVersionInformation +CreateFakeLegacyVersionInformationClient() { + TransportParameters::LegacyVersionInformation legacy_version_information; + legacy_version_information.version = kFakeVersionLabel; + return legacy_version_information; +} + +TransportParameters::LegacyVersionInformation +CreateFakeLegacyVersionInformationServer() { + TransportParameters::LegacyVersionInformation legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + legacy_version_information.supported_versions.push_back(kFakeVersionLabel); + legacy_version_information.supported_versions.push_back(kFakeVersionLabel2); + return legacy_version_information; +} + +TransportParameters::VersionInformation CreateFakeVersionInformation() { + TransportParameters::VersionInformation version_information; + version_information.chosen_version = kFakeVersionLabel; + version_information.other_versions.push_back(kFakeVersionLabel); + version_information.other_versions.push_back(kFakeVersionLabel2); + return version_information; +} + QuicTagVector CreateFakeGoogleConnectionOptions() { return {kALPN, MakeQuicTag('E', 'F', 'G', 0x00), MakeQuicTag('H', 'I', 'J', 0xff)}; @@ -119,6 +143,18 @@ for (TransportParameters::TransportParameterId param_id : grease_params) { params->custom_parameters.erase(param_id); } + // Remove all GREASE versions from version_information.other_versions. + if (params->version_information.has_value()) { + QuicVersionLabelVector& other_versions = + params->version_information.value().other_versions; + for (auto it = other_versions.begin(); it != other_versions.end();) { + if ((*it & 0x0f0f0f0f) == 0x0a0a0a0a) { + it = other_versions.erase(it); + } else { + ++it; + } + } + } } } // namespace @@ -145,8 +181,12 @@ EXPECT_FALSE(orig_params == new_params); EXPECT_TRUE(orig_params != new_params); new_params.perspective = Perspective::IS_CLIENT; - orig_params.version = kFakeVersionLabel; - new_params.version = kFakeVersionLabel; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + new_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.version_information = CreateFakeVersionInformation(); + new_params.version_information = CreateFakeVersionInformation(); orig_params.disable_active_migration = true; new_params.disable_active_migration = true; EXPECT_EQ(orig_params, new_params); @@ -154,13 +194,16 @@ EXPECT_FALSE(orig_params != new_params); // Test comparison on vectors. - orig_params.supported_versions.push_back(kFakeVersionLabel); - new_params.supported_versions.push_back(kFakeVersionLabel2); + orig_params.legacy_version_information.value().supported_versions.push_back( + kFakeVersionLabel); + new_params.legacy_version_information.value().supported_versions.push_back( + kFakeVersionLabel2); EXPECT_NE(orig_params, new_params); EXPECT_FALSE(orig_params == new_params); EXPECT_TRUE(orig_params != new_params); - new_params.supported_versions.pop_back(); - new_params.supported_versions.push_back(kFakeVersionLabel); + new_params.legacy_version_information.value().supported_versions.pop_back(); + new_params.legacy_version_information.value().supported_versions.push_back( + kFakeVersionLabel); orig_params.stateless_reset_token = CreateStatelessResetTokenForTest(); new_params.stateless_reset_token = CreateStatelessResetTokenForTest(); EXPECT_EQ(orig_params, new_params); @@ -219,9 +262,9 @@ TEST_P(TransportParametersTest, CopyConstructor) { TransportParameters orig_params; orig_params.perspective = Perspective::IS_CLIENT; - orig_params.version = kFakeVersionLabel; - orig_params.supported_versions.push_back(kFakeVersionLabel); - orig_params.supported_versions.push_back(kFakeVersionLabel2); + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.version_information = CreateFakeVersionInformation(); orig_params.original_destination_connection_id = CreateFakeOriginalDestinationConnectionId(); orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); @@ -260,7 +303,9 @@ TEST_P(TransportParametersTest, RoundTripClient) { TransportParameters orig_params; orig_params.perspective = Perspective::IS_CLIENT; - orig_params.version = kFakeVersionLabel; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); + orig_params.version_information = CreateFakeVersionInformation(); orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); orig_params.initial_max_data.set_value(kFakeInitialMaxData); @@ -308,9 +353,9 @@ TEST_P(TransportParametersTest, RoundTripServer) { TransportParameters orig_params; orig_params.perspective = Perspective::IS_SERVER; - orig_params.version = kFakeVersionLabel; - orig_params.supported_versions.push_back(kFakeVersionLabel); - orig_params.supported_versions.push_back(kFakeVersionLabel2); + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationServer(); + orig_params.version_information = CreateFakeVersionInformation(); orig_params.original_destination_connection_id = CreateFakeOriginalDestinationConnectionId(); orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); @@ -446,7 +491,8 @@ TEST_P(TransportParametersTest, NoClientParamsWithStatelessResetToken) { TransportParameters orig_params; orig_params.perspective = Perspective::IS_CLIENT; - orig_params.version = kFakeVersionLabel; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); orig_params.stateless_reset_token = CreateStatelessResetTokenForTest(); orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); @@ -539,6 +585,12 @@ 0x80, 0x00, 0x47, 0x52, // parameter id 0x04, // length 0x01, 0x23, 0x45, 0x67, // initial version + // version_information + 0x80, 0xFF, 0x73, 0xDB, // parameter id + 0x0C, // length + 0x01, 0x23, 0x45, 0x67, // chosen version + 0x01, 0x23, 0x45, 0x67, // other version 1 + 0x89, 0xab, 0xcd, 0xef, // other version 2 }; // clang-format on const uint8_t* client_params = @@ -552,8 +604,14 @@ << error_details; EXPECT_TRUE(error_details.empty()); EXPECT_EQ(Perspective::IS_CLIENT, new_params.perspective); - EXPECT_EQ(kFakeVersionLabel, new_params.version); - EXPECT_TRUE(new_params.supported_versions.empty()); + ASSERT_TRUE(new_params.legacy_version_information.has_value()); + EXPECT_EQ(kFakeVersionLabel, + new_params.legacy_version_information.value().version); + EXPECT_TRUE( + new_params.legacy_version_information.value().supported_versions.empty()); + ASSERT_TRUE(new_params.version_information.has_value()); + EXPECT_EQ(new_params.version_information.value(), + CreateFakeVersionInformation()); EXPECT_FALSE(new_params.original_destination_connection_id.has_value()); EXPECT_EQ(kFakeIdleTimeoutMilliseconds, new_params.max_idle_timeout_ms.value()); @@ -790,6 +848,12 @@ 0x08, // length of supported versions array 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + // version_information + 0x80, 0xFF, 0x73, 0xDB, // parameter id + 0x0C, // length + 0x01, 0x23, 0x45, 0x67, // chosen version + 0x01, 0x23, 0x45, 0x67, // other version 1 + 0x89, 0xab, 0xcd, 0xef, // other version 2 }; // clang-format on const uint8_t* server_params = @@ -803,10 +867,21 @@ << error_details; EXPECT_TRUE(error_details.empty()); EXPECT_EQ(Perspective::IS_SERVER, new_params.perspective); - EXPECT_EQ(kFakeVersionLabel, new_params.version); - EXPECT_EQ(2u, new_params.supported_versions.size()); - EXPECT_EQ(kFakeVersionLabel, new_params.supported_versions[0]); - EXPECT_EQ(kFakeVersionLabel2, new_params.supported_versions[1]); + ASSERT_TRUE(new_params.legacy_version_information.has_value()); + EXPECT_EQ(kFakeVersionLabel, + new_params.legacy_version_information.value().version); + ASSERT_EQ( + 2u, + new_params.legacy_version_information.value().supported_versions.size()); + EXPECT_EQ( + kFakeVersionLabel, + new_params.legacy_version_information.value().supported_versions[0]); + EXPECT_EQ( + kFakeVersionLabel2, + new_params.legacy_version_information.value().supported_versions[1]); + ASSERT_TRUE(new_params.version_information.has_value()); + EXPECT_EQ(new_params.version_information.value(), + CreateFakeVersionInformation()); ASSERT_TRUE(new_params.original_destination_connection_id.has_value()); EXPECT_EQ(CreateFakeOriginalDestinationConnectionId(), new_params.original_destination_connection_id.value()); @@ -927,7 +1002,8 @@ std::string custom_value(70000, '?'); TransportParameters orig_params; orig_params.perspective = Perspective::IS_CLIENT; - orig_params.version = kFakeVersionLabel; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); orig_params.custom_parameters[kCustomParameter1] = custom_value; std::vector<uint8_t> serialized; @@ -947,7 +1023,8 @@ TEST_P(TransportParametersTest, SerializationOrderIsRandom) { TransportParameters orig_params; orig_params.perspective = Perspective::IS_CLIENT; - orig_params.version = kFakeVersionLabel; + orig_params.legacy_version_information = + CreateFakeLegacyVersionInformationClient(); orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds); orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest); orig_params.initial_max_data.set_value(kFakeInitialMaxData); @@ -994,9 +1071,8 @@ protected: void SetUp() override { original_params_.perspective = Perspective::IS_SERVER; - original_params_.version = kFakeVersionLabel; - original_params_.supported_versions.push_back(kFakeVersionLabel); - original_params_.supported_versions.push_back(kFakeVersionLabel2); + original_params_.legacy_version_information = + CreateFakeLegacyVersionInformationServer(); original_params_.original_destination_connection_id = CreateFakeOriginalDestinationConnectionId(); original_params_.max_idle_timeout_ms.set_value(
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index d625fd0..0b5e591 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -133,6 +133,8 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_key_update_not_yet_supported, true) // When true, QUIC server will ignore received user agent transport parameter and rely on getting that information from HTTP headers. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_user_agent_transport_parameter, true) +// When true, QUIC will both send and validate the version_information transport parameter. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_version_information, false) // When true, QuicDispatcher will silently drop incoming packets whose UDP source port is on the blocklist. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_blocked_ports, true) // When true, defaults to BBR congestion control instead of Cubic.
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index cb06d87..db898d2 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -630,6 +630,16 @@ // Try converting all pending streams to normal streams. void ProcessAllPendingStreams(); + const ParsedQuicVersionVector& client_original_supported_versions() const { + QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT); + return client_original_supported_versions_; + } + void set_client_original_supported_versions( + const ParsedQuicVersionVector& client_original_supported_versions) { + QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT); + client_original_supported_versions_ = client_original_supported_versions; + } + protected: using StreamMap = absl::flat_hash_map<QuicStreamId, std::unique_ptr<QuicStream>>; @@ -983,6 +993,11 @@ // list may be a superset of the connection framer's supported versions. ParsedQuicVersionVector supported_versions_; + // Only non-empty on the client after receiving a version negotiation packet, + // contains the configured versions from the original session before version + // negotiation was received. + ParsedQuicVersionVector client_original_supported_versions_; + absl::optional<std::string> user_agent_id_; // Initialized to false. Set to true when the session has been properly
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc index 717932f..ca2713f 100644 --- a/quic/core/quic_versions.cc +++ b/quic/core/quic_versions.cc
@@ -611,6 +611,7 @@ void QuicVersionInitializeSupportForIetfDraft() { // Enable necessary flags. + SetQuicReloadableFlag(quic_version_information, true); } void QuicEnableVersion(const ParsedQuicVersion& version) {
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc index 2717db0..7f8fca6 100644 --- a/quic/core/tls_client_handshaker.cc +++ b/quic/core/tls_client_handshaker.cc
@@ -221,8 +221,14 @@ bool TlsClientHandshaker::SetTransportParameters() { TransportParameters params; params.perspective = Perspective::IS_CLIENT; - params.version = + params.legacy_version_information = + TransportParameters::LegacyVersionInformation(); + params.legacy_version_information.value().version = CreateQuicVersionLabel(session()->supported_versions().front()); + params.version_information = TransportParameters::VersionInformation(); + const QuicVersionLabel version = CreateQuicVersionLabel(session()->version()); + params.version_information.value().chosen_version = version; + params.version_information.value().other_versions.push_back(version); if (!handshaker_delegate()->FillTransportParameters(¶ms)) { return false; @@ -266,27 +272,41 @@ session()->connection()->OnTransportParametersReceived( *received_transport_params_); - // When interoperating with non-Google implementations that do not send - // the version extension, set it to what we expect. - if (received_transport_params_->version == 0) { - received_transport_params_->version = - CreateQuicVersionLabel(session()->connection()->version()); + if (received_transport_params_->legacy_version_information.has_value()) { + if (received_transport_params_->legacy_version_information.value() + .version != + CreateQuicVersionLabel(session()->connection()->version())) { + *error_details = "Version mismatch detected"; + return false; + } + if (CryptoUtils::ValidateServerHelloVersions( + received_transport_params_->legacy_version_information.value() + .supported_versions, + session()->connection()->server_supported_versions(), + error_details) != QUIC_NO_ERROR) { + QUICHE_DCHECK(!error_details->empty()); + return false; + } } - if (received_transport_params_->supported_versions.empty()) { - received_transport_params_->supported_versions.push_back( - received_transport_params_->version); + if (received_transport_params_->version_information.has_value()) { + if (!CryptoUtils::ValidateChosenVersion( + received_transport_params_->version_information.value() + .chosen_version, + session()->version(), error_details)) { + QUICHE_DCHECK(!error_details->empty()); + return false; + } + if (!CryptoUtils::CryptoUtils::ValidateServerVersions( + received_transport_params_->version_information.value() + .other_versions, + session()->version(), + session()->client_original_supported_versions(), error_details)) { + QUICHE_DCHECK(!error_details->empty()); + return false; + } } - if (received_transport_params_->version != - CreateQuicVersionLabel(session()->connection()->version())) { - *error_details = "Version mismatch detected"; - return false; - } - if (CryptoUtils::ValidateServerHelloVersions( - received_transport_params_->supported_versions, - session()->connection()->server_supported_versions(), - error_details) != QUIC_NO_ERROR || - handshaker_delegate()->ProcessTransportParameters( + if (handshaker_delegate()->ProcessTransportParameters( *received_transport_params_, /* is_resumption = */ false, error_details) != QUIC_NO_ERROR) { QUICHE_DCHECK(!error_details->empty());
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc index 49bd5a0..c6b3d7d 100644 --- a/quic/core/tls_server_handshaker.cc +++ b/quic/core/tls_server_handshaker.cc
@@ -500,21 +500,31 @@ } } - // When interoperating with non-Google implementations that do not send - // the version extension, set it to what we expect. - if (client_params.version == 0) { - client_params.version = - CreateQuicVersionLabel(session()->connection()->version()); - } - - if (CryptoUtils::ValidateClientHelloVersion( - client_params.version, session()->connection()->version(), - session()->supported_versions(), error_details) != QUIC_NO_ERROR || - handshaker_delegate()->ProcessTransportParameters( - client_params, /* is_resumption = */ false, error_details) != - QUIC_NO_ERROR) { + if (client_params.legacy_version_information.has_value() && + CryptoUtils::ValidateClientHelloVersion( + client_params.legacy_version_information.value().version, + session()->connection()->version(), session()->supported_versions(), + error_details) != QUIC_NO_ERROR) { return false; } + + if (GetQuicReloadableFlag(quic_version_information) && + client_params.version_information.has_value()) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_version_information, 2, 2); + if (!CryptoUtils::ValidateChosenVersion( + client_params.version_information.value().chosen_version, + session()->version(), error_details)) { + QUICHE_DCHECK(!error_details->empty()); + return false; + } + } + + if (handshaker_delegate()->ProcessTransportParameters( + client_params, /* is_resumption = */ false, error_details) != + QUIC_NO_ERROR) { + return false; + } + ProcessAdditionalTransportParameters(client_params); if (!session()->user_agent_id().has_value() && client_params.user_agent_id.has_value()) { @@ -531,10 +541,21 @@ TransportParameters server_params; server_params.perspective = Perspective::IS_SERVER; - server_params.supported_versions = + server_params.legacy_version_information = + TransportParameters::LegacyVersionInformation(); + server_params.legacy_version_information.value().supported_versions = CreateQuicVersionLabelVector(session()->supported_versions()); - server_params.version = + server_params.legacy_version_information.value().version = CreateQuicVersionLabel(session()->connection()->version()); + if (GetQuicReloadableFlag(quic_version_information)) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_version_information, 1, 2); + server_params.version_information = + TransportParameters::VersionInformation(); + server_params.version_information.value().chosen_version = + CreateQuicVersionLabel(session()->version()); + server_params.version_information.value().other_versions = + CreateQuicVersionLabelVector(session()->supported_versions()); + } if (!handshaker_delegate()->FillTransportParameters(&server_params)) { return result;
diff --git a/quic/tools/quic_client_base.cc b/quic/tools/quic_client_base.cc index 6d10de2..e11e25b 100644 --- a/quic/tools/quic_client_base.cc +++ b/quic/tools/quic_client_base.cc
@@ -185,6 +185,9 @@ server_address(), helper(), alarm_factory(), writer, /* owns_writer= */ false, Perspective::IS_CLIENT, client_supported_versions)); + if (can_reconnect_with_different_version) { + session()->set_client_original_supported_versions(supported_versions()); + } if (connection_debug_visitor_ != nullptr) { session()->connection()->set_debug_visitor(connection_debug_visitor_); }