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: