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: