Add SerializeTransportParametersForTicket
This function implements a serialization format of TransportParameters to
pass into SSL_set_quic_early_data_context. It is designed so that compatible
(for 0-RTT resumption) sets of TransportParameters encode to the same value,
and incompatible TransportParameters encode to different values.
New quic transport parameter code, currently unused, not flag protected
PiperOrigin-RevId: 315326616
Change-Id: Id3c3309c481d6192f691a08eb6fd119fc90d5d01
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc
index 12c0d0d..f6d03b5 100644
--- a/quic/core/crypto/transport_parameters.cc
+++ b/quic/core/crypto/transport_parameters.cc
@@ -10,6 +10,8 @@
#include <memory>
#include <utility>
+#include "third_party/boringssl/src/include/openssl/digest.h"
+#include "third_party/boringssl/src/include/openssl/sha.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_framer.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
@@ -1392,4 +1394,87 @@
return true;
}
+namespace {
+
+bool DigestUpdateIntegerParam(
+ EVP_MD_CTX* hash_ctx,
+ const TransportParameters::IntegerParameter& param) {
+ uint64_t value = param.value();
+ return EVP_DigestUpdate(hash_ctx, &value, sizeof(value));
+}
+
+} // namespace
+
+bool SerializeTransportParametersForTicket(
+ const TransportParameters& in,
+ const std::vector<uint8_t>& application_data,
+ std::vector<uint8_t>* out) {
+ std::string error_details;
+ if (!in.AreValid(&error_details)) {
+ QUIC_BUG << "Not serializing invalid transport parameters: "
+ << error_details;
+ return false;
+ }
+
+ out->resize(SHA256_DIGEST_LENGTH + 1);
+ const uint8_t serialization_version = 0;
+ (*out)[0] = serialization_version;
+
+ bssl::ScopedEVP_MD_CTX hash_ctx;
+ // Write application data:
+ uint64_t app_data_len = application_data.size();
+ const uint64_t parameter_version = 0;
+ // The format of the input to the hash function is as follows:
+ // - The application data, prefixed with a 64-bit length field.
+ // - Transport parameters:
+ // - A 64-bit version field indicating which version of encoding is used
+ // for transport parameters.
+ // - A list of 64-bit integers representing the relevant parameters.
+ //
+ // When changing which parameters are included, additional parameters can be
+ // added to the end of the list without changing the version field. New
+ // parameters that are variable length must be length prefixed. If
+ // parameters are removed from the list, the version field must be
+ // incremented.
+ //
+ // Integers happen to be written in host byte order, not network byte order.
+ if (!EVP_DigestInit(hash_ctx.get(), EVP_sha256()) ||
+ !EVP_DigestUpdate(hash_ctx.get(), &app_data_len, sizeof(app_data_len)) ||
+ !EVP_DigestUpdate(hash_ctx.get(), application_data.data(),
+ application_data.size()) ||
+ !EVP_DigestUpdate(hash_ctx.get(), ¶meter_version,
+ sizeof(parameter_version))) {
+ QUIC_BUG << "Unexpected failure of EVP_Digest functions when hashing "
+ "Transport Parameters for ticket";
+ return false;
+ }
+
+ // Write transport parameters specified by draft-ietf-quic-transport-28,
+ // section 7.4.1, that are remembered for 0-RTT.
+ if (!DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_data) ||
+ !DigestUpdateIntegerParam(hash_ctx.get(),
+ in.initial_max_stream_data_bidi_local) ||
+ !DigestUpdateIntegerParam(hash_ctx.get(),
+ in.initial_max_stream_data_bidi_remote) ||
+ !DigestUpdateIntegerParam(hash_ctx.get(),
+ in.initial_max_stream_data_uni) ||
+ !DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_streams_bidi) ||
+ !DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_streams_uni) ||
+ !DigestUpdateIntegerParam(hash_ctx.get(),
+ in.active_connection_id_limit)) {
+ QUIC_BUG << "Unexpected failure of EVP_Digest functions when hashing "
+ "Transport Parameters for ticket";
+ return false;
+ }
+ uint8_t disable_active_migration = in.disable_active_migration ? 1 : 0;
+ if (!EVP_DigestUpdate(hash_ctx.get(), &disable_active_migration,
+ sizeof(disable_active_migration)) ||
+ !EVP_DigestFinal(hash_ctx.get(), out->data() + 1, nullptr)) {
+ QUIC_BUG << "Unexpected failure of EVP_Digest functions when hashing "
+ "Transport Parameters for ticket";
+ return false;
+ }
+ return true;
+}
+
} // namespace quic
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h
index 80c905c..8baeb21 100644
--- a/quic/core/crypto/transport_parameters.h
+++ b/quic/core/crypto/transport_parameters.h
@@ -243,6 +243,19 @@
TransportParameters* out,
std::string* error_details);
+// Serializes |in| and |application_data| in a deterministic format so that
+// multiple calls to SerializeTransportParametersForTicket with the same inputs
+// will generate the same output, and if the inputs differ, then the output will
+// differ. The output of this function is used by the server in
+// SSL_set_quic_early_data_context to determine whether early data should be
+// accepted: Early data will only be accepted if the inputs to this function
+// match what they were on the connection that issued an early data capable
+// ticket.
+QUIC_EXPORT_PRIVATE bool SerializeTransportParametersForTicket(
+ const TransportParameters& in,
+ const std::vector<uint8_t>& application_data,
+ std::vector<uint8_t>* out);
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_CRYPTO_TRANSPORT_PARAMETERS_H_
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc
index e9c55d4..8267c8b 100644
--- a/quic/core/crypto/transport_parameters_test.cc
+++ b/quic/core/crypto/transport_parameters_test.cc
@@ -1266,5 +1266,103 @@
EXPECT_EQ(new_params, orig_params);
}
+class TransportParametersTicketSerializationTest : public QuicTest {
+ 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_.original_destination_connection_id =
+ CreateFakeOriginalDestinationConnectionId();
+ original_params_.max_idle_timeout_ms.set_value(
+ kFakeIdleTimeoutMilliseconds);
+ original_params_.stateless_reset_token = CreateFakeStatelessResetToken();
+ original_params_.max_udp_payload_size.set_value(kFakeMaxPacketSize);
+ original_params_.initial_max_data.set_value(kFakeInitialMaxData);
+ original_params_.initial_max_stream_data_bidi_local.set_value(
+ kFakeInitialMaxStreamDataBidiLocal);
+ original_params_.initial_max_stream_data_bidi_remote.set_value(
+ kFakeInitialMaxStreamDataBidiRemote);
+ original_params_.initial_max_stream_data_uni.set_value(
+ kFakeInitialMaxStreamDataUni);
+ original_params_.initial_max_streams_bidi.set_value(
+ kFakeInitialMaxStreamsBidi);
+ original_params_.initial_max_streams_uni.set_value(
+ kFakeInitialMaxStreamsUni);
+ original_params_.ack_delay_exponent.set_value(kFakeAckDelayExponent);
+ original_params_.max_ack_delay.set_value(kFakeMaxAckDelay);
+ original_params_.disable_active_migration = kFakeDisableMigration;
+ original_params_.preferred_address = CreateFakePreferredAddress();
+ original_params_.active_connection_id_limit.set_value(
+ kFakeActiveConnectionIdLimit);
+ original_params_.initial_source_connection_id =
+ CreateFakeInitialSourceConnectionId();
+ original_params_.retry_source_connection_id =
+ CreateFakeRetrySourceConnectionId();
+ original_params_.google_connection_options =
+ CreateFakeGoogleConnectionOptions();
+
+ ASSERT_TRUE(SerializeTransportParametersForTicket(
+ original_params_, application_state_, &original_serialized_params_));
+ }
+
+ TransportParameters original_params_;
+ std::vector<uint8_t> application_state_ = {0, 1};
+ std::vector<uint8_t> original_serialized_params_;
+};
+
+TEST_F(TransportParametersTicketSerializationTest,
+ StatelessResetTokenDoesntChangeOutput) {
+ // Test that changing the stateless reset token doesn't change the ticket
+ // serialization.
+ TransportParameters new_params = original_params_;
+ new_params.stateless_reset_token = CreateFakePreferredStatelessResetToken();
+ EXPECT_NE(new_params, original_params_);
+
+ std::vector<uint8_t> serialized;
+ ASSERT_TRUE(SerializeTransportParametersForTicket(
+ new_params, application_state_, &serialized));
+ EXPECT_EQ(original_serialized_params_, serialized);
+}
+
+TEST_F(TransportParametersTicketSerializationTest,
+ ConnectionIDDoesntChangeOutput) {
+ // Changing original destination CID doesn't change serialization.
+ TransportParameters new_params = original_params_;
+ new_params.original_destination_connection_id = TestConnectionId(0xCAFE);
+ EXPECT_NE(new_params, original_params_);
+
+ std::vector<uint8_t> serialized;
+ ASSERT_TRUE(SerializeTransportParametersForTicket(
+ new_params, application_state_, &serialized));
+ EXPECT_EQ(original_serialized_params_, serialized);
+}
+
+TEST_F(TransportParametersTicketSerializationTest, StreamLimitChangesOutput) {
+ // Changing a stream limit does change the serialization.
+ TransportParameters new_params = original_params_;
+ new_params.initial_max_stream_data_bidi_local.set_value(
+ kFakeInitialMaxStreamDataBidiLocal + 1);
+ EXPECT_NE(new_params, original_params_);
+
+ std::vector<uint8_t> serialized;
+ ASSERT_TRUE(SerializeTransportParametersForTicket(
+ new_params, application_state_, &serialized));
+ EXPECT_NE(original_serialized_params_, serialized);
+}
+
+TEST_F(TransportParametersTicketSerializationTest,
+ ApplicationStateChangesOutput) {
+ // Changing the application state changes the serialization.
+ std::vector<uint8_t> new_application_state = {0};
+ EXPECT_NE(new_application_state, application_state_);
+
+ std::vector<uint8_t> serialized;
+ ASSERT_TRUE(SerializeTransportParametersForTicket(
+ original_params_, new_application_state, &serialized));
+ EXPECT_NE(original_serialized_params_, serialized);
+}
+
} // namespace test
} // namespace quic