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(), &parameter_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