From fd93527d00a6942eb0ad0e962c45088ef6891389 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 7 Feb 2024 13:27:15 -0800 Subject: [PATCH 01/26] quic: support multiple certs, selected by SNI This brings feature parity between quic and non-quic TLS use for certificate selection. Signed-off-by: Greg Greenway --- envoy/ssl/context_manager.h | 9 +- source/common/quic/envoy_quic_proof_source.cc | 80 +++-------- source/common/quic/envoy_quic_proof_source.h | 13 +- .../quic_server_transport_socket_factory.cc | 95 ++++++++++++- .../quic_server_transport_socket_factory.h | 27 ++-- source/extensions/transport_sockets/tls/BUILD | 1 + .../transport_sockets/tls/context_impl.cc | 133 +++++++++++------- .../transport_sockets/tls/context_impl.h | 46 ++++-- .../tls/context_manager_impl.cc | 11 +- .../tls/context_manager_impl.h | 3 +- .../transport_sockets/tls/ssl_socket.cc | 4 +- source/server/ssl_context_manager.cc | 7 +- test/mocks/ssl/mocks.h | 3 +- 13 files changed, 266 insertions(+), 166 deletions(-) diff --git a/envoy/ssl/context_manager.h b/envoy/ssl/context_manager.h index 73ea031a7f2e..8c7fae3707b2 100644 --- a/envoy/ssl/context_manager.h +++ b/envoy/ssl/context_manager.h @@ -12,6 +12,12 @@ namespace Envoy { namespace Ssl { +// Opaque type defined and used by the ``ServerContext``. +struct TlsContext; + +using ContextAdditionalInitFunc = + std::function; + /** * Manages all of the SSL contexts in the process */ @@ -30,7 +36,8 @@ class ContextManager { */ virtual ServerContextSharedPtr createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, - const std::vector& server_names) PURE; + const std::vector& server_names, + ContextAdditionalInitFunc additional_init) PURE; /** * @return the number of days until the next certificate being managed will expire, the value is diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 337ef148d1b9..7c656c63188f 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -18,67 +18,26 @@ quiche::QuicheReferenceCountedPointer EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { - // TODO(DavidSchinazi) parse the certificate to correctly fill in |cert_matched_sni|. - *cert_matched_sni = false; - - CertConfigWithFilterChain res = - getTlsCertConfigAndFilterChain(server_address, client_address, hostname); - absl::optional> cert_config_ref = - res.cert_config_; - if (!cert_config_ref.has_value()) { - return nullptr; - } - auto& cert_config = cert_config_ref.value().get(); - const std::string& chain_str = cert_config.certificateChain(); - std::stringstream pem_stream(chain_str); - std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); - - quiche::QuicheReferenceCountedPointer cert_chain( - new quic::ProofSource::Chain(chain)); - std::string error_details; - bssl::UniquePtr cert = parseDERCertificate(cert_chain->certs[0], &error_details); - if (cert == nullptr) { - ENVOY_LOG(warn, absl::StrCat("Invalid leaf cert: ", error_details)); - return nullptr; - } - - bssl::UniquePtr pub_key(X509_get_pubkey(cert.get())); - int sign_alg = deduceSignatureAlgorithmFromPublicKey(pub_key.get(), &error_details); - if (sign_alg == 0) { - ENVOY_LOG(warn, absl::StrCat("Failed to deduce signature algorithm from public key: ", - error_details)); - return nullptr; - } - return cert_chain; + CertWithFilterChain res = + getTlsCertConfigAndFilterChain(server_address, client_address, hostname, cert_matched_sni); + return res.cert_; } void EnvoyQuicProofSource::signPayload( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { - CertConfigWithFilterChain res = - getTlsCertConfigAndFilterChain(server_address, client_address, hostname); - absl::optional> cert_config_ref = - res.cert_config_; - if (!cert_config_ref.has_value()) { + CertWithFilterChain res = getTlsCertConfigAndFilterChain(server_address, client_address, hostname, + nullptr /* cert_matched_sni */); + if (res.private_key_ == nullptr) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); callback->Run(false, "", nullptr); return; } - auto& cert_config = cert_config_ref.value().get(); - // Load private key. - const std::string& pkey = cert_config.privateKey(); - std::stringstream pem_str(pkey); - std::unique_ptr pem_key = - quic::CertificatePrivateKey::LoadPemFromStream(&pem_str); - if (pem_key == nullptr) { - ENVOY_LOG(warn, "Failed to load private key."); - callback->Run(false, "", nullptr); - return; - } // Verify the signature algorithm is as expected. std::string error_details; - int sign_alg = deduceSignatureAlgorithmFromPublicKey(pem_key->private_key(), &error_details); + int sign_alg = + deduceSignatureAlgorithmFromPublicKey(res.private_key_->private_key(), &error_details); if (sign_alg != signature_algorithm) { ENVOY_LOG(warn, fmt::format("The signature algorithm {} from the private key is not expected: {}", @@ -88,17 +47,16 @@ void EnvoyQuicProofSource::signPayload( } // Sign. - std::string sig = pem_key->Sign(in, signature_algorithm); + std::string sig = res.private_key_->Sign(in, signature_algorithm); bool success = !sig.empty(); ASSERT(res.filter_chain_.has_value()); callback->Run(success, sig, std::make_unique(res.filter_chain_.value().get())); } -EnvoyQuicProofSource::CertConfigWithFilterChain -EnvoyQuicProofSource::getTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname) { +EnvoyQuicProofSource::CertWithFilterChain EnvoyQuicProofSource::getTlsCertConfigAndFilterChain( + const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, + const std::string& hostname, bool* cert_matched_sni) { ENVOY_LOG(trace, "Getting cert chain for {}", hostname); // TODO(danzh) modify QUICHE to make quic session or ALPN accessible to avoid hard-coded ALPN. Network::ConnectionSocketPtr connection_socket = createServerConnectionSocket( @@ -111,23 +69,19 @@ EnvoyQuicProofSource::getTlsCertConfigAndFilterChain(const quic::QuicSocketAddre if (filter_chain == nullptr) { listener_stats_.no_filter_chain_match_.inc(); ENVOY_LOG(warn, "No matching filter chain found for handshake."); - return {absl::nullopt, absl::nullopt}; + return {}; } ENVOY_LOG(trace, "Got a matching cert chain {}", filter_chain->name()); auto& transport_socket_factory = dynamic_cast(filter_chain->transportSocketFactory()); - std::vector> tls_cert_configs = - transport_socket_factory.getTlsCertificates(); - if (tls_cert_configs.empty()) { + auto [cert, key] = transport_socket_factory.getTlsCertificateAndKey(hostname, cert_matched_sni); + if (cert == nullptr || key == nullptr) { ENVOY_LOG(warn, "No certificate is configured in transport socket config."); - return {absl::nullopt, absl::nullopt}; + return {}; } - // Only return the first TLS cert config. - // TODO(danzh) Choose based on supported cipher suites in TLS1.3 CHLO and prefer EC - // certs if supported. - return {tls_cert_configs[0].get(), *filter_chain}; + return {std::move(cert), std::move(key), *filter_chain}; } void EnvoyQuicProofSource::updateFilterChainManager( diff --git a/source/common/quic/envoy_quic_proof_source.h b/source/common/quic/envoy_quic_proof_source.h index d47ab9ba7bb3..c8fcb023dc8d 100644 --- a/source/common/quic/envoy_quic_proof_source.h +++ b/source/common/quic/envoy_quic_proof_source.h @@ -34,15 +34,16 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { std::unique_ptr callback) override; private: - struct CertConfigWithFilterChain { - absl::optional> cert_config_; + struct CertWithFilterChain { + quiche::QuicheReferenceCountedPointer cert_; + std::shared_ptr private_key_; absl::optional> filter_chain_; }; - CertConfigWithFilterChain - getTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname); + CertWithFilterChain getTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, + bool* cert_matched_sni); Network::Socket& listen_socket_; Network::FilterChainManager* filter_chain_manager_{nullptr}; diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index 8054a65ab306..86988fc27a45 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -5,6 +5,7 @@ #include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.validate.h" #include "source/common/runtime/runtime_features.h" +#include "source/common/quic/envoy_quic_utils.h" #include "source/extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { @@ -13,7 +14,7 @@ namespace Quic { Network::DownstreamTransportSocketFactoryPtr QuicServerTransportSocketConfigFactory::createTransportSocketFactory( const Protobuf::Message& config, Server::Configuration::TransportSocketFactoryContext& context, - const std::vector& /*server_names*/) { + const std::vector& server_names) { auto quic_transport = MessageUtil::downcastAndValidate< const envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport&>( config, context.messageValidationVisitor()); @@ -26,11 +27,60 @@ QuicServerTransportSocketConfigFactory::createTransportSocketFactory( auto factory = std::make_unique( PROTOBUF_GET_WRAPPED_OR_DEFAULT(quic_transport, enable_early_data, true), - context.statsScope(), std::move(server_config)); + context.statsScope(), std::move(server_config), context.sslContextManager(), server_names); factory->initialize(); return factory; } +namespace { +void initializeQuicCertAndKey(Ssl::TlsContext& context, + const Ssl::TlsCertificateConfig& cert_config) { + const std::string& chain_str = cert_config.certificateChain(); + std::stringstream pem_stream(chain_str); + std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); + + quiche::QuicheReferenceCountedPointer cert_chain( + new quic::ProofSource::Chain(chain)); + std::string error_details; + bssl::UniquePtr cert = parseDERCertificate(cert_chain->certs[0], &error_details); + if (cert == nullptr) { + throwEnvoyExceptionOrPanic(absl::StrCat("Invalid leaf cert: ", error_details)); + } + + bssl::UniquePtr pub_key(X509_get_pubkey(cert.get())); + int sign_alg = deduceSignatureAlgorithmFromPublicKey(pub_key.get(), &error_details); + if (sign_alg == 0) { + throwEnvoyExceptionOrPanic( + absl::StrCat("Failed to deduce signature algorithm from public key: ", error_details)); + } + + context.quic_cert_ = std::move(cert_chain); + + const std::string& pkey = cert_config.privateKey(); + std::stringstream pem_str(pkey); + std::unique_ptr pem_key = + quic::CertificatePrivateKey::LoadPemFromStream(&pem_str); + if (pem_key == nullptr) { + throwEnvoyExceptionOrPanic("Failed to load QUIC private key."); + } + + context.quic_private_key_ = std::move(pem_key); +} +} // namespace + +QuicServerTransportSocketFactory::QuicServerTransportSocketFactory( + bool enable_early_data, Stats::Scope& scope, Ssl::ServerContextConfigPtr config, + Envoy::Ssl::ContextManager& manager, const std::vector& server_names) + : QuicTransportSocketFactoryBase(scope, "server"), manager_(manager), stats_scope_(scope), + config_(std::move(config)), server_names_(server_names), + ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_, + initializeQuicCertAndKey)), + enable_early_data_(enable_early_data) {} + +QuicServerTransportSocketFactory::~QuicServerTransportSocketFactory() { + manager_.removeContext(ssl_ctx_); +} + ProtobufTypes::MessagePtr QuicServerTransportSocketConfigFactory::createEmptyConfigProto() { return std::make_unique< envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport>(); @@ -46,6 +96,47 @@ void QuicServerTransportSocketFactory::initialize() { } } +std::pair, + std::shared_ptr> +QuicServerTransportSocketFactory::getTlsCertificateAndKey(absl::string_view sni, + bool* cert_matched_sni) const { + // onSecretUpdated() could be invoked in the middle of checking the existence of ssl_ctx and + // creating SslSocket using ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and + // use the same ssl_ctx to create SslSocket. + Envoy::Ssl::ServerContextSharedPtr ssl_ctx; + { + absl::ReaderMutexLock l(&ssl_ctx_mu_); + ssl_ctx = ssl_ctx_; + } + if (!ssl_ctx) { + ENVOY_LOG(warn, "SDS hasn't finished updating Ssl context config yet."); + stats_.downstream_context_secrets_not_ready_.inc(); + return {}; + } + auto ctx = + std::dynamic_pointer_cast(ssl_ctx); + auto [tls_context, ocsp_staple_action] = ctx->findTlsContext( + sni, true /* TODO: ecdsa_capable */, false /* TODO: ocsp_capable */, cert_matched_sni); + + // Thread safety note: accessing the tls_context requires holding a shared_ptr to the ``ssl_ctx``. + // Both of these members are themselves refcounted, so it is safe to use them after ``ssl_ctx`` + // goes out of scope after the function returns. + return {tls_context.quic_cert_, tls_context.quic_private_key_}; +} + +void QuicServerTransportSocketFactory::onSecretUpdated() { + ENVOY_LOG(debug, "Secret is updated."); + auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_, + initializeQuicCertAndKey); + { + absl::WriterMutexLock l(&ssl_ctx_mu_); + std::swap(ctx, ssl_ctx_); + } + manager_.removeContext(ctx); + + stats_.context_config_update_by_sds_.inc(); +} + REGISTER_FACTORY(QuicServerTransportSocketConfigFactory, Server::Configuration::DownstreamTransportSocketConfigFactory); diff --git a/source/common/quic/quic_server_transport_socket_factory.h b/source/common/quic/quic_server_transport_socket_factory.h index bb64ec76e9af..6c7819f0044c 100644 --- a/source/common/quic/quic_server_transport_socket_factory.h +++ b/source/common/quic/quic_server_transport_socket_factory.h @@ -19,9 +19,10 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock public QuicTransportSocketFactoryBase { public: QuicServerTransportSocketFactory(bool enable_early_data, Stats::Scope& store, - Ssl::ServerContextConfigPtr config) - : QuicTransportSocketFactoryBase(store, "server"), config_(std::move(config)), - enable_early_data_(enable_early_data) {} + Ssl::ServerContextConfigPtr config, + Envoy::Ssl::ContextManager& manager, + const std::vector& server_names); + ~QuicServerTransportSocketFactory() override; // Network::DownstreamTransportSocketFactory Network::TransportSocketPtr createDownstreamTransportSocket() const override { @@ -31,24 +32,22 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock void initialize() override; - // Return TLS certificates if the context config is ready. - std::vector> - getTlsCertificates() const { - if (!config_->isReady()) { - ENVOY_LOG(warn, "SDS hasn't finished updating Ssl context config yet."); - stats_.downstream_context_secrets_not_ready_.inc(); - return {}; - } - return config_->tlsCertificates(); - } + std::pair, + std::shared_ptr> + getTlsCertificateAndKey(absl::string_view sni, bool* cert_matched_sni) const; bool earlyDataEnabled() const { return enable_early_data_; } protected: - void onSecretUpdated() override { stats_.context_config_update_by_sds_.inc(); } + void onSecretUpdated() override; private: + Envoy::Ssl::ContextManager& manager_; + Stats::Scope& stats_scope_; Ssl::ServerContextConfigPtr config_; + const std::vector server_names_; + mutable absl::Mutex ssl_ctx_mu_; + Envoy::Ssl::ServerContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); bool enable_early_data_; }; diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index 8758719e9990..1b7b0ab74c66 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -181,6 +181,7 @@ envoy_cc_library( "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", + "@com_github_google_quiche//:quic_core_crypto_proof_source_lib", ], ) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index cd87826018ae..c58d38768c5b 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -40,10 +40,6 @@ #include "openssl/rand.h" namespace Envoy { -namespace Extensions { -namespace TransportSockets { -namespace Tls { - namespace { bool cbsContainsU16(CBS& cbs, uint16_t n) { @@ -71,6 +67,10 @@ void logSslErrorChain() { } // namespace +namespace Extensions { +namespace TransportSockets { +namespace Tls { + int ContextImpl::sslExtendedSocketInfoIndex() { CONSTRUCT_ON_FIRST_USE(int, []() -> int { int ssl_context_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); @@ -80,7 +80,7 @@ int ContextImpl::sslExtendedSocketInfoIndex() { } ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, - TimeSource& time_source) + TimeSource& time_source, Ssl::ContextAdditionalInitFunc additional_init) : scope_(scope), stats_(generateSslStats(scope)), time_source_(time_source), tls_max_version_(config.maxProtocolVersion()), stat_name_set_(scope.symbolTable().makeSet("TransportSockets::Tls")), @@ -292,6 +292,10 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ctx.loadPrivateKey(tls_certificate.privateKey(), tls_certificate.privateKeyPath(), tls_certificate.password()); } + + if (additional_init != nullptr) { + additional_init(ctx, tls_certificate); + } } } @@ -372,7 +376,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c // As late as possible, run the custom SSL_CTX configuration callback on each // SSL_CTX, if set. if (auto sslctx_cb = config.sslctxCb(); sslctx_cb) { - for (TlsContext& ctx : tls_contexts_) { + for (Ssl::TlsContext& ctx : tls_contexts_) { sslctx_cb(ctx.ssl_ctx_.get()); } } @@ -654,7 +658,7 @@ std::vector ContextImpl::getCertChainInformat ClientContextImpl::ClientContextImpl(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, TimeSource& time_source) - : ContextImpl(scope, config, time_source), + : ContextImpl(scope, config, time_source, nullptr /* additional_init */), server_name_indication_(config.serverNameIndication()), allow_renegotiation_(config.allowRenegotiation()), enforce_rsa_key_usage_(config.enforceRsaKeyUsage()), @@ -783,8 +787,10 @@ int ClientContextImpl::newSessionKey(SSL_SESSION* session) { ServerContextImpl::ServerContextImpl(Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, const std::vector& server_names, - TimeSource& time_source) - : ContextImpl(scope, config, time_source), session_ticket_keys_(config.sessionTicketKeys()), + TimeSource& time_source, + Ssl::ContextAdditionalInitFunc additional_init) + : ContextImpl(scope, config, time_source, additional_init), + session_ticket_keys_(config.sessionTicketKeys()), ocsp_staple_policy_(config.ocspStaplePolicy()), full_scan_certs_on_sni_mismatch_(config.fullScanCertsOnSNIMismatch()) { if (config.tlsCertificates().empty() && !config.capabilities().provides_certificates) { @@ -889,7 +895,7 @@ ServerContextImpl::ServerContextImpl(Stats::Scope& scope, } } -void ServerContextImpl::populateServerNamesMap(TlsContext& ctx, int pkey_id) { +void ServerContextImpl::populateServerNamesMap(Ssl::TlsContext& ctx, int pkey_id) { if (ctx.cert_chain_ == nullptr) { return; } @@ -911,7 +917,7 @@ void ServerContextImpl::populateServerNamesMap(TlsContext& ctx, int pkey_id) { // implemented. return; } - sn_match->second.emplace(std::pair>(pkey_id, ctx)); + sn_match->second.emplace(std::pair>(pkey_id, ctx)); }; bssl::UniquePtr san_names(static_cast( @@ -1191,7 +1197,7 @@ bool ServerContextImpl::isClientOcspCapable(const SSL_CLIENT_HELLO* ssl_client_h return false; } -OcspStapleAction ServerContextImpl::ocspStapleAction(const TlsContext& ctx, +OcspStapleAction ServerContextImpl::ocspStapleAction(const Ssl::TlsContext& ctx, bool client_ocsp_capable) { if (!client_ocsp_capable) { return OcspStapleAction::ClientNotCapable; @@ -1233,20 +1239,22 @@ OcspStapleAction ServerContextImpl::ocspStapleAction(const TlsContext& ctx, PANIC_DUE_TO_CORRUPT_ENUM; } -enum ssl_select_cert_result_t -ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { - const bool client_ecdsa_capable = isClientEcdsaCapable(ssl_client_hello); - const bool client_ocsp_capable = isClientOcspCapable(ssl_client_hello); - absl::string_view sni = absl::NullSafeStringView( - SSL_get_servername(ssl_client_hello->ssl, TLSEXT_NAMETYPE_host_name)); +std::pair +ServerContextImpl::findTlsContext(absl::string_view sni, bool client_ecdsa_capable, + bool client_ocsp_capable, bool* cert_matched_sni) { + bool unused = false; + if (cert_matched_sni == nullptr) { + // Avoid need for nullptr checks when this is set. + cert_matched_sni = &unused; + } // selected_ctx represents the final selected certificate, it should meet all requirements or pick - // a candidate - const TlsContext* selected_ctx = nullptr; - const TlsContext* candidate_ctx = nullptr; + // a candidate. + const Ssl::TlsContext* selected_ctx = nullptr; + const Ssl::TlsContext* candidate_ctx = nullptr; OcspStapleAction ocsp_staple_action; - auto selected = [&](const TlsContext& ctx) -> bool { + auto selected = [&](const Ssl::TlsContext& ctx) -> bool { auto action = ocspStapleAction(ctx, client_ocsp_capable); if (action == OcspStapleAction::Fail) { // The selected ctx must adhere to OCSP policy @@ -1309,6 +1317,7 @@ ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { select_from_map(wildcard); } } + *cert_matched_sni = (selected_ctx != nullptr || candidate_ctx != nullptr); tail_select(full_scan_certs_on_sni_mismatch_); } // Full scan certs if SNI is not provided by client; @@ -1327,10 +1336,24 @@ ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { tail_select(false); } + ASSERT(selected_ctx != nullptr); + return {*selected_ctx, ocsp_staple_action}; +} + +enum ssl_select_cert_result_t +ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { + absl::string_view sni = absl::NullSafeStringView( + SSL_get_servername(ssl_client_hello->ssl, TLSEXT_NAMETYPE_host_name)); + const bool client_ecdsa_capable = isClientEcdsaCapable(ssl_client_hello); + const bool client_ocsp_capable = isClientOcspCapable(ssl_client_hello); + + auto [selected_ctx, ocsp_staple_action] = + findTlsContext(sni, client_ecdsa_capable, client_ocsp_capable, nullptr); + // Apply the selected context. This must be done before OCSP stapling below // since applying the context can remove the previously-set OCSP response. // This will only return NULL if memory allocation fails. - RELEASE_ASSERT(SSL_set_SSL_CTX(ssl_client_hello->ssl, selected_ctx->ssl_ctx_.get()) != nullptr, + RELEASE_ASSERT(SSL_set_SSL_CTX(ssl_client_hello->ssl, selected_ctx.ssl_ctx_.get()) != nullptr, ""); if (client_ocsp_capable) { @@ -1340,9 +1363,9 @@ ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { switch (ocsp_staple_action) { case OcspStapleAction::Staple: { // We avoid setting the OCSP response if the client didn't request it, but doing so is safe. - RELEASE_ASSERT(selected_ctx->ocsp_response_, + RELEASE_ASSERT(selected_ctx.ocsp_response_, "OCSP response must be present under OcspStapleAction::Staple"); - auto& resp_bytes = selected_ctx->ocsp_response_->rawBytes(); + auto& resp_bytes = selected_ctx.ocsp_response_->rawBytes(); int rc = SSL_set_ocsp_response(ssl_client_hello->ssl, resp_bytes.data(), resp_bytes.size()); RELEASE_ASSERT(rc != 0, ""); stats_.ocsp_staple_responses_.inc(); @@ -1360,6 +1383,31 @@ ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { return ssl_select_cert_success; } +ValidationResults ContextImpl::customVerifyCertChainForQuic( + STACK_OF(X509) & cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, + const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, + const CertValidator::ExtraValidationContext& validation_context, const std::string& host_name) { + ASSERT(!tls_contexts_.empty()); + // It doesn't matter which SSL context is used, because they share the same cert validation + // config. + SSL_CTX* ssl_ctx = tls_contexts_[0].ssl_ctx_.get(); + if (SSL_CTX_get_verify_mode(ssl_ctx) == SSL_VERIFY_NONE) { + // Skip validation if the TLS is configured SSL_VERIFY_NONE. + return {ValidationResults::ValidationStatus::Successful, + Envoy::Ssl::ClientValidationStatus::NotValidated, absl::nullopt, absl::nullopt}; + } + ValidationResults result = + cert_validator_->doVerifyCertChain(cert_chain, std::move(callback), transport_socket_options, + *ssl_ctx, validation_context, is_server, host_name); + return result; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions + +namespace Ssl { + bool TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t client_version) { const SSL_CIPHER* c = SSL_get_cipher_by_value(cipher_id); if (c == nullptr) { @@ -1380,25 +1428,6 @@ bool TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t client_version) { return false; } -ValidationResults ContextImpl::customVerifyCertChainForQuic( - STACK_OF(X509)& cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, - const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, - const CertValidator::ExtraValidationContext& validation_context, const std::string& host_name) { - ASSERT(!tls_contexts_.empty()); - // It doesn't matter which SSL context is used, because they share the same cert validation - // config. - SSL_CTX* ssl_ctx = tls_contexts_[0].ssl_ctx_.get(); - if (SSL_CTX_get_verify_mode(ssl_ctx) == SSL_VERIFY_NONE) { - // Skip validation if the TLS is configured SSL_VERIFY_NONE. - return {ValidationResults::ValidationStatus::Successful, - Envoy::Ssl::ClientValidationStatus::NotValidated, absl::nullopt, absl::nullopt}; - } - ValidationResults result = - cert_validator_->doVerifyCertChain(cert_chain, std::move(callback), transport_socket_options, - *ssl_ctx, validation_context, is_server, host_name); - return result; -} - void TlsContext::loadCertificateChain(const std::string& data, const std::string& data_path) { cert_chain_file_path_ = data_path; bssl::UniquePtr bio(BIO_new_mem_buf(const_cast(data.data()), data.size())); @@ -1441,9 +1470,9 @@ void TlsContext::loadPrivateKey(const std::string& data, const std::string& data !password.empty() ? const_cast(password.c_str()) : nullptr)); if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ssl_ctx_.get(), pkey.get())) { - throwEnvoyExceptionOrPanic(fmt::format("Failed to load private key from {}, Cause: {}", - data_path, - Utility::getLastCryptoError().value_or("unknown"))); + throwEnvoyExceptionOrPanic(fmt::format( + "Failed to load private key from {}, Cause: {}", data_path, + Extensions::TransportSockets::Tls::Utility::getLastCryptoError().value_or("unknown"))); } checkPrivateKey(pkey, data_path); @@ -1480,9 +1509,9 @@ void TlsContext::loadPkcs12(const std::string& data, const std::string& data_pat throwEnvoyExceptionOrPanic(absl::StrCat("Failed to load certificate from ", data_path)); } if (temp_private_key == nullptr || !SSL_CTX_use_PrivateKey(ssl_ctx_.get(), pkey.get())) { - throwEnvoyExceptionOrPanic(fmt::format("Failed to load private key from {}, Cause: {}", - data_path, - Utility::getLastCryptoError().value_or("unknown"))); + throwEnvoyExceptionOrPanic(fmt::format( + "Failed to load private key from {}, Cause: {}", data_path, + Extensions::TransportSockets::Tls::Utility::getLastCryptoError().value_or("unknown"))); } checkPrivateKey(pkey, data_path); @@ -1516,7 +1545,5 @@ void TlsContext::checkPrivateKey(const bssl::UniquePtr& pkey, #endif } -} // namespace Tls -} // namespace TransportSockets -} // namespace Extensions +} // namespace Ssl } // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 6f034afd6b35..2cc7d61f0393 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -28,14 +28,16 @@ #include "openssl/ssl.h" #include "openssl/x509v3.h" +#ifdef ENVOY_ENABLE_QUIC +#include "quiche/quic/core/crypto/proof_source.h" +#endif + namespace Envoy { #ifndef OPENSSL_IS_BORINGSSL #error Envoy requires BoringSSL #endif -namespace Extensions { -namespace TransportSockets { -namespace Tls { +namespace Ssl { struct TlsContext { // Each certificate specified for the context has its own SSL_CTX. `SSL_CTXs` @@ -45,11 +47,16 @@ struct TlsContext { bssl::UniquePtr ssl_ctx_; bssl::UniquePtr cert_chain_; std::string cert_chain_file_path_; - Ocsp::OcspResponseWrapperPtr ocsp_response_; + Extensions::TransportSockets::Tls::Ocsp::OcspResponseWrapperPtr ocsp_response_; bool is_ecdsa_{}; bool is_must_staple_{}; Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; +#ifdef ENVOY_ENABLE_QUIC + quiche::QuicheReferenceCountedPointer quic_cert_; + std::shared_ptr quic_private_key_; +#endif + std::string getCertChainFileName() const { return cert_chain_file_path_; }; bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { @@ -62,6 +69,11 @@ struct TlsContext { const std::string& password); void checkPrivateKey(const bssl::UniquePtr& pkey, const std::string& key_path); }; +} // namespace Ssl + +namespace Extensions { +namespace TransportSockets { +namespace Tls { class ContextImpl : public virtual Envoy::Ssl::Context, protected Logger::Loggable { @@ -93,7 +105,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context, // Validate cert asynchronously for a QUIC connection. ValidationResults customVerifyCertChainForQuic( - STACK_OF(X509)& cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, + STACK_OF(X509) & cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, const CertValidator::ExtraValidationContext& validation_context, const std::string& host_name); @@ -103,8 +115,8 @@ class ContextImpl : public virtual Envoy::Ssl::Context, protected: friend class ContextImplPeer; - ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, - TimeSource& time_source); + ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source, + Ssl::ContextAdditionalInitFunc additional_init); /** * The global SSL-library index used for storing a pointer to the context @@ -126,13 +138,13 @@ class ContextImpl : public virtual Envoy::Ssl::Context, Envoy::Ssl::SslExtendedSocketInfo* extended_socket_info, const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, SSL* ssl); - void populateServerNamesMap(TlsContext& ctx, const int pkey_id); + void populateServerNamesMap(Ssl::TlsContext& ctx, const int pkey_id); // This is always non-empty, with the first context used for all new SSL // objects. For server contexts, once we have ClientHello, we // potentially switch to a different CertificateContext based on certificate // selection. - std::vector tls_contexts_; + std::vector tls_contexts_; CertValidatorPtr cert_validator_; Stats::Scope& scope_; SslStats stats_; @@ -183,23 +195,31 @@ enum class OcspStapleAction { Staple, NoStaple, Fail, ClientNotCapable }; class ServerContextImpl : public ContextImpl, public Envoy::Ssl::ServerContext { public: ServerContextImpl(Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, - const std::vector& server_names, TimeSource& time_source); + const std::vector& server_names, TimeSource& time_source, + Ssl::ContextAdditionalInitFunc additional_init); // Select the TLS certificate context in SSL_CTX_set_select_certificate_cb() callback with // ClientHello details. This is made public for use by custom TLS extensions who want to // manually create and use this as a client hello callback. enum ssl_select_cert_result_t selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello); + // Finds the best matching context. The returned context will have the same lifetime as + // this ``ServerContextImpl``. + std::pair findTlsContext(absl::string_view sni, + bool client_ecdsa_capable, + bool client_ocsp_capable, + bool* cert_matched_sni); + private: // Currently, at most one certificate of a given key type may be specified for each exact // server name or wildcard domain name. - using PkeyTypesMap = absl::flat_hash_map>; + using PkeyTypesMap = absl::flat_hash_map>; // Both exact server names and wildcard domains are part of the same map, in which wildcard // domains are prefixed with "." (i.e. ".example.com" for "*.example.com") to differentiate // between exact and wildcard entries. using ServerNamesMap = absl::flat_hash_map; - void populateServerNamesMap(TlsContext& ctx, const int pkey_id); + void populateServerNamesMap(Ssl::TlsContext& ctx, const int pkey_id); using SessionContextID = std::array; @@ -209,7 +229,7 @@ class ServerContextImpl : public ContextImpl, public Envoy::Ssl::ServerContext { HMAC_CTX* hmac_ctx, int encrypt); bool isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello); bool isClientOcspCapable(const SSL_CLIENT_HELLO* ssl_client_hello); - OcspStapleAction ocspStapleAction(const TlsContext& ctx, bool client_ocsp_capable); + OcspStapleAction ocspStapleAction(const Ssl::TlsContext& ctx, bool client_ocsp_capable); SessionContextID generateHashForSessionContextId(const std::vector& server_names); diff --git a/source/extensions/transport_sockets/tls/context_manager_impl.cc b/source/extensions/transport_sockets/tls/context_manager_impl.cc index 4544ec7a1580..9bfdbadf27fd 100644 --- a/source/extensions/transport_sockets/tls/context_manager_impl.cc +++ b/source/extensions/transport_sockets/tls/context_manager_impl.cc @@ -30,16 +30,15 @@ ContextManagerImpl::createSslClientContext(Stats::Scope& scope, return context; } -Envoy::Ssl::ServerContextSharedPtr -ContextManagerImpl::createSslServerContext(Stats::Scope& scope, - const Envoy::Ssl::ServerContextConfig& config, - const std::vector& server_names) { +Envoy::Ssl::ServerContextSharedPtr ContextManagerImpl::createSslServerContext( + Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, + const std::vector& server_names, Ssl::ContextAdditionalInitFunc additional_init) { if (!config.isReady()) { return nullptr; } - Envoy::Ssl::ServerContextSharedPtr context = - std::make_shared(scope, config, server_names, time_source_); + Envoy::Ssl::ServerContextSharedPtr context = std::make_shared( + scope, config, server_names, time_source_, std::move(additional_init)); contexts_.insert(context); return context; } diff --git a/source/extensions/transport_sockets/tls/context_manager_impl.h b/source/extensions/transport_sockets/tls/context_manager_impl.h index d7386f1d1988..4142aa4fe82d 100644 --- a/source/extensions/transport_sockets/tls/context_manager_impl.h +++ b/source/extensions/transport_sockets/tls/context_manager_impl.h @@ -34,7 +34,8 @@ class ContextManagerImpl final : public Envoy::Ssl::ContextManager { const Envoy::Ssl::ClientContextConfig& config) override; Ssl::ServerContextSharedPtr createSslServerContext(Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, - const std::vector& server_names) override; + const std::vector& server_names, + Ssl::ContextAdditionalInitFunc additional_init) override; absl::optional daysUntilFirstCertExpires() const override; absl::optional secondsUntilFirstOcspResponseExpires() const override; void iterateContexts(std::function callback) override; diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index bc56bca6b417..c6196c7f21c3 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -429,7 +429,7 @@ ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPt const std::vector& server_names) : manager_(manager), stats_scope_(stats_scope), stats_(generateStats("server", stats_scope)), config_(std::move(config)), server_names_(server_names), - ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_)) { + ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr)) { config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); }); } @@ -463,7 +463,7 @@ bool ServerSslSocketFactory::implementsSecureTransport() const { return true; } void ServerSslSocketFactory::onAddOrUpdateSecret() { ENVOY_LOG(debug, "Secret is updated."); - auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_); + auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr); { absl::WriterMutexLock l(&ssl_ctx_mu_); std::swap(ctx, ssl_ctx_); diff --git a/source/server/ssl_context_manager.cc b/source/server/ssl_context_manager.cc index d3b8df9d17f5..8a9fec347dee 100644 --- a/source/server/ssl_context_manager.cc +++ b/source/server/ssl_context_manager.cc @@ -20,10 +20,9 @@ class SslContextManagerNoTlsStub final : public Envoy::Ssl::ContextManager { throwException(); } - Ssl::ServerContextSharedPtr - createSslServerContext(Stats::Scope& /* scope */, - const Envoy::Ssl::ServerContextConfig& /* config */, - const std::vector& /* server_names */) override { + Ssl::ServerContextSharedPtr createSslServerContext( + Stats::Scope& /* scope */, const Envoy::Ssl::ServerContextConfig& /* config */, + const std::vector& /* server_names */, Ssl::ContextAdditionalInitFunc) override { throwException(); } diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index a7e85351b41e..e33c2f856c05 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -28,7 +28,8 @@ class MockContextManager : public ContextManager { (Stats::Scope & scope, const ClientContextConfig& config)); MOCK_METHOD(ServerContextSharedPtr, createSslServerContext, (Stats::Scope & stats, const ServerContextConfig& config, - const std::vector& server_names)); + const std::vector& server_names, + ContextAdditionalInitFunc additional_init)); MOCK_METHOD(absl::optional, daysUntilFirstCertExpires, (), (const)); MOCK_METHOD(absl::optional, secondsUntilFirstOcspResponseExpires, (), (const)); MOCK_METHOD(void, iterateContexts, (std::function callback)); From c6e52915b1f1e005d6272d8fa429bfada97e6855 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 8 Feb 2024 08:50:26 -0800 Subject: [PATCH 02/26] comments and whitespace Signed-off-by: Greg Greenway --- source/common/quic/envoy_quic_proof_source.cc | 1 + source/common/quic/quic_server_transport_socket_factory.cc | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 7c656c63188f..fb34522250ac 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -34,6 +34,7 @@ void EnvoyQuicProofSource::signPayload( callback->Run(false, "", nullptr); return; } + // Verify the signature algorithm is as expected. std::string error_details; int sign_alg = diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index 86988fc27a45..fadb12ca3a76 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -100,9 +100,8 @@ std::pair, std::shared_ptr> QuicServerTransportSocketFactory::getTlsCertificateAndKey(absl::string_view sni, bool* cert_matched_sni) const { - // onSecretUpdated() could be invoked in the middle of checking the existence of ssl_ctx and - // creating SslSocket using ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and - // use the same ssl_ctx to create SslSocket. + // onSecretUpdated() could be invoked in the middle of checking the existence of , and using, + // ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and use the same ssl_ctx. Envoy::Ssl::ServerContextSharedPtr ssl_ctx; { absl::ReaderMutexLock l(&ssl_ctx_mu_); From 534667a742824bcc8cc2c7bc95b4594f2fe2234c Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 8 Feb 2024 08:50:42 -0800 Subject: [PATCH 03/26] wip: try to fix integration tests Signed-off-by: Greg Greenway --- test/integration/base_integration_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index bfae7ee7bbe9..499cc9011e1a 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -74,6 +74,7 @@ BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstrea })); ON_CALL(factory_context_.server_context_, api()).WillByDefault(ReturnRef(*api_)); ON_CALL(factory_context_, statsScope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + ON_CALL(factory_context_, sslContextManager()).WillByDefault(ReturnRef(context_manager_)); #ifndef ENVOY_ADMIN_FUNCTIONALITY config_helper_.addConfigModifier( From 05d5e6277a050417608bc1c8aa92e0f58ea86919 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 8 Feb 2024 12:51:00 -0800 Subject: [PATCH 04/26] fix quic handoff hotrestart test Signed-off-by: Greg Greenway --- test/integration/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/BUILD b/test/integration/BUILD index b29e7d22c976..6b2b2eb06e06 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -356,6 +356,7 @@ envoy_cc_test_binary( "//source/extensions/load_balancing_policies/random:config", "//source/extensions/load_balancing_policies/ring_hash:config", "//source/extensions/load_balancing_policies/round_robin:config", + "//source/extensions/transport_sockets/tls:config", ], ) From 34b39f6e8dd1ef5755ed8516976a59d4a3afcd19 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 8 Feb 2024 13:53:08 -0800 Subject: [PATCH 05/26] integration test fixes Signed-off-by: Greg Greenway --- test/integration/ssl_utility.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/ssl_utility.cc b/test/integration/ssl_utility.cc index 0c4ae2e6dfd8..af7f6f74baab 100644 --- a/test/integration/ssl_utility.cc +++ b/test/integration/ssl_utility.cc @@ -127,6 +127,9 @@ createUpstreamSslContext(ContextManager& context_manager, Api::Api& api, bool us } envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport quic_config; quic_config.mutable_downstream_tls_context()->MergeFrom(tls_context); + ON_CALL(mock_factory_ctx, statsScope()) + .WillByDefault(ReturnRef(*upstream_stats_store->rootScope())); + ON_CALL(mock_factory_ctx, sslContextManager()).WillByDefault(ReturnRef(context_manager)); std::vector server_names; auto& config_factory = Config::Utility::getAndCheckFactoryByName< From aa980d18ba277e47380957e181ffab6dd12c6267 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 8 Feb 2024 15:33:24 -0800 Subject: [PATCH 06/26] fix tls transport socket tests Signed-off-by: Greg Greenway --- .../tls/context_impl_test.cc | 21 +++++++++++-------- .../tls/handshaker_factory_test.cc | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 11a441051229..4d96fa7e5971 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -112,8 +112,8 @@ class SslContextImplTest : public SslCertsTest { }}; } void loadConfig(ServerContextConfigImpl& cfg) { - Envoy::Ssl::ServerContextSharedPtr server_ctx( - manager_.createSslServerContext(*store_.rootScope(), cfg, std::vector{})); + Envoy::Ssl::ServerContextSharedPtr server_ctx(manager_.createSslServerContext( + *store_.rootScope(), cfg, std::vector{}, nullptr)); auto cleanup = cleanUpHelper(server_ctx); } @@ -620,14 +620,15 @@ TEST_F(SslContextImplTest, MustHaveSubjectOrSAN) { TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); ServerContextConfigImpl server_context_config(tls_context, factory_context_); EXPECT_THROW_WITH_REGEX( - manager_.createSslServerContext(*store_.rootScope(), server_context_config, {}), + manager_.createSslServerContext(*store_.rootScope(), server_context_config, {}, nullptr), EnvoyException, "has neither subject CN nor SAN names"); } class SslServerContextImplOcspTest : public SslContextImplTest { public: Envoy::Ssl::ServerContextSharedPtr loadConfig(ServerContextConfigImpl& cfg) { - return manager_.createSslServerContext(*store_.rootScope(), cfg, std::vector{}); + return manager_.createSslServerContext(*store_.rootScope(), cfg, std::vector{}, + nullptr); } Envoy::Ssl::ServerContextSharedPtr loadConfigYaml(const std::string& yaml) { @@ -826,8 +827,8 @@ TEST_F(SslServerContextImplOcspTest, TestGetCertInformationWithOCSP) { class SslServerContextImplTicketTest : public SslContextImplTest { public: void loadConfig(ServerContextConfigImpl& cfg) { - Envoy::Ssl::ServerContextSharedPtr server_ctx( - manager_.createSslServerContext(*store_.rootScope(), cfg, std::vector{})); + Envoy::Ssl::ServerContextSharedPtr server_ctx(manager_.createSslServerContext( + *store_.rootScope(), cfg, std::vector{}, nullptr)); auto cleanup = cleanUpHelper(server_ctx); } @@ -1908,7 +1909,7 @@ TEST_F(ServerContextConfigImplTest, TlsCertificateNonEmpty) { Stats::IsolatedStoreImpl store; EXPECT_THROW_WITH_MESSAGE( Envoy::Ssl::ServerContextSharedPtr server_ctx(manager.createSslServerContext( - *store.rootScope(), client_context_config, std::vector{})), + *store.rootScope(), client_context_config, std::vector{}, nullptr)), EnvoyException, "Server TlsCertificates must have a certificate specified"); } @@ -2033,7 +2034,7 @@ TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureNoMethod) { ServerContextConfigImpl server_context_config(tls_context, factory_context_); EXPECT_THROW_WITH_MESSAGE( Envoy::Ssl::ServerContextSharedPtr server_ctx(manager.createSslServerContext( - *store.rootScope(), server_context_config, std::vector{})), + *store.rootScope(), server_context_config, std::vector{}, nullptr)), EnvoyException, "Failed to get BoringSSL private key method from provider"); } @@ -2201,13 +2202,15 @@ TEST_F(ServerContextConfigImplTest, Pkcs12LoadFailureBothPkcs12AndCertChain) { "Certificate configuration can't have both pkcs12 and certificate_chain"); } +// TODO: test throw from additional_init + // Subclass ContextImpl so we can instantiate directly from tests, despite the // constructor being protected. class TestContextImpl : public ContextImpl { public: TestContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source) - : ContextImpl(scope, config, time_source), pool_(scope.symbolTable()), + : ContextImpl(scope, config, time_source, nullptr), pool_(scope.symbolTable()), fallback_(pool_.add("fallback")) {} void incCounter(absl::string_view name, absl::string_view value) { diff --git a/test/extensions/transport_sockets/tls/handshaker_factory_test.cc b/test/extensions/transport_sockets/tls/handshaker_factory_test.cc index 64b5e9d0f05d..bbf7eb965e8b 100644 --- a/test/extensions/transport_sockets/tls/handshaker_factory_test.cc +++ b/test/extensions/transport_sockets/tls/handshaker_factory_test.cc @@ -296,7 +296,7 @@ TEST_F(HandshakerFactoryDownstreamTest, ServerHandshakerProvidesCertificates) { tls_context_, mock_factory_ctx); EXPECT_TRUE(server_context_config.isReady()); EXPECT_NO_THROW(context_manager_->createSslServerContext( - *stats_store_.rootScope(), server_context_config, std::vector{})); + *stats_store_.rootScope(), server_context_config, std::vector{}, nullptr)); } } // namespace From ba6799f9e74decd10c5ca316664efb8cdfae1c83 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 12 Feb 2024 13:15:11 -0800 Subject: [PATCH 07/26] fix quic tests Signed-off-by: Greg Greenway --- test/common/quic/active_quic_listener_test.cc | 4 +- .../common/quic/envoy_quic_dispatcher_test.cc | 4 +- .../quic/envoy_quic_proof_source_test.cc | 139 ++++++------------ test/mocks/ssl/mocks.cc | 14 +- test/mocks/ssl/mocks.h | 8 + 5 files changed, 70 insertions(+), 99 deletions(-) diff --git a/test/common/quic/active_quic_listener_test.cc b/test/common/quic/active_quic_listener_test.cc index 6c58ae781cf4..1ba2e8d21664 100644 --- a/test/common/quic/active_quic_listener_test.cc +++ b/test/common/quic/active_quic_listener_test.cc @@ -112,7 +112,8 @@ class ActiveQuicListenerTest : public testing::TestWithParam>()), + std::make_unique>(), + ssl_context_manager_, {}), quic_version_(quic::CurrentSupportedHttp3Versions()[0]), quic_stat_names_(listener_config_.listenerScope().symbolTable()) {} @@ -328,6 +329,7 @@ class ActiveQuicListenerTest : public testing::TestWithParam udp_listener_config_; NiceMock listener_config_; NiceMock udp_packet_writer_factory_; + NiceMock ssl_context_manager_; quic::QuicConfig quic_config_; Server::ConnectionHandlerImpl connection_handler_; std::unique_ptr quic_listener_; diff --git a/test/common/quic/envoy_quic_dispatcher_test.cc b/test/common/quic/envoy_quic_dispatcher_test.cc index e14c36d09d9b..fb016b442b86 100644 --- a/test/common/quic/envoy_quic_dispatcher_test.cc +++ b/test/common/quic/envoy_quic_dispatcher_test.cc @@ -77,7 +77,8 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam>()) { + std::make_unique>(), + ssl_context_manager_, {}) { auto writer = new testing::NiceMock(); envoy_quic_dispatcher_.InitializeWithWriter(writer); EXPECT_CALL(*writer, WritePacket(_, _, _, _, _, _)) @@ -252,6 +253,7 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam ssl_context_manager_; QuicServerTransportSocketFactory transport_socket_factory_; }; diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index 2c6bcc991cf8..e899ffe6b9d0 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -19,6 +19,7 @@ using testing::Invoke; using testing::Return; using testing::ReturnRef; +using testing::SaveArg; namespace Envoy { @@ -143,22 +144,34 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { EnvoyQuicProofSourceTest() : server_address_(quic::QuicIpAddress::Loopback4(), 12345), client_address_(quic::QuicIpAddress::Loopback4(), 54321), - mock_context_config_(new Ssl::MockServerContextConfig()), + mock_context_config_(new NiceMock()), listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), proof_source_(listen_socket_, filter_chain_manager_, listener_stats_, time_system_) { - EXPECT_CALL(*mock_context_config_, setSecretUpdateCallback(_)).Times(testing::AtLeast(1u)); + EXPECT_CALL(*mock_context_config_, setSecretUpdateCallback(_)) + .Times(testing::AtLeast(1u)) + .WillRepeatedly(SaveArg<0>(&secret_update_callback_)); EXPECT_CALL(*mock_context_config_, alpnProtocols()).WillRepeatedly(ReturnRef(alpn_)); transport_socket_factory_ = std::make_unique( true, listener_config_.listenerScope(), - std::unique_ptr(mock_context_config_)); + std::unique_ptr(mock_context_config_), ssl_context_manager_, + std::vector{}); transport_socket_factory_->initialize(); EXPECT_CALL(filter_chain_, name()).WillRepeatedly(Return("")); } - void expectCertChainAndPrivateKey(const std::string& cert, bool expect_private_key) { - EXPECT_CALL(listen_socket_, ioHandle()).Times(expect_private_key ? 2u : 1u); + void expectCertChainAndPrivateKey(const std::string& cert, bool expect_private_key, + bool expect_fail_to_load = false) { + int times = -1; + if (expect_fail_to_load) { + times = 0; + } else if (expect_private_key) { + times = 2; + } else { + times = 1; + } + EXPECT_CALL(listen_socket_, ioHandle()).Times(times); EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) .WillRepeatedly(Invoke( [&](const Network::ConnectionSocket& connection_socket, const StreamInfo::StreamInfo&) { @@ -177,13 +190,27 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { std::vector> tls_cert_configs{ std::reference_wrapper(tls_cert_config_)}; EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); - EXPECT_CALL(tls_cert_config_, certificateChain()).WillOnce(ReturnRef(cert)); + EXPECT_CALL(tls_cert_config_, pkcs12()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(tls_cert_config_, certificateChainPath()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(tls_cert_config_, privateKeyMethod()).WillRepeatedly(Return(nullptr)); + EXPECT_CALL(tls_cert_config_, privateKeyPath()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(tls_cert_config_, password()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + static const std::vector ocsp_staple; + EXPECT_CALL(tls_cert_config_, ocspStaple()).WillRepeatedly(ReturnRef(ocsp_staple)); + EXPECT_CALL(tls_cert_config_, certificateChain()) + .Times(testing::AtLeast(1)) + .WillRepeatedly(ReturnRef(cert)); if (expect_private_key) { - EXPECT_CALL(tls_cert_config_, privateKey()).WillOnce(ReturnRef(pkey_)); + EXPECT_CALL(tls_cert_config_, privateKey()) + .Times(testing::AtLeast(1)) + .WillRepeatedly(ReturnRef(pkey_)); } + ASSERT_TRUE(secret_update_callback_ != nullptr); + secret_update_callback_(); } protected: + Event::GlobalTimeSystem time_system_; std::string hostname_{"www.fake.com"}; quic::QuicSocketAddress server_address_; quic::QuicSocketAddress client_address_; @@ -196,11 +223,12 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { Network::MockFilterChainManager filter_chain_manager_; Network::MockListenSocket listen_socket_; testing::NiceMock listener_config_; + Extensions::TransportSockets::Tls::ContextManagerImpl ssl_context_manager_{time_system_}; Ssl::MockServerContextConfig* mock_context_config_; + std::function secret_update_callback_; std::unique_ptr transport_socket_factory_; Ssl::MockTlsCertificateConfig tls_cert_config_; Server::ListenerStats listener_stats_; - Event::GlobalTimeSystem time_system_; EnvoyQuicProofSource proof_source_; std::string alpn_{"h3"}; }; @@ -228,7 +256,7 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetCerChainAndSignatureAndVerify) { TEST_F(EnvoyQuicProofSourceTest, GetCertChainFailBadConfig) { // No filter chain. - EXPECT_CALL(listen_socket_, ioHandle()).Times(3); + EXPECT_CALL(listen_socket_, ioHandle()).Times(2); EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { return nullptr; @@ -244,27 +272,6 @@ TEST_F(EnvoyQuicProofSourceTest, GetCertChainFailBadConfig) { })); EXPECT_CALL(filter_chain_, transportSocketFactory()) .WillOnce(ReturnRef(*transport_socket_factory_)); - EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(false)); - EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, - &cert_matched_sni)); - - // No certs in config. - EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) - .WillRepeatedly(Invoke( - [&](const Network::ConnectionSocket& connection_socket, const StreamInfo::StreamInfo&) { - EXPECT_EQ(*quicAddressToEnvoyAddressInstance(server_address_), - *connection_socket.connectionInfoProvider().localAddress()); - EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), - *connection_socket.connectionInfoProvider().remoteAddress()); - EXPECT_EQ("quic", connection_socket.detectedTransportProtocol()); - EXPECT_EQ("h3", connection_socket.requestedApplicationProtocols()[0]); - return &filter_chain_; - })); - EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillOnce(ReturnRef(*transport_socket_factory_)); - EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); - std::vector> tls_cert_configs{}; - EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillOnce(Return(tls_cert_configs)); EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, &cert_matched_sni)); } @@ -273,10 +280,8 @@ TEST_F(EnvoyQuicProofSourceTest, GetCertChainFailInvalidCert) { std::string invalid_cert{R"(-----BEGIN CERTIFICATE----- invalid certificate -----END CERTIFICATE-----)"}; - expectCertChainAndPrivateKey(invalid_cert, false); - bool cert_matched_sni; - EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, - &cert_matched_sni)); + EXPECT_THROW_WITH_MESSAGE(expectCertChainAndPrivateKey(invalid_cert, false, true), EnvoyException, + "Failed to load certificate chain from "); } TEST_F(EnvoyQuicProofSourceTest, GetCertChainFailInvalidPublicKeyInCert) { @@ -300,10 +305,10 @@ f/lOd5zz2e7Tu2pUtx1sX1tlKph1D0ANpJwxRV78R2hjmynLSl7h4Ual9NMubqkD x96rVeUbRJ/qU4//nNM/XQa9vIAIcTZ0jFhmb0c3R4rmoqqC3vkSDwtaE5yuS5T4 GUy+n0vQNB0cXGzgcGI= -----END CERTIFICATE-----)"}; - expectCertChainAndPrivateKey(cert_with_rsa_1024, false); - bool cert_matched_sni; - EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, - &cert_matched_sni)); + EXPECT_THROW_WITH_MESSAGE(expectCertChainAndPrivateKey(cert_with_rsa_1024, false, true), + EnvoyException, + "Failed to load certificate chain from , only RSA certificates with " + "2048-bit or larger keys are supported"); } TEST_F(EnvoyQuicProofSourceTest, ComputeSignatureFailNoFilterChain) { @@ -319,63 +324,5 @@ TEST_F(EnvoyQuicProofSourceTest, ComputeSignatureFailNoFilterChain) { std::make_unique(false, filter_chain_, signature)); } -TEST_F(EnvoyQuicProofSourceTest, UnexpectedPrivateKey) { - EXPECT_CALL(listen_socket_, ioHandle()); - EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) - .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { - return &filter_chain_; - })); - EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(*transport_socket_factory_)); - - Ssl::MockTlsCertificateConfig tls_cert_config; - std::vector> tls_cert_configs{ - std::reference_wrapper(tls_cert_config)}; - EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); - EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); - std::string rsa_pkey_1024_len(R"(-----BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQC79hDq/OwN3ke3EF6Ntdi9R+VSrl9MStk992l1us8lZhq+e0zU -OlvxbUeZ8wyVkzs1gqI1it1IwF+EpdGhHhjggZjg040GD3HWSuyCzpHh+nLwJxtQ -D837PCg0zl+TnKv1YjY3I1F3trGhIqfd2B6pgaJ4hpr+0hdqnKP0Htd4DwIDAQAB -AoGASNypUD59Tx70k+1fifWNMEq3heacgJmfPxsyoXWqKSg8g8yOStLYo20mTXJf -VXg+go7CTJkpELOqE2SoL5nYMD0D/YIZCgDx85k0GWHdA6udNn4to95ZTeZPrBHx -T0QNQHnZI3A7RwLinO60IRY0NYzhkTEBxIuvIY6u0DVbrAECQQDpshbxK3DHc7Yi -Au7BUsxP8RbG4pP5IIVoD4YvJuwUkdrfrwejqTdkfchJJc+Gu/+h8vy7eASPHLLT -NBk5wFoPAkEAzeaKnx0CgNs0RX4+sSF727FroD98VUM38OFEJQ6U9OAWGvaKd8ey -yAYUjR2Sl5ZRyrwWv4IqyWgUGhZqNG0CAQJAPTjjm8DGpenhcB2WkNzxG4xMbEQV -gfGMIYvXmmi29liTn4AKH00IbvIo00jtih2cRcATh8VUZG2fR4dhiGik7wJAWSwS -NwzaS7IjtkERp6cHvELfiLxV/Zsp/BGjcKUbD96I1E6X834ySHyRo/f9x9bbP4Es -HO6j1yxTIGU6w8++AQJACdFPnRidOaj5oJmcZq0s6WGTYfegjTOKgi5KQzO0FTwG -qGm130brdD+1U1EJnEFmleLZ/W6mEi3MxcKpWOpTqQ== ------END RSA PRIVATE KEY-----)"); - EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(rsa_pkey_1024_len)); - std::string signature; - proof_source_.ComputeTlsSignature( - server_address_, client_address_, hostname_, SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", - std::make_unique(false, filter_chain_, signature)); -} - -TEST_F(EnvoyQuicProofSourceTest, InvalidPrivateKey) { - EXPECT_CALL(listen_socket_, ioHandle()); - EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) - .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { - return &filter_chain_; - })); - EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(*transport_socket_factory_)); - - Ssl::MockTlsCertificateConfig tls_cert_config; - std::vector> tls_cert_configs{ - std::reference_wrapper(tls_cert_config)}; - EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); - EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); - std::string invalid_pkey("abcdefg"); - EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(invalid_pkey)); - std::string signature; - proof_source_.ComputeTlsSignature( - server_address_, client_address_, hostname_, SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", - std::make_unique(false, filter_chain_, signature)); -} - } // namespace Quic } // namespace Envoy diff --git a/test/mocks/ssl/mocks.cc b/test/mocks/ssl/mocks.cc index fd274c0679d0..792686eaa9f3 100644 --- a/test/mocks/ssl/mocks.cc +++ b/test/mocks/ssl/mocks.cc @@ -27,7 +27,19 @@ MockClientContextConfig::MockClientContextConfig() { } MockClientContextConfig::~MockClientContextConfig() = default; -MockServerContextConfig::MockServerContextConfig() = default; +MockServerContextConfig::MockServerContextConfig() { + capabilities_.provides_ciphers_and_curves = true; + capabilities_.provides_sigalgs = true; + + ON_CALL(*this, cipherSuites()).WillByDefault(testing::ReturnRef(ciphers_)); + ON_CALL(*this, capabilities()).WillByDefault(testing::Return(capabilities_)); + ON_CALL(*this, alpnProtocols()).WillByDefault(testing::ReturnRef(alpn_)); + ON_CALL(*this, signatureAlgorithms()).WillByDefault(testing::ReturnRef(sigalgs_)); + ON_CALL(*this, sessionTicketKeys()).WillByDefault(testing::ReturnRef(ticket_keys_)); + ON_CALL(*this, tlsKeyLogLocal()).WillByDefault(testing::ReturnRef(iplist_)); + ON_CALL(*this, tlsKeyLogRemote()).WillByDefault(testing::ReturnRef(iplist_)); + ON_CALL(*this, tlsKeyLogPath()).WillByDefault(testing::ReturnRef(path_)); +} MockServerContextConfig::~MockServerContextConfig() = default; MockPrivateKeyMethodManager::MockPrivateKeyMethodManager() = default; diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index e33c2f856c05..9aa51fdd9658 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -149,6 +149,14 @@ class MockServerContextConfig : public ServerContextConfig { MOCK_METHOD(const std::string&, tlsKeyLogPath, (), (const)); MOCK_METHOD(AccessLog::AccessLogManager&, accessLogManager, (), (const)); MOCK_METHOD(bool, fullScanCertsOnSNIMismatch, (), (const)); + + Ssl::HandshakerCapabilities capabilities_; + std::string ciphers_{"RSA"}; + std::string alpn_{""}; + std::string sigalgs_{""}; + Network::Address::IpList iplist_; + std::string path_; + std::vector ticket_keys_; }; class MockTlsCertificateConfig : public TlsCertificateConfig { From 0e105cde196b6f21b5c61a09852b6559d9e7d1f9 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 12 Feb 2024 15:55:13 -0800 Subject: [PATCH 08/26] re-use already loaded certs Signed-off-by: Greg Greenway --- .../quic_server_transport_socket_factory.cc | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index fadb12ca3a76..13a9b32dfad8 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -34,9 +34,31 @@ QuicServerTransportSocketConfigFactory::createTransportSocketFactory( namespace { void initializeQuicCertAndKey(Ssl::TlsContext& context, - const Ssl::TlsCertificateConfig& cert_config) { - const std::string& chain_str = cert_config.certificateChain(); - std::stringstream pem_stream(chain_str); + const Ssl::TlsCertificateConfig& /*cert_config*/) { + // Convert the certificate chain loaded into the context into PEM, as that is what the QUICHE + // API expects. By using the version already loaded, instead of loading it from the source, + // we can reuse all the code that loads from different formats, allows using passwords on the key, + // etc. + std::string certs_str; + auto process_one_cert = [&](X509* cert) { + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + int result = PEM_write_bio_X509(bio.get(), cert); + ASSERT(result == 1); + BUF_MEM* buf_mem = nullptr; + result = BIO_get_mem_ptr(bio.get(), &buf_mem); + certs_str.append(buf_mem->data, buf_mem->length); + }; + + process_one_cert(SSL_CTX_get0_certificate(context.ssl_ctx_.get())); + + STACK_OF(X509)* chain_stack = nullptr; + int result = SSL_CTX_get0_chain_certs(context.ssl_ctx_.get(), &chain_stack); + ASSERT(result == 1); + for (size_t i = 0; i < sk_X509_num(chain_stack); i++) { + process_one_cert(sk_X509_value(chain_stack, i)); + } + + std::istringstream pem_stream(certs_str); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); quiche::QuicheReferenceCountedPointer cert_chain( @@ -47,7 +69,7 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, throwEnvoyExceptionOrPanic(absl::StrCat("Invalid leaf cert: ", error_details)); } - bssl::UniquePtr pub_key(X509_get_pubkey(cert.get())); + bssl::UniquePtr pub_key(X509_get_pubkey(context.cert_chain_.get())); int sign_alg = deduceSignatureAlgorithmFromPublicKey(pub_key.get(), &error_details); if (sign_alg == 0) { throwEnvoyExceptionOrPanic( @@ -56,10 +78,10 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, context.quic_cert_ = std::move(cert_chain); - const std::string& pkey = cert_config.privateKey(); - std::stringstream pem_str(pkey); + bssl::UniquePtr privateKey( + bssl::UpRef(SSL_CTX_get0_privatekey(context.ssl_ctx_.get()))); std::unique_ptr pem_key = - quic::CertificatePrivateKey::LoadPemFromStream(&pem_str); + std::make_unique(std::move(privateKey)); if (pem_key == nullptr) { throwEnvoyExceptionOrPanic("Failed to load QUIC private key."); } From e6bab0008ff8ed9f22ec6fe7738b38048b4cc7f4 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 12 Feb 2024 16:01:14 -0800 Subject: [PATCH 09/26] changelog Signed-off-by: Greg Greenway --- changelogs/current.yaml | 4 ++++ docs/root/intro/arch_overview/security/ssl.rst | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e08127197d53..dbba1fbeddd0 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -84,6 +84,10 @@ new_features: Added QUIC protocol option :ref:`send_disable_active_migration ` to make the server send clients a transport parameter to discourage client endpoints from active migration. +- area: quic + change: | + Added support for QUIC listeners to :ref:`choose certificates based on SNI ` and load certificates + from formats other than PEM, such as :ref:`pkcs12 `. - area: ext_proc change: | implemented diff --git a/docs/root/intro/arch_overview/security/ssl.rst b/docs/root/intro/arch_overview/security/ssl.rst index 8538b163f984..79c0da9d22f9 100644 --- a/docs/root/intro/arch_overview/security/ssl.rst +++ b/docs/root/intro/arch_overview/security/ssl.rst @@ -97,8 +97,6 @@ See the reference for :ref:`UpstreamTlsContexts `. +.. _arch_overview_ssl_cert_select: + Certificate selection --------------------- From 6d77bc112b532f15dc66c5b38d1e51f275d87843 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 12 Feb 2024 16:46:28 -0800 Subject: [PATCH 10/26] fix format Signed-off-by: Greg Greenway --- source/common/quic/quic_server_transport_socket_factory.cc | 2 +- source/extensions/transport_sockets/tls/BUILD | 2 +- source/extensions/transport_sockets/tls/context_impl.cc | 2 +- source/extensions/transport_sockets/tls/context_impl.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index 13a9b32dfad8..b1c77eef303c 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -4,8 +4,8 @@ #include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.validate.h" -#include "source/common/runtime/runtime_features.h" #include "source/common/quic/envoy_quic_utils.h" +#include "source/common/runtime/runtime_features.h" #include "source/extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index 1b7b0ab74c66..43362ca9a48d 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -179,9 +179,9 @@ envoy_cc_library( "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", "//source/extensions/transport_sockets/tls/ocsp:ocsp_lib", "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", + "@com_github_google_quiche//:quic_core_crypto_proof_source_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", - "@com_github_google_quiche//:quic_core_crypto_proof_source_lib", ], ) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index c58d38768c5b..f91ba26c174d 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -1384,7 +1384,7 @@ ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { } ValidationResults ContextImpl::customVerifyCertChainForQuic( - STACK_OF(X509) & cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, + STACK_OF(X509)& cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, const CertValidator::ExtraValidationContext& validation_context, const std::string& host_name) { ASSERT(!tls_contexts_.empty()); diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 2cc7d61f0393..28c41010ac7a 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -105,7 +105,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context, // Validate cert asynchronously for a QUIC connection. ValidationResults customVerifyCertChainForQuic( - STACK_OF(X509) & cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, + STACK_OF(X509)& cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, const CertValidator::ExtraValidationContext& validation_context, const std::string& host_name); From 176791c041bcf2582a5d5dcd01e461d2df1537d2 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 12 Feb 2024 17:05:35 -0800 Subject: [PATCH 11/26] fix spelling Signed-off-by: Greg Greenway --- source/common/quic/quic_server_transport_socket_factory.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index b1c77eef303c..dfdc084481dd 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -140,8 +140,8 @@ QuicServerTransportSocketFactory::getTlsCertificateAndKey(absl::string_view sni, sni, true /* TODO: ecdsa_capable */, false /* TODO: ocsp_capable */, cert_matched_sni); // Thread safety note: accessing the tls_context requires holding a shared_ptr to the ``ssl_ctx``. - // Both of these members are themselves refcounted, so it is safe to use them after ``ssl_ctx`` - // goes out of scope after the function returns. + // Both of these members are themselves reference counted, so it is safe to use them after + // ``ssl_ctx`` goes out of scope after the function returns. return {tls_context.quic_cert_, tls_context.quic_private_key_}; } From 5db400b8c8d3285eca0e83eb93131e1aa1c056ae Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 12 Feb 2024 17:19:24 -0800 Subject: [PATCH 12/26] remove some unneeded stuff Signed-off-by: Greg Greenway --- source/common/quic/quic_server_transport_socket_factory.cc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index dfdc084481dd..933f6161ea8d 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -60,15 +60,10 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, std::istringstream pem_stream(certs_str); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); - quiche::QuicheReferenceCountedPointer cert_chain( new quic::ProofSource::Chain(chain)); - std::string error_details; - bssl::UniquePtr cert = parseDERCertificate(cert_chain->certs[0], &error_details); - if (cert == nullptr) { - throwEnvoyExceptionOrPanic(absl::StrCat("Invalid leaf cert: ", error_details)); - } + std::string error_details; bssl::UniquePtr pub_key(X509_get_pubkey(context.cert_chain_.get())); int sign_alg = deduceSignatureAlgorithmFromPublicKey(pub_key.get(), &error_details); if (sign_alg == 0) { From f4ac1ae557ac1cf0915192d4350cbe486e63a089 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 13 Feb 2024 08:38:48 -0800 Subject: [PATCH 13/26] fix another test Signed-off-by: Greg Greenway --- test/server/ssl_context_manager_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/server/ssl_context_manager_test.cc b/test/server/ssl_context_manager_test.cc index 928fbb124b9b..dbe2bdc8db6e 100644 --- a/test/server/ssl_context_manager_test.cc +++ b/test/server/ssl_context_manager_test.cc @@ -28,8 +28,9 @@ TEST(SslContextManager, createStub) { EXPECT_EQ(manager->secondsUntilFirstOcspResponseExpires(), absl::nullopt); EXPECT_THROW_WITH_MESSAGE(manager->createSslClientContext(scope, client_config), EnvoyException, "SSL is not supported in this configuration"); - EXPECT_THROW_WITH_MESSAGE(manager->createSslServerContext(scope, server_config, server_names), - EnvoyException, "SSL is not supported in this configuration"); + EXPECT_THROW_WITH_MESSAGE( + manager->createSslServerContext(scope, server_config, server_names, nullptr), EnvoyException, + "SSL is not supported in this configuration"); EXPECT_NO_THROW(manager->iterateContexts([](const Envoy::Ssl::Context&) -> void {})); } From 16c8bbd4f9f9365779142be7c2a6369a693dac24 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 13 Feb 2024 09:09:23 -0800 Subject: [PATCH 14/26] fix test Signed-off-by: Greg Greenway --- .../listener_manager/listener_manager_impl_quic_only_test.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/common/listener_manager/listener_manager_impl_quic_only_test.cc b/test/common/listener_manager/listener_manager_impl_quic_only_test.cc index 67b334f46ddd..a1c6f9159de8 100644 --- a/test/common/listener_manager/listener_manager_impl_quic_only_test.cc +++ b/test/common/listener_manager/listener_manager_impl_quic_only_test.cc @@ -166,7 +166,9 @@ TEST_P(ListenerManagerImplQuicOnlyTest, QuicListenerFactoryAndSslContext) { auto& quic_socket_factory = dynamic_cast( filter_chain->transportSocketFactory()); EXPECT_TRUE(quic_socket_factory.implementsSecureTransport()); - EXPECT_FALSE(quic_socket_factory.getTlsCertificates().empty()); + auto [cert, key] = quic_socket_factory.getTlsCertificateAndKey("", nullptr); + EXPECT_TRUE(cert != nullptr); + EXPECT_TRUE(key != nullptr); EXPECT_TRUE(listener_factory_.socket_->socket_is_open_); // Stop listening shouldn't close the socket. From e7355bb2b013992f1bd4c750152dd764914ec89f Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 13 Feb 2024 11:05:53 -0800 Subject: [PATCH 15/26] fix compile-time-options Signed-off-by: Greg Greenway --- source/extensions/transport_sockets/tls/context_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 1836a638b22c..5a94bdfecc40 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -327,7 +327,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c // Use the SSL library to iterate over the configured ciphers. // // Note that if a negotiated cipher suite is outside of this set, we'll issue an ENVOY_BUG. - for (TlsContext& tls_context : tls_contexts_) { + for (Ssl::TlsContext& tls_context : tls_contexts_) { for (const SSL_CIPHER* cipher : SSL_CTX_get_ciphers(tls_context.ssl_ctx_.get())) { stat_name_set_->rememberBuiltin(SSL_CIPHER_get_name(cipher)); } From fac971959ae3ec0a91fc7c513133e2e3dcbc1cf5 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 14 Feb 2024 08:46:08 -0800 Subject: [PATCH 16/26] fix asan error Signed-off-by: Greg Greenway --- source/common/quic/quic_server_transport_socket_factory.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index 933f6161ea8d..f2a1bad8b64c 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -127,6 +127,7 @@ QuicServerTransportSocketFactory::getTlsCertificateAndKey(absl::string_view sni, if (!ssl_ctx) { ENVOY_LOG(warn, "SDS hasn't finished updating Ssl context config yet."); stats_.downstream_context_secrets_not_ready_.inc(); + *cert_matched_sni = false; return {}; } auto ctx = From 4c095a199ad3a3d8c5b06e99a68aa98a9224a2ee Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 14 Feb 2024 08:51:28 -0800 Subject: [PATCH 17/26] slightly lower coverage for quic Some paths can't be hit anymore because they're caught earlier when constructing the ContextImpl, but they should still remain in the code for sanity Signed-off-by: Greg Greenway --- test/per_file_coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 9759ec2c2658..feab99a60e67 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -16,7 +16,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/matcher:94.6" "source/common/network:94.4" # Flaky, `activateFileEvents`, `startSecureTransport` and `ioctl`, listener_socket do not always report LCOV "source/common/network/dns_resolver:91.4" # A few lines of MacOS code not tested in linux scripts. Tested in MacOS scripts -"source/common/quic:93.4" +"source/common/quic:93.3" "source/common/secret:95.1" "source/common/signal:87.2" # Death tests don't report LCOV "source/common/thread:0.0" # Death tests don't report LCOV From 0849a2ff42be0ee77aa2a20ed1d53c90012001df Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 14 Feb 2024 09:12:33 -0800 Subject: [PATCH 18/26] create chain more directly Signed-off-by: Greg Greenway --- .../quic/quic_server_transport_socket_factory.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index f2a1bad8b64c..403812a3feb3 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -39,14 +39,19 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, // API expects. By using the version already loaded, instead of loading it from the source, // we can reuse all the code that loads from different formats, allows using passwords on the key, // etc. - std::string certs_str; + std::vector chain; auto process_one_cert = [&](X509* cert) { bssl::UniquePtr bio(BIO_new(BIO_s_mem())); int result = PEM_write_bio_X509(bio.get(), cert); ASSERT(result == 1); BUF_MEM* buf_mem = nullptr; result = BIO_get_mem_ptr(bio.get(), &buf_mem); - certs_str.append(buf_mem->data, buf_mem->length); + std::string cert_str(buf_mem->data, buf_mem->length); + std::istringstream pem_stream(cert_str); + auto pem_result = quic::ReadNextPemMessage(&pem_stream); + RELEASE_ASSERT(pem_result.status == quic::PemReadResult::Status::kOk, + "Failed to read already loaded cert"); + chain.push_back(std::move(pem_result.contents)); }; process_one_cert(SSL_CTX_get0_certificate(context.ssl_ctx_.get())); @@ -58,8 +63,6 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, process_one_cert(sk_X509_value(chain_stack, i)); } - std::istringstream pem_stream(certs_str); - std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); quiche::QuicheReferenceCountedPointer cert_chain( new quic::ProofSource::Chain(chain)); From e9fe6753f5437b2bdc4a183a7d2ad97ca50d9efc Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 14 Feb 2024 15:09:41 -0800 Subject: [PATCH 19/26] asan Signed-off-by: Greg Greenway --- source/common/quic/envoy_quic_proof_source.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index fb34522250ac..1f93ec923972 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -18,6 +18,9 @@ quiche::QuicheReferenceCountedPointer EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { + // Ensure this is set even in error paths. + *cert_matched_sni = false; + CertWithFilterChain res = getTlsCertConfigAndFilterChain(server_address, client_address, hostname, cert_matched_sni); return res.cert_; From a43ec56a1c1f46f37edff120beab766a62490845 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 26 Feb 2024 17:06:54 -0800 Subject: [PATCH 20/26] runtime guard Signed-off-by: Greg Greenway --- changelogs/current.yaml | 1 + source/common/quic/envoy_quic_proof_source.cc | 128 ++++++++++++++++++ source/common/quic/envoy_quic_proof_source.h | 19 +++ .../quic_server_transport_socket_factory.cc | 27 ++-- .../quic_server_transport_socket_factory.h | 13 ++ source/common/runtime/runtime_features.cc | 1 + .../integration/quic_http_integration_test.cc | 6 + 7 files changed, 187 insertions(+), 8 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f6ab6f7364a3..c1ad57245949 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -131,6 +131,7 @@ new_features: change: | Added support for QUIC listeners to :ref:`choose certificates based on SNI ` and load certificates from formats other than PEM, such as :ref:`pkcs12 `. + This behavior can be disabled with runtime flag ``envoy.restart_features.quic_handle_certs_with_shared_tls_code``. - area: ext_proc change: | implemented diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 1f93ec923972..23314a23a82f 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -6,6 +6,7 @@ #include "source/common/quic/envoy_quic_utils.h" #include "source/common/quic/quic_io_handle_wrapper.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/stream_info/stream_info_impl.h" #include "openssl/bytestring.h" @@ -18,6 +19,11 @@ quiche::QuicheReferenceCountedPointer EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.restart_features.quic_handle_certs_with_shared_tls_code")) { + return legacyGetCertChain(server_address, client_address, hostname, cert_matched_sni); + } + // Ensure this is set even in error paths. *cert_matched_sni = false; @@ -26,10 +32,54 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address return res.cert_; } +quiche::QuicheReferenceCountedPointer +EnvoyQuicProofSource::legacyGetCertChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, bool* cert_matched_sni) { + *cert_matched_sni = false; + + LegacyCertConfigWithFilterChain res = + legacyGetTlsCertConfigAndFilterChain(server_address, client_address, hostname); + absl::optional> cert_config_ref = + res.cert_config_; + if (!cert_config_ref.has_value()) { + return nullptr; + } + auto& cert_config = cert_config_ref.value().get(); + const std::string& chain_str = cert_config.certificateChain(); + std::stringstream pem_stream(chain_str); + std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); + + quiche::QuicheReferenceCountedPointer cert_chain( + new quic::ProofSource::Chain(chain)); + std::string error_details; + bssl::UniquePtr cert = parseDERCertificate(cert_chain->certs[0], &error_details); + if (cert == nullptr) { + ENVOY_LOG(warn, absl::StrCat("Invalid leaf cert: ", error_details)); + return nullptr; + } + + bssl::UniquePtr pub_key(X509_get_pubkey(cert.get())); + int sign_alg = deduceSignatureAlgorithmFromPublicKey(pub_key.get(), &error_details); + if (sign_alg == 0) { + ENVOY_LOG(warn, absl::StrCat("Failed to deduce signature algorithm from public key: ", + error_details)); + return nullptr; + } + return cert_chain; +} + void EnvoyQuicProofSource::signPayload( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { + + if (!Runtime::runtimeFeatureEnabled( + "envoy.restart_features.quic_handle_certs_with_shared_tls_code")) { + return legacySignPayload(server_address, client_address, hostname, signature_algorithm, in, + std::move(callback)); + } + CertWithFilterChain res = getTlsCertConfigAndFilterChain(server_address, client_address, hostname, nullptr /* cert_matched_sni */); if (res.private_key_ == nullptr) { @@ -58,6 +108,49 @@ void EnvoyQuicProofSource::signPayload( std::make_unique(res.filter_chain_.value().get())); } +void EnvoyQuicProofSource::legacySignPayload( + const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, + const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, + std::unique_ptr callback) { + LegacyCertConfigWithFilterChain res = + legacyGetTlsCertConfigAndFilterChain(server_address, client_address, hostname); + absl::optional> cert_config_ref = + res.cert_config_; + if (!cert_config_ref.has_value()) { + ENVOY_LOG(warn, "No matching filter chain found for handshake."); + callback->Run(false, "", nullptr); + return; + } + auto& cert_config = cert_config_ref.value().get(); + // Load private key. + const std::string& pkey = cert_config.privateKey(); + std::stringstream pem_str(pkey); + std::unique_ptr pem_key = + quic::CertificatePrivateKey::LoadPemFromStream(&pem_str); + if (pem_key == nullptr) { + ENVOY_LOG(warn, "Failed to load private key."); + callback->Run(false, "", nullptr); + return; + } + // Verify the signature algorithm is as expected. + std::string error_details; + int sign_alg = deduceSignatureAlgorithmFromPublicKey(pem_key->private_key(), &error_details); + if (sign_alg != signature_algorithm) { + ENVOY_LOG(warn, + fmt::format("The signature algorithm {} from the private key is not expected: {}", + sign_alg, error_details)); + callback->Run(false, "", nullptr); + return; + } + + // Sign. + std::string sig = pem_key->Sign(in, signature_algorithm); + bool success = !sig.empty(); + ASSERT(res.filter_chain_.has_value()); + callback->Run(success, sig, + std::make_unique(res.filter_chain_.value().get())); +} + EnvoyQuicProofSource::CertWithFilterChain EnvoyQuicProofSource::getTlsCertConfigAndFilterChain( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { @@ -88,6 +181,41 @@ EnvoyQuicProofSource::CertWithFilterChain EnvoyQuicProofSource::getTlsCertConfig return {std::move(cert), std::move(key), *filter_chain}; } +EnvoyQuicProofSource::LegacyCertConfigWithFilterChain +EnvoyQuicProofSource::legacyGetTlsCertConfigAndFilterChain( + const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, + const std::string& hostname) { + ENVOY_LOG(trace, "Getting cert chain for {}", hostname); + // TODO(danzh) modify QUICHE to make quic session or ALPN accessible to avoid hard-coded ALPN. + Network::ConnectionSocketPtr connection_socket = createServerConnectionSocket( + listen_socket_.ioHandle(), server_address, client_address, hostname, "h3"); + StreamInfo::StreamInfoImpl info(time_source_, + connection_socket->connectionInfoProviderSharedPtr()); + const Network::FilterChain* filter_chain = + filter_chain_manager_->findFilterChain(*connection_socket, info); + + if (filter_chain == nullptr) { + listener_stats_.no_filter_chain_match_.inc(); + ENVOY_LOG(warn, "No matching filter chain found for handshake."); + return {absl::nullopt, absl::nullopt}; + } + ENVOY_LOG(trace, "Got a matching cert chain {}", filter_chain->name()); + + auto& transport_socket_factory = + dynamic_cast(filter_chain->transportSocketFactory()); + + std::vector> tls_cert_configs = + transport_socket_factory.legacyGetTlsCertificates(); + if (tls_cert_configs.empty()) { + ENVOY_LOG(warn, "No certificate is configured in transport socket config."); + return {absl::nullopt, absl::nullopt}; + } + // Only return the first TLS cert config. + // TODO(danzh) Choose based on supported cipher suites in TLS1.3 CHLO and prefer EC + // certs if supported. + return {tls_cert_configs[0].get(), *filter_chain}; +} + void EnvoyQuicProofSource::updateFilterChainManager( Network::FilterChainManager& filter_chain_manager) { filter_chain_manager_ = &filter_chain_manager; diff --git a/source/common/quic/envoy_quic_proof_source.h b/source/common/quic/envoy_quic_proof_source.h index c8fcb023dc8d..dc6204f63ba1 100644 --- a/source/common/quic/envoy_quic_proof_source.h +++ b/source/common/quic/envoy_quic_proof_source.h @@ -34,6 +34,15 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { std::unique_ptr callback) override; private: + quiche::QuicheReferenceCountedPointer + legacyGetCertChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, const std::string& hostname, + bool* cert_matched_sni); + void legacySignPayload(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, const std::string& hostname, + uint16_t signature_algorithm, absl::string_view in, + std::unique_ptr callback); + struct CertWithFilterChain { quiche::QuicheReferenceCountedPointer cert_; std::shared_ptr private_key_; @@ -45,6 +54,16 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { const std::string& hostname, bool* cert_matched_sni); + struct LegacyCertConfigWithFilterChain { + absl::optional> cert_config_; + absl::optional> filter_chain_; + }; + + LegacyCertConfigWithFilterChain + legacyGetTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname); + Network::Socket& listen_socket_; Network::FilterChainManager* filter_chain_manager_{nullptr}; Server::ListenerStats& listener_stats_; diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index 403812a3feb3..c3b4b54608c7 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -93,14 +93,22 @@ QuicServerTransportSocketFactory::QuicServerTransportSocketFactory( Envoy::Ssl::ContextManager& manager, const std::vector& server_names) : QuicTransportSocketFactoryBase(scope, "server"), manager_(manager), stats_scope_(scope), config_(std::move(config)), server_names_(server_names), - ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_, - initializeQuicCertAndKey)), + ssl_ctx_(Runtime::runtimeFeatureEnabled( + "envoy.restart_features.quic_handle_certs_with_shared_tls_code") + ? createSslServerContext() + : nullptr), enable_early_data_(enable_early_data) {} QuicServerTransportSocketFactory::~QuicServerTransportSocketFactory() { manager_.removeContext(ssl_ctx_); } +Envoy::Ssl::ServerContextSharedPtr +QuicServerTransportSocketFactory::createSslServerContext() const { + return manager_.createSslServerContext(stats_scope_, *config_, server_names_, + initializeQuicCertAndKey); +} + ProtobufTypes::MessagePtr QuicServerTransportSocketConfigFactory::createEmptyConfigProto() { return std::make_unique< envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport>(); @@ -146,13 +154,16 @@ QuicServerTransportSocketFactory::getTlsCertificateAndKey(absl::string_view sni, void QuicServerTransportSocketFactory::onSecretUpdated() { ENVOY_LOG(debug, "Secret is updated."); - auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_, - initializeQuicCertAndKey); - { - absl::WriterMutexLock l(&ssl_ctx_mu_); - std::swap(ctx, ssl_ctx_); + + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.quic_handle_certs_with_shared_tls_code")) { + auto ctx = createSslServerContext(); + { + absl::WriterMutexLock l(&ssl_ctx_mu_); + std::swap(ctx, ssl_ctx_); + } + manager_.removeContext(ctx); } - manager_.removeContext(ctx); stats_.context_config_update_by_sds_.inc(); } diff --git a/source/common/quic/quic_server_transport_socket_factory.h b/source/common/quic/quic_server_transport_socket_factory.h index 6c7819f0044c..18f774090c80 100644 --- a/source/common/quic/quic_server_transport_socket_factory.h +++ b/source/common/quic/quic_server_transport_socket_factory.h @@ -36,12 +36,25 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock std::shared_ptr> getTlsCertificateAndKey(absl::string_view sni, bool* cert_matched_sni) const; + // Return TLS certificates if the context config is ready. + std::vector> + legacyGetTlsCertificates() const { + if (!config_->isReady()) { + ENVOY_LOG(warn, "SDS hasn't finished updating Ssl context config yet."); + stats_.downstream_context_secrets_not_ready_.inc(); + return {}; + } + return config_->tlsCertificates(); + } + bool earlyDataEnabled() const { return enable_early_data_; } protected: void onSecretUpdated() override; private: + Envoy::Ssl::ServerContextSharedPtr createSslServerContext() const; + Envoy::Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; Ssl::ServerContextConfigPtr config_; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 19173d3b2cd5..9c2136358a2a 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -97,6 +97,7 @@ RUNTIME_GUARD(envoy_reloadable_features_use_http3_header_normalisation); RUNTIME_GUARD(envoy_reloadable_features_validate_connect); RUNTIME_GUARD(envoy_reloadable_features_validate_grpc_header_before_log_grpc_status); RUNTIME_GUARD(envoy_reloadable_features_validate_upstream_headers); +RUNTIME_GUARD(envoy_restart_features_quic_handle_certs_with_shared_tls_code); RUNTIME_GUARD(envoy_restart_features_send_goaway_for_premature_rst_streams); RUNTIME_GUARD(envoy_restart_features_udp_read_normalize_addresses); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index a4460d7f6add..01b28f9b9e25 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -693,6 +693,12 @@ TEST_P(QuicHttpIntegrationTest, EarlyDataDisabled) { codec_client_->close(); } +TEST_P(QuicHttpIntegrationTest, LegacyCertLoadingAndSelection) { + config_helper_.addRuntimeOverride("envoy.restart_features.quic_handle_certs_with_shared_tls_code", + "false"); + testMultipleQuicConnections(); +} + // Ensure multiple quic connections work, regardless of platform BPF support TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsDefaultMode) { testMultipleQuicConnections(); From 9b7ae243f849188103b5637c120b74b72f6e175c Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 27 Feb 2024 17:43:54 -0800 Subject: [PATCH 21/26] re-add unit tests for old code to improve coverage Signed-off-by: Greg Greenway --- test/common/quic/BUILD | 1 + .../quic/envoy_quic_proof_source_test.cc | 248 ++++++++++++++++++ 2 files changed, 249 insertions(+) diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index 0d9444d28b12..1b7b5b0095a5 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -65,6 +65,7 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:context_config_lib", "//test/mocks/network:network_mocks", "//test/mocks/ssl:ssl_mocks", + "//test/test_common:test_runtime_lib", "@com_github_google_quiche//:quic_core_versions_lib", "@com_github_google_quiche//:quic_test_tools_test_certificates_lib", ], diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index e899ffe6b9d0..7ef70682eadf 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -10,6 +10,7 @@ #include "test/common/quic/test_utils.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ssl/mocks.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -324,5 +325,252 @@ TEST_F(EnvoyQuicProofSourceTest, ComputeSignatureFailNoFilterChain) { std::make_unique(false, filter_chain_, signature)); } +// Test with `envoy.restart_features.quic_handle_certs_with_shared_tls_code` set to false. This +// has different expectations, so the old tests are kept here verbatim. +class LegacyEnvoyQuicProofSourceTest : public ::testing::Test { +public: + LegacyEnvoyQuicProofSourceTest() + : server_address_(quic::QuicIpAddress::Loopback4(), 12345), + client_address_(quic::QuicIpAddress::Loopback4(), 54321), + mock_context_config_(new Ssl::MockServerContextConfig()), + listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), + POOL_GAUGE(listener_config_.listenerScope()), + POOL_HISTOGRAM(listener_config_.listenerScope()))}), + proof_source_(listen_socket_, filter_chain_manager_, listener_stats_, time_system_) { + scoped_runtime_.mergeValues( + {{"envoy.restart_features.quic_handle_certs_with_shared_tls_code", "false"}}); + + EXPECT_CALL(*mock_context_config_, setSecretUpdateCallback(_)).Times(testing::AtLeast(1u)); + EXPECT_CALL(*mock_context_config_, alpnProtocols()).WillRepeatedly(ReturnRef(alpn_)); + transport_socket_factory_ = std::make_unique( + true, listener_config_.listenerScope(), + std::unique_ptr(mock_context_config_), ssl_context_manager_, + std::vector{}); + transport_socket_factory_->initialize(); + EXPECT_CALL(filter_chain_, name()).WillRepeatedly(Return("")); + } + + void expectCertChainAndPrivateKey(const std::string& cert, bool expect_private_key) { + EXPECT_CALL(listen_socket_, ioHandle()).Times(expect_private_key ? 2u : 1u); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillRepeatedly(Invoke( + [&](const Network::ConnectionSocket& connection_socket, const StreamInfo::StreamInfo&) { + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(server_address_), + *connection_socket.connectionInfoProvider().localAddress()); + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), + *connection_socket.connectionInfoProvider().remoteAddress()); + EXPECT_EQ("quic", connection_socket.detectedTransportProtocol()); + EXPECT_EQ("h3", connection_socket.requestedApplicationProtocols()[0]); + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + + EXPECT_CALL(*mock_context_config_, isReady()).WillRepeatedly(Return(true)); + std::vector> tls_cert_configs{ + std::reference_wrapper(tls_cert_config_)}; + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(tls_cert_config_, certificateChain()).WillOnce(ReturnRef(cert)); + if (expect_private_key) { + EXPECT_CALL(tls_cert_config_, privateKey()).WillOnce(ReturnRef(pkey_)); + } + } + +protected: + TestScopedRuntime scoped_runtime_; + Event::GlobalTimeSystem time_system_; + std::string hostname_{"www.fake.com"}; + quic::QuicSocketAddress server_address_; + quic::QuicSocketAddress client_address_; + quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; + absl::string_view chlo_hash_{"aaaaa"}; + std::string server_config_{"Server Config"}; + std::string expected_certs_{quic::test::kTestCertificateChainPem}; + std::string pkey_{quic::test::kTestCertificatePrivateKeyPem}; + Network::MockFilterChain filter_chain_; + Network::MockFilterChainManager filter_chain_manager_; + Network::MockListenSocket listen_socket_; + testing::NiceMock listener_config_; + Extensions::TransportSockets::Tls::ContextManagerImpl ssl_context_manager_{time_system_}; + Ssl::MockServerContextConfig* mock_context_config_; + std::unique_ptr transport_socket_factory_; + Ssl::MockTlsCertificateConfig tls_cert_config_; + Server::ListenerStats listener_stats_; + EnvoyQuicProofSource proof_source_; + std::string alpn_{"h3"}; +}; + +TEST_F(LegacyEnvoyQuicProofSourceTest, TestGetCerChainAndSignatureAndVerify) { + expectCertChainAndPrivateKey(expected_certs_, true); + bool cert_matched_sni; + quiche::QuicheReferenceCountedPointer chain = + proof_source_.GetCertChain(server_address_, client_address_, hostname_, &cert_matched_sni); + EXPECT_EQ(2, chain->certs.size()); + + std::string error_details; + bssl::UniquePtr cert = parseDERCertificate(chain->certs[0], &error_details); + EXPECT_NE(cert, nullptr); + bssl::UniquePtr pub_key(X509_get_pubkey(cert.get())); + int sign_alg = deduceSignatureAlgorithmFromPublicKey(pub_key.get(), &error_details); + EXPECT_EQ(sign_alg, SSL_SIGN_RSA_PSS_RSAE_SHA256); + std::string signature; + proof_source_.ComputeTlsSignature( + server_address_, client_address_, hostname_, SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", + std::make_unique(true, filter_chain_, signature)); + SignatureVerifier verifier; + verifier.verifyCertsAndSignature(chain, "payload", signature); +} + +TEST_F(LegacyEnvoyQuicProofSourceTest, GetCertChainFailBadConfig) { + // No filter chain. + EXPECT_CALL(listen_socket_, ioHandle()).Times(3); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { + return nullptr; + })); + bool cert_matched_sni; + EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, + &cert_matched_sni)); + + // Cert not ready. + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillOnce(ReturnRef(*transport_socket_factory_)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(false)); + EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, + &cert_matched_sni)); + + // No certs in config. + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillRepeatedly(Invoke( + [&](const Network::ConnectionSocket& connection_socket, const StreamInfo::StreamInfo&) { + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(server_address_), + *connection_socket.connectionInfoProvider().localAddress()); + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), + *connection_socket.connectionInfoProvider().remoteAddress()); + EXPECT_EQ("quic", connection_socket.detectedTransportProtocol()); + EXPECT_EQ("h3", connection_socket.requestedApplicationProtocols()[0]); + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillOnce(ReturnRef(*transport_socket_factory_)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); + std::vector> tls_cert_configs{}; + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillOnce(Return(tls_cert_configs)); + EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, + &cert_matched_sni)); +} + +TEST_F(LegacyEnvoyQuicProofSourceTest, GetCertChainFailInvalidCert) { + std::string invalid_cert{R"(-----BEGIN CERTIFICATE----- + invalid certificate + -----END CERTIFICATE-----)"}; + expectCertChainAndPrivateKey(invalid_cert, false); + bool cert_matched_sni; + EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, + &cert_matched_sni)); +} + +TEST_F(LegacyEnvoyQuicProofSourceTest, GetCertChainFailInvalidPublicKeyInCert) { + // This is a valid cert with RSA public key. But we don't support RSA key with + // length < 1024. + std::string cert_with_rsa_1024{R"(-----BEGIN CERTIFICATE----- +MIIC2jCCAkOgAwIBAgIUDBHEwlCvLGh3w0O8VwIW+CjYXY8wDQYJKoZIhvcNAQEL +BQAwfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMRIwEAYDVQQHDAlDYW1icmlk +Z2UxDzANBgNVBAoMBkdvb2dsZTEOMAwGA1UECwwFZW52b3kxDTALBgNVBAMMBHRl +c3QxHzAdBgkqhkiG9w0BCQEWEGRhbnpoQGdvb2dsZS5jb20wHhcNMjAwODA0MTg1 +OTQ4WhcNMjEwODA0MTg1OTQ4WjB/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTUEx +EjAQBgNVBAcMCUNhbWJyaWRnZTEPMA0GA1UECgwGR29vZ2xlMQ4wDAYDVQQLDAVl +bnZveTENMAsGA1UEAwwEdGVzdDEfMB0GCSqGSIb3DQEJARYQZGFuemhAZ29vZ2xl +LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAykCZNjxws+sNfnp18nsp ++7LN81J/RSwAHLkGnwEtd3OxSUuiCYHgYlyuEAwJdf99+SaFrgcA4LvYJ/Mhm/fZ +msnpfsAvoQ49+ax0fm1x56ii4KgNiu9iFsWwwVmkHkgjlRcRsmhr4WeIf14Yvpqs +JNsbNVSCZ4GLQ2V6BqIHlhcCAwEAAaNTMFEwHQYDVR0OBBYEFDO1KPYcdRmeKDvL +H2Yzj8el2Xe1MB8GA1UdIwQYMBaAFDO1KPYcdRmeKDvLH2Yzj8el2Xe1MA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAnwWVmwSK9TDml7oHGBavzOC1 +f/lOd5zz2e7Tu2pUtx1sX1tlKph1D0ANpJwxRV78R2hjmynLSl7h4Ual9NMubqkD +x96rVeUbRJ/qU4//nNM/XQa9vIAIcTZ0jFhmb0c3R4rmoqqC3vkSDwtaE5yuS5T4 +GUy+n0vQNB0cXGzgcGI= +-----END CERTIFICATE-----)"}; + expectCertChainAndPrivateKey(cert_with_rsa_1024, false); + bool cert_matched_sni; + EXPECT_EQ(nullptr, proof_source_.GetCertChain(server_address_, client_address_, hostname_, + &cert_matched_sni)); +} + +TEST_F(LegacyEnvoyQuicProofSourceTest, ComputeSignatureFailNoFilterChain) { + EXPECT_CALL(listen_socket_, ioHandle()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { + return nullptr; + })); + + std::string signature; + proof_source_.ComputeTlsSignature( + server_address_, client_address_, hostname_, SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", + std::make_unique(false, filter_chain_, signature)); +} + +TEST_F(LegacyEnvoyQuicProofSourceTest, UnexpectedPrivateKey) { + EXPECT_CALL(listen_socket_, ioHandle()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + + Ssl::MockTlsCertificateConfig tls_cert_config; + std::vector> tls_cert_configs{ + std::reference_wrapper(tls_cert_config)}; + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); + std::string rsa_pkey_1024_len(R"(-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQC79hDq/OwN3ke3EF6Ntdi9R+VSrl9MStk992l1us8lZhq+e0zU +OlvxbUeZ8wyVkzs1gqI1it1IwF+EpdGhHhjggZjg040GD3HWSuyCzpHh+nLwJxtQ +D837PCg0zl+TnKv1YjY3I1F3trGhIqfd2B6pgaJ4hpr+0hdqnKP0Htd4DwIDAQAB +AoGASNypUD59Tx70k+1fifWNMEq3heacgJmfPxsyoXWqKSg8g8yOStLYo20mTXJf +VXg+go7CTJkpELOqE2SoL5nYMD0D/YIZCgDx85k0GWHdA6udNn4to95ZTeZPrBHx +T0QNQHnZI3A7RwLinO60IRY0NYzhkTEBxIuvIY6u0DVbrAECQQDpshbxK3DHc7Yi +Au7BUsxP8RbG4pP5IIVoD4YvJuwUkdrfrwejqTdkfchJJc+Gu/+h8vy7eASPHLLT +NBk5wFoPAkEAzeaKnx0CgNs0RX4+sSF727FroD98VUM38OFEJQ6U9OAWGvaKd8ey +yAYUjR2Sl5ZRyrwWv4IqyWgUGhZqNG0CAQJAPTjjm8DGpenhcB2WkNzxG4xMbEQV +gfGMIYvXmmi29liTn4AKH00IbvIo00jtih2cRcATh8VUZG2fR4dhiGik7wJAWSwS +NwzaS7IjtkERp6cHvELfiLxV/Zsp/BGjcKUbD96I1E6X834ySHyRo/f9x9bbP4Es +HO6j1yxTIGU6w8++AQJACdFPnRidOaj5oJmcZq0s6WGTYfegjTOKgi5KQzO0FTwG +qGm130brdD+1U1EJnEFmleLZ/W6mEi3MxcKpWOpTqQ== +-----END RSA PRIVATE KEY-----)"); + EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(rsa_pkey_1024_len)); + std::string signature; + proof_source_.ComputeTlsSignature( + server_address_, client_address_, hostname_, SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", + std::make_unique(false, filter_chain_, signature)); +} + +TEST_F(LegacyEnvoyQuicProofSourceTest, InvalidPrivateKey) { + EXPECT_CALL(listen_socket_, ioHandle()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + + Ssl::MockTlsCertificateConfig tls_cert_config; + std::vector> tls_cert_configs{ + std::reference_wrapper(tls_cert_config)}; + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); + std::string invalid_pkey("abcdefg"); + EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(invalid_pkey)); + std::string signature; + proof_source_.ComputeTlsSignature( + server_address_, client_address_, hostname_, SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", + std::make_unique(false, filter_chain_, signature)); +} + } // namespace Quic } // namespace Envoy From 1beac839fa6a49a24b9edee6560456e461a8b022 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 28 Feb 2024 08:38:21 -0800 Subject: [PATCH 22/26] latch runtime value Signed-off-by: Greg Greenway --- .../quic/quic_server_transport_socket_factory.cc | 15 +++++++-------- .../quic/quic_server_transport_socket_factory.h | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index c3b4b54608c7..c0a56ca679a4 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -91,12 +91,12 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, QuicServerTransportSocketFactory::QuicServerTransportSocketFactory( bool enable_early_data, Stats::Scope& scope, Ssl::ServerContextConfigPtr config, Envoy::Ssl::ContextManager& manager, const std::vector& server_names) - : QuicTransportSocketFactoryBase(scope, "server"), manager_(manager), stats_scope_(scope), - config_(std::move(config)), server_names_(server_names), - ssl_ctx_(Runtime::runtimeFeatureEnabled( - "envoy.restart_features.quic_handle_certs_with_shared_tls_code") - ? createSslServerContext() - : nullptr), + : QuicTransportSocketFactoryBase(scope, "server"), + handle_certs_with_shared_tls_code_(Runtime::runtimeFeatureEnabled( + "envoy.restart_features.quic_handle_certs_with_shared_tls_code")), + manager_(manager), stats_scope_(scope), config_(std::move(config)), + server_names_(server_names), + ssl_ctx_(handle_certs_with_shared_tls_code_ ? createSslServerContext() : nullptr), enable_early_data_(enable_early_data) {} QuicServerTransportSocketFactory::~QuicServerTransportSocketFactory() { @@ -155,8 +155,7 @@ QuicServerTransportSocketFactory::getTlsCertificateAndKey(absl::string_view sni, void QuicServerTransportSocketFactory::onSecretUpdated() { ENVOY_LOG(debug, "Secret is updated."); - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.quic_handle_certs_with_shared_tls_code")) { + if (handle_certs_with_shared_tls_code_) { auto ctx = createSslServerContext(); { absl::WriterMutexLock l(&ssl_ctx_mu_); diff --git a/source/common/quic/quic_server_transport_socket_factory.h b/source/common/quic/quic_server_transport_socket_factory.h index 18f774090c80..4982222597f4 100644 --- a/source/common/quic/quic_server_transport_socket_factory.h +++ b/source/common/quic/quic_server_transport_socket_factory.h @@ -55,6 +55,7 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock private: Envoy::Ssl::ServerContextSharedPtr createSslServerContext() const; + const bool handle_certs_with_shared_tls_code_; Envoy::Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; Ssl::ServerContextConfigPtr config_; From 031cfa4e3f7cb79d18c3a2892109edc6b6759918 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 28 Feb 2024 09:12:03 -0800 Subject: [PATCH 23/26] rename function Signed-off-by: Greg Greenway --- source/common/quic/envoy_quic_proof_source.cc | 8 ++++---- source/common/quic/envoy_quic_proof_source.h | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 23314a23a82f..4e466690de9f 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -28,7 +28,7 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address *cert_matched_sni = false; CertWithFilterChain res = - getTlsCertConfigAndFilterChain(server_address, client_address, hostname, cert_matched_sni); + getTlsCertAndFilterChain(server_address, client_address, hostname, cert_matched_sni); return res.cert_; } @@ -80,8 +80,8 @@ void EnvoyQuicProofSource::signPayload( std::move(callback)); } - CertWithFilterChain res = getTlsCertConfigAndFilterChain(server_address, client_address, hostname, - nullptr /* cert_matched_sni */); + CertWithFilterChain res = getTlsCertAndFilterChain(server_address, client_address, hostname, + nullptr /* cert_matched_sni */); if (res.private_key_ == nullptr) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); callback->Run(false, "", nullptr); @@ -151,7 +151,7 @@ void EnvoyQuicProofSource::legacySignPayload( std::make_unique(res.filter_chain_.value().get())); } -EnvoyQuicProofSource::CertWithFilterChain EnvoyQuicProofSource::getTlsCertConfigAndFilterChain( +EnvoyQuicProofSource::CertWithFilterChain EnvoyQuicProofSource::getTlsCertAndFilterChain( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { ENVOY_LOG(trace, "Getting cert chain for {}", hostname); diff --git a/source/common/quic/envoy_quic_proof_source.h b/source/common/quic/envoy_quic_proof_source.h index dc6204f63ba1..3d3f74231a2f 100644 --- a/source/common/quic/envoy_quic_proof_source.h +++ b/source/common/quic/envoy_quic_proof_source.h @@ -49,10 +49,9 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { absl::optional> filter_chain_; }; - CertWithFilterChain getTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname, - bool* cert_matched_sni); + CertWithFilterChain getTlsCertAndFilterChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, bool* cert_matched_sni); struct LegacyCertConfigWithFilterChain { absl::optional> cert_config_; From 5c1c95da6832cca634e2963044ea2e510522e7c8 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 4 Mar 2024 13:11:33 -0800 Subject: [PATCH 24/26] switch from assert to exception Signed-off-by: Greg Greenway --- source/common/quic/quic_server_transport_socket_factory.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index c0a56ca679a4..a08870baee54 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -49,8 +49,10 @@ void initializeQuicCertAndKey(Ssl::TlsContext& context, std::string cert_str(buf_mem->data, buf_mem->length); std::istringstream pem_stream(cert_str); auto pem_result = quic::ReadNextPemMessage(&pem_stream); - RELEASE_ASSERT(pem_result.status == quic::PemReadResult::Status::kOk, - "Failed to read already loaded cert"); + if (pem_result.status != quic::PemReadResult::Status::kOk) { + throwEnvoyExceptionOrPanic( + "Error loading certificate in QUIC context: error from ReadNextPemMessage"); + } chain.push_back(std::move(pem_result.contents)); }; From 92f49f5f15de16de7503ab9b74b5b0759c726c3f Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 4 Mar 2024 14:37:26 -0800 Subject: [PATCH 25/26] factor out common code; use latched runtime value Signed-off-by: Greg Greenway --- source/common/quic/envoy_quic_proof_source.cc | 111 ++++++++---------- source/common/quic/envoy_quic_proof_source.h | 24 ++-- .../quic_server_transport_socket_factory.h | 2 + 3 files changed, 66 insertions(+), 71 deletions(-) diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 4e466690de9f..9670dba204e3 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -19,27 +19,25 @@ quiche::QuicheReferenceCountedPointer EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { - if (!Runtime::runtimeFeatureEnabled( - "envoy.restart_features.quic_handle_certs_with_shared_tls_code")) { - return legacyGetCertChain(server_address, client_address, hostname, cert_matched_sni); - } // Ensure this is set even in error paths. *cert_matched_sni = false; - CertWithFilterChain res = - getTlsCertAndFilterChain(server_address, client_address, hostname, cert_matched_sni); - return res.cert_; + auto res = getTransportSocketAndFilterChain(server_address, client_address, hostname); + if (!res.has_value()) { + return nullptr; + } + + if (!res->transport_socket_factory_.handleCertsWithSharedTlsCode()) { + return legacyGetCertChain(*res); + } + + return getTlsCertAndFilterChain(*res, hostname, cert_matched_sni).cert_; } quiche::QuicheReferenceCountedPointer -EnvoyQuicProofSource::legacyGetCertChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname, bool* cert_matched_sni) { - *cert_matched_sni = false; - - LegacyCertConfigWithFilterChain res = - legacyGetTlsCertConfigAndFilterChain(server_address, client_address, hostname); +EnvoyQuicProofSource::legacyGetCertChain(const TransportSocketFactoryWithFilterChain& data) { + LegacyCertConfigWithFilterChain res = legacyGetTlsCertConfigAndFilterChain(data); absl::optional> cert_config_ref = res.cert_config_; if (!cert_config_ref.has_value()) { @@ -73,15 +71,19 @@ void EnvoyQuicProofSource::signPayload( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { + auto data = getTransportSocketAndFilterChain(server_address, client_address, hostname); + if (!data.has_value()) { + ENVOY_LOG(warn, "No matching filter chain found for handshake."); + callback->Run(false, "", nullptr); + return; + } - if (!Runtime::runtimeFeatureEnabled( - "envoy.restart_features.quic_handle_certs_with_shared_tls_code")) { - return legacySignPayload(server_address, client_address, hostname, signature_algorithm, in, - std::move(callback)); + if (!data->transport_socket_factory_.handleCertsWithSharedTlsCode()) { + return legacySignPayload(*data, signature_algorithm, in, std::move(callback)); } - CertWithFilterChain res = getTlsCertAndFilterChain(server_address, client_address, hostname, - nullptr /* cert_matched_sni */); + CertWithFilterChain res = + getTlsCertAndFilterChain(*data, hostname, nullptr /* cert_matched_sni */); if (res.private_key_ == nullptr) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); callback->Run(false, "", nullptr); @@ -109,11 +111,9 @@ void EnvoyQuicProofSource::signPayload( } void EnvoyQuicProofSource::legacySignPayload( - const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, - const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, - std::unique_ptr callback) { - LegacyCertConfigWithFilterChain res = - legacyGetTlsCertConfigAndFilterChain(server_address, client_address, hostname); + const TransportSocketFactoryWithFilterChain& data, uint16_t signature_algorithm, + absl::string_view in, std::unique_ptr callback) { + LegacyCertConfigWithFilterChain res = legacyGetTlsCertConfigAndFilterChain(data); absl::optional> cert_config_ref = res.cert_config_; if (!cert_config_ref.has_value()) { @@ -151,38 +151,37 @@ void EnvoyQuicProofSource::legacySignPayload( std::make_unique(res.filter_chain_.value().get())); } -EnvoyQuicProofSource::CertWithFilterChain EnvoyQuicProofSource::getTlsCertAndFilterChain( - const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, - const std::string& hostname, bool* cert_matched_sni) { - ENVOY_LOG(trace, "Getting cert chain for {}", hostname); - // TODO(danzh) modify QUICHE to make quic session or ALPN accessible to avoid hard-coded ALPN. - Network::ConnectionSocketPtr connection_socket = createServerConnectionSocket( - listen_socket_.ioHandle(), server_address, client_address, hostname, "h3"); - StreamInfo::StreamInfoImpl info(time_source_, - connection_socket->connectionInfoProviderSharedPtr()); - const Network::FilterChain* filter_chain = - filter_chain_manager_->findFilterChain(*connection_socket, info); - - if (filter_chain == nullptr) { - listener_stats_.no_filter_chain_match_.inc(); - ENVOY_LOG(warn, "No matching filter chain found for handshake."); - return {}; - } - ENVOY_LOG(trace, "Got a matching cert chain {}", filter_chain->name()); - - auto& transport_socket_factory = - dynamic_cast(filter_chain->transportSocketFactory()); - - auto [cert, key] = transport_socket_factory.getTlsCertificateAndKey(hostname, cert_matched_sni); +EnvoyQuicProofSource::CertWithFilterChain +EnvoyQuicProofSource::getTlsCertAndFilterChain(const TransportSocketFactoryWithFilterChain& data, + const std::string& hostname, + bool* cert_matched_sni) { + auto [cert, key] = + data.transport_socket_factory_.getTlsCertificateAndKey(hostname, cert_matched_sni); if (cert == nullptr || key == nullptr) { ENVOY_LOG(warn, "No certificate is configured in transport socket config."); return {}; } - return {std::move(cert), std::move(key), *filter_chain}; + return {std::move(cert), std::move(key), data.filter_chain_}; } EnvoyQuicProofSource::LegacyCertConfigWithFilterChain EnvoyQuicProofSource::legacyGetTlsCertConfigAndFilterChain( + const TransportSocketFactoryWithFilterChain& data) { + + std::vector> tls_cert_configs = + data.transport_socket_factory_.legacyGetTlsCertificates(); + if (tls_cert_configs.empty()) { + ENVOY_LOG(warn, "No certificate is configured in transport socket config."); + return {absl::nullopt, absl::nullopt}; + } + // Only return the first TLS cert config. + // TODO(danzh) Choose based on supported cipher suites in TLS1.3 CHLO and prefer EC + // certs if supported. + return {tls_cert_configs[0].get(), data.filter_chain_}; +} + +absl::optional +EnvoyQuicProofSource::getTransportSocketAndFilterChain( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname) { ENVOY_LOG(trace, "Getting cert chain for {}", hostname); @@ -197,23 +196,13 @@ EnvoyQuicProofSource::legacyGetTlsCertConfigAndFilterChain( if (filter_chain == nullptr) { listener_stats_.no_filter_chain_match_.inc(); ENVOY_LOG(warn, "No matching filter chain found for handshake."); - return {absl::nullopt, absl::nullopt}; + return {}; } ENVOY_LOG(trace, "Got a matching cert chain {}", filter_chain->name()); auto& transport_socket_factory = dynamic_cast(filter_chain->transportSocketFactory()); - - std::vector> tls_cert_configs = - transport_socket_factory.legacyGetTlsCertificates(); - if (tls_cert_configs.empty()) { - ENVOY_LOG(warn, "No certificate is configured in transport socket config."); - return {absl::nullopt, absl::nullopt}; - } - // Only return the first TLS cert config. - // TODO(danzh) Choose based on supported cipher suites in TLS1.3 CHLO and prefer EC - // certs if supported. - return {tls_cert_configs[0].get(), *filter_chain}; + return TransportSocketFactoryWithFilterChain{transport_socket_factory, *filter_chain}; } void EnvoyQuicProofSource::updateFilterChainManager( diff --git a/source/common/quic/envoy_quic_proof_source.h b/source/common/quic/envoy_quic_proof_source.h index 3d3f74231a2f..e950b982445b 100644 --- a/source/common/quic/envoy_quic_proof_source.h +++ b/source/common/quic/envoy_quic_proof_source.h @@ -34,12 +34,14 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { std::unique_ptr callback) override; private: + struct TransportSocketFactoryWithFilterChain { + const QuicServerTransportSocketFactory& transport_socket_factory_; + const Network::FilterChain& filter_chain_; + }; + quiche::QuicheReferenceCountedPointer - legacyGetCertChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, const std::string& hostname, - bool* cert_matched_sni); - void legacySignPayload(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, const std::string& hostname, + legacyGetCertChain(const TransportSocketFactoryWithFilterChain& data); + void legacySignPayload(const TransportSocketFactoryWithFilterChain& data, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback); @@ -49,8 +51,7 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { absl::optional> filter_chain_; }; - CertWithFilterChain getTlsCertAndFilterChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, + CertWithFilterChain getTlsCertAndFilterChain(const TransportSocketFactoryWithFilterChain& data, const std::string& hostname, bool* cert_matched_sni); struct LegacyCertConfigWithFilterChain { @@ -59,9 +60,12 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { }; LegacyCertConfigWithFilterChain - legacyGetTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname); + legacyGetTlsCertConfigAndFilterChain(const TransportSocketFactoryWithFilterChain& data); + + absl::optional + getTransportSocketAndFilterChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname); Network::Socket& listen_socket_; Network::FilterChainManager* filter_chain_manager_{nullptr}; diff --git a/source/common/quic/quic_server_transport_socket_factory.h b/source/common/quic/quic_server_transport_socket_factory.h index 4982222597f4..63cda077f333 100644 --- a/source/common/quic/quic_server_transport_socket_factory.h +++ b/source/common/quic/quic_server_transport_socket_factory.h @@ -49,6 +49,8 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock bool earlyDataEnabled() const { return enable_early_data_; } + bool handleCertsWithSharedTlsCode() const { return handle_certs_with_shared_tls_code_; } + protected: void onSecretUpdated() override; From ce2a2f838a17eae49bbfe9f0d1c5fec14af77713 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 4 Mar 2024 14:50:38 -0800 Subject: [PATCH 26/26] remove conflict marker Signed-off-by: Greg Greenway --- test/integration/quic_http_integration_test.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 11b41516e1d0..98bd46084959 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -759,7 +759,6 @@ TEST_P(QuicHttpIntegrationTest, MultipleUpstreamQuicConnections) { testMultipleUpstreamQuicConnections(); } ->>>>>>> upstream/main TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsDefaultMode) { testMultipleQuicConnections(); }