Make QuicCryptoStream::ExportKeyingMaterial pure virtual and implement it in subclasses.

For IETF QUIC, this method delegates to TlsHandshaker::ExportKeyingMaterialForLabel
For gQUIC, this method always return false. Code related to the existing gQUIC implementation is removed as it is not actively used anywhere.

PiperOrigin-RevId: 400827743
diff --git a/quic/core/crypto/crypto_utils.cc b/quic/core/crypto/crypto_utils.cc
index 9c12d8c..2f1fa34 100644
--- a/quic/core/crypto/crypto_utils.cc
+++ b/quic/core/crypto/crypto_utils.cc
@@ -542,35 +542,6 @@
 }
 
 // static
-bool CryptoUtils::ExportKeyingMaterial(absl::string_view subkey_secret,
-                                       absl::string_view label,
-                                       absl::string_view context,
-                                       size_t result_len,
-                                       std::string* result) {
-  for (size_t i = 0; i < label.length(); i++) {
-    if (label[i] == '\0') {
-      QUIC_LOG(ERROR) << "ExportKeyingMaterial label may not contain NULs";
-      return false;
-    }
-  }
-  // Create HKDF info input: null-terminated label + length-prefixed context
-  if (context.length() >= std::numeric_limits<uint32_t>::max()) {
-    QUIC_LOG(ERROR) << "Context value longer than 2^32";
-    return false;
-  }
-  uint32_t context_length = static_cast<uint32_t>(context.length());
-  std::string info = std::string(label);
-  info.push_back('\0');
-  info.append(reinterpret_cast<char*>(&context_length), sizeof(context_length));
-  info.append(context.data(), context.length());
-
-  QuicHKDF hkdf(subkey_secret, absl::string_view() /* no salt */, info,
-                result_len, 0 /* no fixed IV */, 0 /* no subkey secret */);
-  *result = std::string(hkdf.client_write_key());
-  return true;
-}
-
-// static
 uint64_t CryptoUtils::ComputeLeafCertHash(absl::string_view cert) {
   return QuicUtils::FNV1a_64_Hash(cert);
 }
diff --git a/quic/core/crypto/crypto_utils.h b/quic/core/crypto/crypto_utils.h
index 7aa32e1..15ffe06 100644
--- a/quic/core/crypto/crypto_utils.h
+++ b/quic/core/crypto/crypto_utils.h
@@ -166,16 +166,6 @@
                          CrypterPair* crypters,
                          std::string* subkey_secret);
 
-  // Performs key extraction to derive a new secret of |result_len| bytes
-  // dependent on |subkey_secret|, |label|, and |context|. Returns false if the
-  // parameters are invalid (e.g. |label| contains null bytes); returns true on
-  // success.
-  static bool ExportKeyingMaterial(absl::string_view subkey_secret,
-                                   absl::string_view label,
-                                   absl::string_view context,
-                                   size_t result_len,
-                                   std::string* result);
-
   // Computes the FNV-1a hash of the provided DER-encoded cert for use in the
   // XLCT tag.
   static uint64_t ComputeLeafCertHash(absl::string_view cert);
diff --git a/quic/core/crypto/crypto_utils_test.cc b/quic/core/crypto/crypto_utils_test.cc
index 0f2df3a..6c3d384 100644
--- a/quic/core/crypto/crypto_utils_test.cc
+++ b/quic/core/crypto/crypto_utils_test.cc
@@ -19,63 +19,6 @@
 
 class CryptoUtilsTest : public QuicTest {};
 
-TEST_F(CryptoUtilsTest, TestExportKeyingMaterial) {
-  const struct TestVector {
-    // Input (strings of hexadecimal digits):
-    const char* subkey_secret;
-    const char* label;
-    const char* context;
-    size_t result_len;
-
-    // Expected output (string of hexadecimal digits):
-    const char* expected;  // Null if it should fail.
-  } test_vector[] = {
-      // Try a typical input
-      {"4823c1189ecc40fce888fbb4cf9ae6254f19ba12e6d9af54788f195a6f509ca3",
-       "e934f78d7a71dd85420fceeb8cea0317",
-       "b8d766b5d3c8aba0009c7ed3de553eba53b4de1030ea91383dcdf724cd8b7217", 32,
-       "a9979da0d5f1c1387d7cbe68f5c4163ddb445a03c4ad6ee72cb49d56726d679e"},
-      // Don't let the label contain nulls
-      {"14fe51e082ffee7d1b4d8d4ab41f8c55", "3132333435363700",
-       "58585858585858585858585858585858", 16, nullptr},
-      // Make sure nulls in the context are fine
-      {"d862c2e36b0a42f7827c67ebc8d44df7", "7a5b95e4e8378123",
-       "4142434445464700", 16, "12d418c6d0738a2e4d85b2d0170f76e1"},
-      // ... and give a different result than without
-      {"d862c2e36b0a42f7827c67ebc8d44df7", "7a5b95e4e8378123", "41424344454647",
-       16, "abfa1c479a6e3ffb98a11dee7d196408"},
-      // Try weird lengths
-      {"d0ec8a34f6cc9a8c96", "49711798cc6251",
-       "933d4a2f30d22f089cfba842791116adc121e0", 23,
-       "c9a46ed0757bd1812f1f21b4d41e62125fec8364a21db7"},
-  };
-
-  for (size_t i = 0; i < ABSL_ARRAYSIZE(test_vector); i++) {
-    // Decode the test vector.
-    std::string subkey_secret =
-        absl::HexStringToBytes(test_vector[i].subkey_secret);
-    std::string label = absl::HexStringToBytes(test_vector[i].label);
-    std::string context = absl::HexStringToBytes(test_vector[i].context);
-    size_t result_len = test_vector[i].result_len;
-    bool expect_ok = test_vector[i].expected != nullptr;
-    std::string expected;
-    if (expect_ok) {
-      expected = absl::HexStringToBytes(test_vector[i].expected);
-    }
-
-    std::string result;
-    bool ok = CryptoUtils::ExportKeyingMaterial(subkey_secret, label, context,
-                                                result_len, &result);
-    EXPECT_EQ(expect_ok, ok);
-    if (expect_ok) {
-      EXPECT_EQ(result_len, result.length());
-      quiche::test::CompareCharArraysWithHexError(
-          "HKDF output", result.data(), result.length(), expected.data(),
-          expected.length());
-    }
-  }
-}
-
 TEST_F(CryptoUtilsTest, HandshakeFailureReasonToString) {
   EXPECT_STREQ("HANDSHAKE_OK",
                CryptoUtils::HandshakeFailureReasonToString(HANDSHAKE_OK));
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 66a0af6..eb1f66c 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -860,6 +860,51 @@
   server_thread_->Resume();
 }
 
+TEST_P(EndToEndTest, ExportKeyingMaterial) {
+  ASSERT_TRUE(Initialize());
+  if (!version_.UsesTls()) {
+    return;
+  }
+  const char* kExportLabel = "label";
+  const int kExportLen = 30;
+  std::string client_keying_material_export, server_keying_material_export;
+
+  EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable());
+  ASSERT_TRUE(server_thread_);
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  server_thread_->Pause();
+  QuicSpdySession* server_session = GetServerSession();
+  QuicCryptoStream* server_crypto_stream = nullptr;
+  if (server_session != nullptr) {
+    server_crypto_stream =
+        QuicSessionPeer::GetMutableCryptoStream(server_session);
+  } else {
+    ADD_FAILURE() << "Missing server session";
+  }
+  if (server_crypto_stream != nullptr) {
+    ASSERT_TRUE(server_crypto_stream->ExportKeyingMaterial(
+        kExportLabel, /*context=*/"", kExportLen,
+        &server_keying_material_export));
+
+  } else {
+    ADD_FAILURE() << "Missing server crypto stream";
+  }
+  server_thread_->Resume();
+
+  QuicSpdyClientSession* client_session = GetClientSession();
+  ASSERT_TRUE(client_session);
+  QuicCryptoStream* client_crypto_stream =
+      QuicSessionPeer::GetMutableCryptoStream(client_session);
+  ASSERT_TRUE(client_crypto_stream);
+  ASSERT_TRUE(client_crypto_stream->ExportKeyingMaterial(
+      kExportLabel, /*context=*/"", kExportLen,
+      &client_keying_material_export));
+  ASSERT_EQ(client_keying_material_export.size(),
+            static_cast<size_t>(kExportLen));
+  EXPECT_EQ(client_keying_material_export, server_keying_material_export);
+}
+
 TEST_P(EndToEndTest, SimpleRequestResponse) {
   ASSERT_TRUE(Initialize());
 
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 48f488d..5c3e525 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -188,6 +188,13 @@
                           ConnectionCloseSource /*source*/) override {}
   SSL* GetSsl() const override { return nullptr; }
 
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    return false;
+  }
+
  private:
   using QuicCryptoStream::session;
 
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 37ed187..48b654b 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -168,6 +168,13 @@
 
   MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override));
 
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    return false;
+  }
+
   SSL* GetSsl() const override { return nullptr; }
 
  private:
diff --git a/quic/core/quic_crypto_client_handshaker.h b/quic/core/quic_crypto_client_handshaker.h
index e926066..d9f1035 100644
--- a/quic/core/quic_crypto_client_handshaker.h
+++ b/quic/core/quic_crypto_client_handshaker.h
@@ -65,6 +65,13 @@
       std::unique_ptr<ApplicationState> /*application_state*/) override {
     QUICHE_NOTREACHED();
   }
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    QUICHE_NOTREACHED();
+    return false;
+  }
 
   // From QuicCryptoHandshaker
   void OnHandshakeMessage(const CryptoHandshakeMessage& message) override;
diff --git a/quic/core/quic_crypto_client_stream.cc b/quic/core/quic_crypto_client_stream.cc
index 27de0b8..fe6471f 100644
--- a/quic/core/quic_crypto_client_stream.cc
+++ b/quic/core/quic_crypto_client_stream.cc
@@ -128,6 +128,13 @@
   return handshaker_->CreateCurrentOneRttEncrypter();
 }
 
+bool QuicCryptoClientStream::ExportKeyingMaterial(absl::string_view label,
+                                                  absl::string_view context,
+                                                  size_t result_len,
+                                                  std::string* result) {
+  return handshaker_->ExportKeyingMaterial(label, context, result_len, result);
+}
+
 std::string QuicCryptoClientStream::chlo_hash() const {
   return handshaker_->chlo_hash();
 }
diff --git a/quic/core/quic_crypto_client_stream.h b/quic/core/quic_crypto_client_stream.h
index e539165..daea001 100644
--- a/quic/core/quic_crypto_client_stream.h
+++ b/quic/core/quic_crypto_client_stream.h
@@ -65,6 +65,14 @@
   // client.  Does not count update messages that were received prior
   // to handshake confirmation.
   virtual int num_scup_messages_received() const = 0;
+
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    QUICHE_NOTREACHED();
+    return false;
+  }
 };
 
 class QUIC_EXPORT_PRIVATE QuicCryptoClientStream
@@ -187,6 +195,13 @@
     // Called when application state is received.
     virtual void SetServerApplicationStateForResumption(
         std::unique_ptr<ApplicationState> application_state) = 0;
+
+    // Called to obtain keying material export of length |result_len| with the
+    // given |label| and |context|. Returns false on failure.
+    virtual bool ExportKeyingMaterial(absl::string_view label,
+                                      absl::string_view context,
+                                      size_t result_len,
+                                      std::string* result) = 0;
   };
 
   // ProofHandler is an interface that handles callbacks from the crypto
@@ -253,7 +268,8 @@
       override;
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override;
   SSL* GetSsl() const override;
-
+  bool ExportKeyingMaterial(absl::string_view label, absl::string_view context,
+                            size_t result_len, std::string* result) override;
   std::string chlo_hash() const;
 
  protected:
diff --git a/quic/core/quic_crypto_server_stream_base.h b/quic/core/quic_crypto_server_stream_base.h
index d2b9f15..967d6d1 100644
--- a/quic/core/quic_crypto_server_stream_base.h
+++ b/quic/core/quic_crypto_server_stream_base.h
@@ -93,6 +93,14 @@
   // made. The Details are owned by the QuicCryptoServerStreamBase and the
   // pointer is only valid while the owning object is still valid.
   virtual const ProofSource::Details* ProofSourceDetails() const = 0;
+
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    QUICHE_NOTREACHED();
+    return false;
+  }
 };
 
 // Creates an appropriate QuicCryptoServerStream for the provided parameters,
diff --git a/quic/core/quic_crypto_stream.cc b/quic/core/quic_crypto_stream.cc
index a14cef0..99fb2cd 100644
--- a/quic/core/quic_crypto_stream.cc
+++ b/quic/core/quic_crypto_stream.cc
@@ -127,20 +127,6 @@
   }
 }
 
-bool QuicCryptoStream::ExportKeyingMaterial(absl::string_view label,
-                                            absl::string_view context,
-                                            size_t result_len,
-                                            std::string* result) const {
-  if (!one_rtt_keys_available()) {
-    QUIC_DLOG(ERROR) << "ExportKeyingMaterial was called before forward-secure"
-                     << "encryption was established.";
-    return false;
-  }
-  return CryptoUtils::ExportKeyingMaterial(
-      crypto_negotiated_params().subkey_secret, label, context, result_len,
-      result);
-}
-
 void QuicCryptoStream::WriteCryptoData(EncryptionLevel level,
                                        absl::string_view data) {
   if (!QuicVersionUsesCryptoFrames(session()->transport_version())) {
diff --git a/quic/core/quic_crypto_stream.h b/quic/core/quic_crypto_stream.h
index 37edc87..b42b2d9 100644
--- a/quic/core/quic_crypto_stream.h
+++ b/quic/core/quic_crypto_stream.h
@@ -63,11 +63,12 @@
   // Performs key extraction to derive a new secret of |result_len| bytes
   // dependent on |label|, |context|, and the stream's negotiated subkey secret.
   // Returns false if the handshake has not been confirmed or the parameters are
-  // invalid (e.g. |label| contains null bytes); returns true on success.
-  bool ExportKeyingMaterial(absl::string_view label,
-                            absl::string_view context,
-                            size_t result_len,
-                            std::string* result) const;
+  // invalid (e.g. |label| contains null bytes); returns true on success. This
+  // method is only supported for IETF QUIC and MUST NOT be called in gQUIC as
+  // that'll trigger an assert in DEBUG build.
+  virtual bool ExportKeyingMaterial(absl::string_view label,
+                                    absl::string_view context,
+                                    size_t result_len, std::string* result) = 0;
 
   // Writes |data| to the QuicStream at level |level|.
   virtual void WriteCryptoData(EncryptionLevel level, absl::string_view data);
diff --git a/quic/core/quic_crypto_stream_test.cc b/quic/core/quic_crypto_stream_test.cc
index 6624a95..e1bbf35 100644
--- a/quic/core/quic_crypto_stream_test.cc
+++ b/quic/core/quic_crypto_stream_test.cc
@@ -79,6 +79,12 @@
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override {
     return nullptr;
   }
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    return false;
+  }
   SSL* GetSsl() const override { return nullptr; }
 
  private:
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index f87302f..b2f6274 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -168,6 +168,13 @@
   void OnConnectionClosed(QuicErrorCode /*error*/,
                           ConnectionCloseSource /*source*/) override {}
 
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    return false;
+  }
+
   SSL* GetSsl() const override { return nullptr; }
 
  private:
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 4010590..50ba814 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -326,6 +326,13 @@
   return "";
 }
 
+bool TlsClientHandshaker::ExportKeyingMaterial(absl::string_view label,
+                                               absl::string_view context,
+                                               size_t result_len,
+                                               std::string* result) {
+  return ExportKeyingMaterialForLabel(label, context, result_len, result);
+}
+
 bool TlsClientHandshaker::encryption_established() const {
   return encryption_established_;
 }
diff --git a/quic/core/tls_client_handshaker.h b/quic/core/tls_client_handshaker.h
index 4e0e3b6..4543f9b 100644
--- a/quic/core/tls_client_handshaker.h
+++ b/quic/core/tls_client_handshaker.h
@@ -50,6 +50,8 @@
   bool ReceivedInchoateReject() const override;
   int num_scup_messages_received() const override;
   std::string chlo_hash() const override;
+  bool ExportKeyingMaterial(absl::string_view label, absl::string_view context,
+                            size_t result_len, std::string* result) override;
 
   // From QuicCryptoClientStream::HandshakerInterface and TlsHandshaker
   bool encryption_established() const override;
diff --git a/quic/core/tls_handshaker.cc b/quic/core/tls_handshaker.cc
index 4b2d717..0c9b6f4 100644
--- a/quic/core/tls_handshaker.cc
+++ b/quic/core/tls_handshaker.cc
@@ -328,6 +328,23 @@
   return encrypter;
 }
 
+bool TlsHandshaker::ExportKeyingMaterialForLabel(absl::string_view label,
+                                                 absl::string_view context,
+                                                 size_t result_len,
+                                                 std::string* result) {
+  // TODO(haoyuewang) Adding support of keying material export when 0-RTT is
+  // accepted.
+  if (SSL_in_init(ssl())) {
+    return false;
+  }
+  result->resize(result_len);
+  return SSL_export_keying_material(
+             ssl(), reinterpret_cast<uint8_t*>(result->data()), result_len,
+             label.data(), label.size(),
+             reinterpret_cast<const uint8_t*>(context.data()), context.size(),
+             !context.empty()) == 1;
+}
+
 void TlsHandshaker::WriteMessage(EncryptionLevel level,
                                  absl::string_view data) {
   stream_->WriteCryptoData(level, data);
diff --git a/quic/core/tls_handshaker.h b/quic/core/tls_handshaker.h
index 72ae099..3335564 100644
--- a/quic/core/tls_handshaker.h
+++ b/quic/core/tls_handshaker.h
@@ -53,6 +53,9 @@
   std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter();
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter();
   virtual HandshakeState GetHandshakeState() const = 0;
+  bool ExportKeyingMaterialForLabel(absl::string_view label,
+                                    absl::string_view context,
+                                    size_t result_len, std::string* result);
 
  protected:
   // Called when a new message is received on the crypto stream and is available
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index 3abf0ac..4e47a63 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -363,6 +363,13 @@
   return proof_source_details_.get();
 }
 
+bool TlsServerHandshaker::ExportKeyingMaterial(absl::string_view label,
+                                               absl::string_view context,
+                                               size_t result_len,
+                                               std::string* result) {
+  return ExportKeyingMaterialForLabel(label, context, result_len, result);
+}
+
 void TlsServerHandshaker::OnConnectionClosed(QuicErrorCode error,
                                              ConnectionCloseSource source) {
   TlsHandshaker::OnConnectionClosed(error, source);
diff --git a/quic/core/tls_server_handshaker.h b/quic/core/tls_server_handshaker.h
index f2a42ad..b7179a7 100644
--- a/quic/core/tls_server_handshaker.h
+++ b/quic/core/tls_server_handshaker.h
@@ -64,6 +64,8 @@
   bool ShouldSendExpectCTHeader() const override;
   bool DidCertMatchSni() const override;
   const ProofSource::Details* ProofSourceDetails() const override;
+  bool ExportKeyingMaterial(absl::string_view label, absl::string_view context,
+                            size_t result_len, std::string* result) override;
   SSL* GetSsl() const override;
 
   // From QuicCryptoServerStreamBase and TlsHandshaker
diff --git a/quic/test_tools/crypto_test_utils.cc b/quic/test_tools/crypto_test_utils.cc
index d28b4fa..febd8d3 100644
--- a/quic/test_tools/crypto_test_utils.cc
+++ b/quic/test_tools/crypto_test_utils.cc
@@ -634,22 +634,6 @@
       "subkey secret", client_subkey_secret.data(),
       client_subkey_secret.length(), server_subkey_secret.data(),
       server_subkey_secret.length());
-
-  const char kSampleLabel[] = "label";
-  const char kSampleContext[] = "context";
-  const size_t kSampleOutputLength = 32;
-  std::string client_key_extraction;
-  std::string server_key_extraction;
-  EXPECT_TRUE(client->ExportKeyingMaterial(kSampleLabel, kSampleContext,
-                                           kSampleOutputLength,
-                                           &client_key_extraction));
-  EXPECT_TRUE(server->ExportKeyingMaterial(kSampleLabel, kSampleContext,
-                                           kSampleOutputLength,
-                                           &server_key_extraction));
-  quiche::test::CompareCharArraysWithHexError(
-      "sample key extraction", client_key_extraction.data(),
-      client_key_extraction.length(), server_key_extraction.data(),
-      server_key_extraction.length());
 }
 
 QuicTag ParseTag(const char* tagstr) {
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 6e9db90..e746ddb 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -844,6 +844,12 @@
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override {
     return nullptr;
   }
+  bool ExportKeyingMaterial(absl::string_view /*label*/,
+                            absl::string_view /*context*/,
+                            size_t /*result_len*/,
+                            std::string* /*result*/) override {
+    return false;
+  }
   SSL* GetSsl() const override { return nullptr; }
 
  private: