quic: add TLS key log support (NSS Key Log Format)#44821
Open
bellatoris wants to merge 3 commits intoenvoyproxy:mainfrom
Open
quic: add TLS key log support (NSS Key Log Format)#44821bellatoris wants to merge 3 commits intoenvoyproxy:mainfrom
bellatoris wants to merge 3 commits intoenvoyproxy:mainfrom
Conversation
Signed-off-by: Doogie Min <doogie.min@sendbird.com>
Contributor
Author
|
Hi @danzh2010 and @RyanTheOptimist — when you have a moment, would you mind taking a look? This is a small follow-up to #42734 that addresses the |
Contributor
Author
|
/retest |
danzh2010
reviewed
May 5, 2026
- Rename ContextImpl::writeKeyLog to maybeWriteKeyLog to make the no-op-when-not-configured contract obvious at the call site. - Read connection addresses from the EnvoyQuicServerSession's Network::Connection facet (passed in at handshaker construction) instead of re-converting QUICHE addresses on every key log line. - Inline the runtime-flag literal in the integration test fixture instead of holding a single-use constant. Signed-off-by: Doogie Min <doogie.min@sendbird.com>
Contributor
Author
|
@danzh2010 addressed all three review comments in the latest commit:
PR description updated to match. PTAL when you have a moment, thanks! |
Match danzh2010's review comment literally by doing the static_cast<EnvoyQuicServerSession*>(session()) inside the keylog callback rather than at handshaker construction time. The original deviation existed because the literal pattern triggered a Bazel cycle (envoy_tls_server_handshaker -> envoy_quic_server_session_lib -> envoy_quic_proof_source_lib -> envoy_tls_server_handshaker) - but the cycle had a vestigial edge: envoy_quic_server_session.cc included envoy_quic_proof_source.h while referencing zero proof source symbols. Removing that stale include and adding the direct quic_server_transport_socket_factory.h include it was transitively providing dissolves the cycle. The envoy_connection_ member, its constructor parameter, and the construction-time cast in envoy_quic_crypto_server_stream.cc are no longer needed. Signed-off-by: Doogie Min <doogie.min@sendbird.com>
Contributor
Author
|
/retest |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Commit Message: quic: add TLS key log support so QUIC TLS secrets can be exported in NSS Key Log Format
Additional Description:
Summary
When debugging QUIC TLS behavior — session ticket resumption, certificate compression, or other handshake-level questions — the typical workflow is to open a packet capture in Wireshark and supply per-connection TLS secrets via a NSS Key Log file. Today this only works when the client can emit the key log itself via
SSLKEYLOGFILE. Some client stacks — for example Apple's Network framework on macOS and iOS, and SDKs built on top of it — don't expose that hook, so operators are left without an easy way to inspect those handshakes.For TCP TLS this is already covered: Envoy's
DownstreamTlsContexthas akey_logfield that hooks BoringSSL's key log callback and writes NSS-format lines to a file, with optional local/remote address filters. The same proto field exists for QUIC but is not yet plumbed (see #25418, underCommonTlsContext: key_log).This PR bridges that gap so the same
key_logconfiguration that already works for TCP TLS produces NSS Key Log lines for QUIC connections too, giving operators a server-side way to decrypt QUIC captures when the client can't help.Implementation
We piggy-back on the per-connection
EnvoyTlsServerHandshakerintroduced by #42734 and add a sibling static callbackEnvoyTlsServerHandshaker::keylogCallbacknext to the existingticketKeyCallback. The callback retrieves the handshaker from SSL ex_data, downcasts the QUIC session to itsNetwork::Connectionfacet (static_cast<EnvoyQuicServerSession*>(handshaker->session())), reads cached local/peer addresses from its connection info provider, and forwards the line to the pinnedServerContextImpl'smaybeWriteKeyLog(...).To keep the address-filter + file-write logic from being duplicated, the existing inline body of
ContextImpl::keylogCallbackis factored into a newvoid ContextImpl::maybeWriteKeyLog(line, local_addr, remote_addr)that both the TCP and QUIC paths call. The TCP callback is now a thin shim: fetch the per-connectionNetwork::TransportSocketCallbacksfrom ex_data, pull addresses off the connection, and callmaybeWriteKeyLog. No behavior change for TCP.The
SSL_CTX_set_keylog_callbackinstall lives inEnvoyQuicProofSource::OnNewSslCtx, paired with the existingSSL_CTX_set_tlsext_ticket_key_cbinstall under the same runtime flag. That's where the QUICHESSL_CTXis constructed, before any handshake on it exists, so installation is race-free and there is no per-connection bookkeeping. When no chain on the listener haskey_logconfigured, BoringSSL still invokes the callback per secret derivation, butmaybeWriteKeyLogshort-circuits on a null key log file with a single load + compare; the cost is dominated by BoringSSL's own dispatch overhead and is indistinguishable from "not registered" in any production-relevant measurement.A small preparatory cleanup is included:
source/common/quic/envoy_quic_server_session.ccwas includingenvoy_quic_proof_source.hwithout referencing any proof-source symbol, and the matching BUILD dep onenvoy_quic_proof_source_libexisted only to satisfy strict-headers for that vestigial include. Removing the include + dep (and adding the directquic_server_transport_socket_factory.hinclude the proof-source header was transitively providing) breaks a cycle that would otherwise preventenvoy_tls_server_handshakerfrom depending onenvoy_quic_server_session_libto do the cast.Key design decisions:
Reuse the pinned
ServerContextImplfrom quic: add TLS session ticket resumption support #42734. The handshaker already pins aServerContextSharedPtrfor session-ticket purposes. The key log callback uses the same pin, so an SDS rotation that swaps the factory's active context does not affect in-flight connections — every line a connection emits goes to the key log file the connection was created with, matching TCP TLS behavior.maybeWriteKeyLoglives onContextImpl, shared by TCP and QUIC. The key log file and IP-list filter members already live onContextImpl, so factoring the filter+write step into a member of that class is the natural home. RefactoringContextImpl::keylogCallbackto delegate makes the new method genuinely shared rather than parallel-copied logic.Addresses come from
EnvoyQuicServerSession'sNetwork::Connectionfacet. InsidekeylogCallback,static_cast<EnvoyQuicServerSession*>(handshaker->session())->connectionInfoProvider()returns the same cached envoy address objects the connection was created with — no per-line allocation.Graceful fallback on null handshaker — same shape as
ticketKeyCallback. If the runtime guard is off and the vanillaquic::TlsServerHandshakeris in use, no handshaker is in ex_data, the callback finds null, and silently returns. NoENVOY_BUG, no crash.Flow
Limitations / Follow-ups
EnvoyTlsServerHandshakeris currently constructed only when theenvoy.reloadable_features.quic_session_ticket_supportruntime guard from #42734 is on. As a consequence, QUIC key log only fires for listeners on instances where that guard is enabled. This is fine for our debugging use case (we are happy to flip both at the same time), and the key log callback is harmless when not backed by a handshaker. A small follow-up could broaden handshaker creation to also trigger whenkey_logis configured, decoupling the two features entirely; we left that out of this PR to keep the diff minimal.Risk Level: Low (no behavior change unless
key_logis configured; the TCP TLS path is refactored but observably unchanged)Testing:
EnvoyTlsServerHandshakerTest.KeylogCallbackNullHandshaker(graceful fallback when no handshaker is in ex_data).quic_http_integration_test(KeylogNoFilter,KeylogLocalFilterMatches,KeylogRemoteFilterMatches,KeylogLocalAndRemoteFilterMatch,KeylogLocalFilterNoMatch,KeylogRemoteFilterNoMatch,KeylogNotConfigured), each parameterized on IPv4/IPv6. Positive cases assert the file contains all five TLS 1.3 secrets (CLIENT_HANDSHAKE_TRAFFIC_SECRET,SERVER_HANDSHAKE_TRAFFIC_SECRET,CLIENT_TRAFFIC_SECRET,SERVER_TRAFFIC_SECRET,EXPORTER_SECRET); address-filter-no-match cases assert the file is created but empty;KeylogNotConfiguredasserts no file is created whenkey_logis absent from the proto config.test/common/tls/integration/ssl_integration_testcontinue to pass after thekeylogCallback→maybeWriteKeyLogrefactor.Docs Changes: N/A — uses the existing
key_logproto already documented for TCP TLS.Release Notes: N/A.
Platform Specific Features: N/A
[Optional Fixes #Issue] Fixes #35339. Also addresses the
CommonTlsContext: key_logbullet of #25418.