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_);
}