From 0045de43c788a7af9d7cccfe8f9eb02405ef64a3 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 1 Jul 2020 16:31:39 -0400 Subject: [PATCH 01/28] add verifier impl and proof source details Signed-off-by: Dan Zhang --- ...tificate_validation_context_config_impl.cc | 5 +- source/extensions/quic_listeners/quiche/BUILD | 40 +++- .../quiche/active_quic_listener.cc | 19 +- .../quiche/active_quic_listener.h | 7 - .../quiche/envoy_quic_crypto_server_stream.h | 119 ++++++++++++ .../quiche/envoy_quic_dispatcher.cc | 5 +- .../quiche/envoy_quic_fake_proof_verifier.h | 61 ------ .../quiche/envoy_quic_proof_source.cc | 27 +-- .../quiche/envoy_quic_proof_source.h | 43 ++++- .../quiche/envoy_quic_proof_source_base.cc | 47 +++++ ...ource.h => envoy_quic_proof_source_base.h} | 56 +++--- .../quiche/envoy_quic_proof_verifier.cc | 77 ++++++++ .../quiche/envoy_quic_proof_verifier.h | 27 +++ .../quiche/envoy_quic_proof_verifier_base.cc | 54 ++++++ .../quiche/envoy_quic_proof_verifier_base.h | 42 +++++ .../quiche/envoy_quic_server_connection.cc | 26 +-- .../quiche/envoy_quic_server_connection.h | 10 +- .../quiche/envoy_quic_server_session.cc | 65 +++++-- .../quiche/envoy_quic_server_session.h | 6 +- .../quiche/quic_transport_socket_factory.h | 2 + .../tls/context_config_impl.h | 5 +- .../transport_sockets/tls/context_impl.cc | 48 ++--- .../transport_sockets/tls/context_impl.h | 7 + test/extensions/quic_listeners/quiche/BUILD | 27 ++- .../quiche/active_quic_listener_test.cc | 51 ------ .../quiche/crypto_test_utils_for_envoy.cc | 4 +- .../quiche/envoy_quic_dispatcher_test.cc | 173 ++---------------- .../quiche/envoy_quic_proof_source_test.cc | 107 ++++++++--- .../quiche/envoy_quic_proof_verifier_test.cc | 136 ++++++++++++++ .../quiche/envoy_quic_server_session_test.cc | 122 +++++++----- .../quiche/envoy_quic_server_stream_test.cc | 3 +- .../integration/quic_http_integration_test.cc | 64 ++++++- .../quic_listeners/quiche/test_proof_source.h | 4 +- .../quiche/test_proof_verifier.h | 25 +++ test/mocks/ssl/mocks.h | 17 ++ 35 files changed, 1020 insertions(+), 511 deletions(-) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc rename source/extensions/quic_listeners/quiche/{envoy_quic_fake_proof_source.h => envoy_quic_proof_source_base.h} (56%) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc create mode 100644 test/extensions/quic_listeners/quiche/test_proof_verifier.h diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index 2f4a1ac8bc84..f17a2d81af94 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -39,8 +39,9 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl( certificateRevocationListPath())); } if (!subject_alt_name_matchers_.empty() || !verify_subject_alt_name_list_.empty()) { - throw EnvoyException("SAN-based verification of peer certificates without " - "trusted CA is insecure and not allowed"); + throw EnvoyException(absl::StrCat("SAN-based verification of peer certificates without " + "trusted CA is insecure and not allowed ", + ca_cert_path_)); } if (allow_expired_certificate_) { throw EnvoyException("Certificate validity period is always ignored without trusted CA"); diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 3082bdf98eca..b144a2166ea9 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -62,12 +62,16 @@ envoy_cc_library( ) envoy_cc_library( - name = "envoy_quic_fake_proof_source_lib", - hdrs = ["envoy_quic_fake_proof_source.h"], + name = "envoy_quic_proof_source_base_lib", + srcs = ["envoy_quic_proof_source_base.cc"], + hdrs = ["envoy_quic_proof_source_base.h"], external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ + "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", + "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_crypto_proof_source_interface_lib", + "@com_googlesource_quiche//:quic_core_data_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], ) @@ -79,27 +83,42 @@ envoy_cc_library( external_deps = ["ssl"], tags = ["nofips"], deps = [ - ":envoy_quic_fake_proof_source_lib", + ":envoy_quic_proof_source_base_lib", ":envoy_quic_utils_lib", ":quic_io_handle_wrapper_lib", ":quic_transport_socket_factory_lib", "//include/envoy/ssl:tls_certificate_config_interface", "//source/extensions/transport_sockets:well_known_names", + "//source/server:connection_handler_lib", "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", ], ) envoy_cc_library( - name = "envoy_quic_proof_verifier_lib", - hdrs = ["envoy_quic_fake_proof_verifier.h"], + name = "envoy_quic_proof_verifier_base_lib", + srcs = ["envoy_quic_proof_verifier_base.cc"], + hdrs = ["envoy_quic_proof_verifier_base.h"], external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ + "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], ) +envoy_cc_library( + name = "envoy_quic_proof_verifier_lib", + srcs = ["envoy_quic_proof_verifier.cc"], + hdrs = ["envoy_quic_proof_verifier.h"], + external_deps = ["quiche_quic_platform"], + tags = ["nofips"], + deps = [ + ":envoy_quic_proof_verifier_base_lib", + "//source/extensions/transport_sockets/tls:context_lib", + ], +) + envoy_cc_library( name = "spdy_server_push_utils_for_envoy_lib", srcs = ["spdy_server_push_utils_for_envoy.cc"], @@ -171,6 +190,7 @@ envoy_cc_library( ], tags = ["nofips"], deps = [ + ":envoy_quic_crypto_server_stream_lib", ":envoy_quic_stream_lib", ":envoy_quic_utils_lib", ":quic_filter_manager_connection_lib", @@ -343,3 +363,13 @@ envoy_cc_extension( "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", ], ) + +envoy_cc_library( + name = "envoy_quic_crypto_server_stream_lib", + hdrs = ["envoy_quic_crypto_server_stream.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_proof_source_lib", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index 55f5da2e49f1..8580d4f7cc61 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -33,20 +33,6 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, const quic::QuicConfig& quic_config, Network::Socket::OptionsSharedPtr options, const envoy::config::core::v3::RuntimeFeatureFlag& enabled) - : ActiveQuicListener(dispatcher, parent, listen_socket, listener_config, quic_config, - std::move(options), - std::make_unique( - listen_socket, listener_config.filterChainManager()), - enabled) {} - -ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, - Network::ConnectionHandler& parent, - Network::SocketSharedPtr listen_socket, - Network::ListenerConfig& listener_config, - const quic::QuicConfig& quic_config, - Network::Socket::OptionsSharedPtr options, - std::unique_ptr proof_source, - const envoy::config::core::v3::RuntimeFeatureFlag& enabled) : Server::ConnectionHandlerImpl::ActiveListenerImplBase(parent, &listener_config), dispatcher_(dispatcher), version_manager_(quic::CurrentSupportedVersions()), listen_socket_(*listen_socket), enabled_(enabled, Runtime::LoaderSingleton::get()) { @@ -65,7 +51,10 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, random->RandBytes(random_seed_, sizeof(random_seed_)); crypto_config_ = std::make_unique( quiche::QuicheStringPiece(reinterpret_cast(random_seed_), sizeof(random_seed_)), - quic::QuicRandom::GetInstance(), std::move(proof_source), quic::KeyExchangeSource::Default()); + quic::QuicRandom::GetInstance(), + std::make_unique(listen_socket_, listener_config.filterChainManager(), + stats_), + quic::KeyExchangeSource::Default()); auto connection_helper = std::make_unique(dispatcher_); crypto_config_->AddDefaultConfig(random, connection_helper->GetClock(), quic::QuicCryptoServerConfig::ConfigOptions()); diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index a9c87d4b4d66..8d0d5c9dd46e 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -36,13 +36,6 @@ class ActiveQuicListener : public Network::UdpListenerCallbacks, Network::Socket::OptionsSharedPtr options, const envoy::config::core::v3::RuntimeFeatureFlag& enabled); - ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, - Network::SocketSharedPtr listen_socket, - Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config, - Network::Socket::OptionsSharedPtr options, - std::unique_ptr proof_source, - const envoy::config::core::v3::RuntimeFeatureFlag& enabled); - ~ActiveQuicListener() override; void onListenerShutdown(); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h new file mode 100644 index 000000000000..520aca4eee13 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h @@ -0,0 +1,119 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_crypto_server_stream.h" +#include "quiche/quic/core/tls_server_handshaker.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source.h" + +#include + +namespace Envoy { +namespace Quic { + +class EnvoyCryptoServerStream : protected Logger::Loggable { +public: + virtual ~EnvoyCryptoServerStream() {} + virtual const DetailsWithFilterChain* proofSourceDetails() const = 0; +}; + +// A dedicated stream to do QUIC crypto handshake. +class EnvoyQuicCryptoServerStream : public quic::QuicCryptoServerStream, + public EnvoyCryptoServerStream { +public: + // A wrapper to retain proof source details which has filter chain. + class EnvoyProcessClientHelloResultCallback : public quic::ProcessClientHelloResultCallback { + public: + EnvoyProcessClientHelloResultCallback( + EnvoyQuicCryptoServerStream* parent, + std::unique_ptr done_cb) + : parent_(parent), done_cb_(std::move(done_cb)) {} + + void Run(quic::QuicErrorCode error, const std::string& error_details, + std::unique_ptr message, + std::unique_ptr diversification_nonce, + std::unique_ptr proof_source_details) override { + if (parent_ == nullptr) { + return; + } + + if (proof_source_details != nullptr) { + // Retain a copy of the proof source details after getting filter chain. + parent_->details_ = std::make_unique( + static_cast(*proof_source_details)); + } else { + ENVOY_LOG_MISC( + debug, + "ProofSource didn't provide ProofSource::Details. No filter chain will be installed."); + } + parent_->done_cb_wrapper_ = nullptr; + parent_ = nullptr; + done_cb_->Run(error, error_details, std::move(message), std::move(diversification_nonce), + std::move(proof_source_details)); + } + + void Cancel() { parent_ = nullptr; } + + private: + EnvoyQuicCryptoServerStream* parent_; + std::unique_ptr done_cb_; + }; + + EnvoyQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache, + quic::QuicSession* session, + quic::QuicCryptoServerStreamBase::Helper* helper) + : quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, session, helper) {} + + ~EnvoyQuicCryptoServerStream() override { + if (done_cb_wrapper_ != nullptr) { + done_cb_wrapper_->Cancel(); + } + } + + // quic::QuicCryptoServerStream + // Override to retain ProofSource::Details. + void ProcessClientHello( + quic::QuicReferenceCountedPointer result, + std::unique_ptr proof_source_details, + std::unique_ptr done_cb) override { + auto done_cb_wrapper = + std::make_unique(this, std::move(done_cb)); + done_cb_wrapper_ = done_cb_wrapper.get(); + auto details = static_cast(proof_source_details.get()); + if (details != nullptr) { + // Retain a copy of the old proof source details. + details_ = std::make_unique(*details); + } + quic::QuicCryptoServerStream::ProcessClientHello(result, std::move(proof_source_details), + std::move(done_cb_wrapper)); + } + + const DetailsWithFilterChain* proofSourceDetails() const override { return details_.get(); } + +private: + EnvoyProcessClientHelloResultCallback* done_cb_wrapper_; + std::unique_ptr details_; +}; + +class EnvoyQuicTlsServerHandshaker : public quic::TlsServerHandshaker, + public EnvoyCryptoServerStream { +public: + EnvoyQuicTlsServerHandshaker(quic::QuicSession* session, + const quic::QuicCryptoServerConfig& crypto_config) + : quic::TlsServerHandshaker(session, crypto_config) {} + + const DetailsWithFilterChain* proofSourceDetails() const override { + return dynamic_cast(proof_source_details()); + } +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index ab999d5b204d..08564b722580 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -52,12 +52,11 @@ std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( quiche::QuicheStringPiece /*alpn*/, const quic::ParsedQuicVersion& version) { auto quic_connection = std::make_unique( server_connection_id, peer_address, *helper(), *alarm_factory(), writer(), - /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, listener_config_, - listener_stats_, listen_socket_); + /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, listen_socket_); auto quic_session = std::make_unique( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_, - listener_config_.perConnectionBufferLimitBytes()); + listener_config_.perConnectionBufferLimitBytes(), listener_config_); quic_session->Initialize(); // Filter chain can't be retrieved here as self address is unknown at this // point. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h deleted file mode 100644 index af107983317b..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "absl/strings/str_cat.h" - -#pragma GCC diagnostic push - -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" - -#include "quiche/quic/core/crypto/proof_verifier.h" -#include "quiche/quic/core/quic_versions.h" - -#pragma GCC diagnostic pop - -namespace Envoy { -namespace Quic { - -// A fake implementation of quic::ProofVerifier which approves the certs and -// signature produced by EnvoyQuicFakeProofSource. -class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { -public: - ~EnvoyQuicFakeProofVerifier() override = default; - - // quic::ProofVerifier - // Return success if the certs chain is valid and signature is "Fake signature for { - // [server_config] }". Otherwise failure. - quic::QuicAsyncStatus - VerifyProof(const std::string& hostname, const uint16_t port, - const std::string& /*server_config*/, quic::QuicTransportVersion /*quic_version*/, - absl::string_view /*chlo_hash*/, const std::vector& certs, - const std::string& cert_sct, const std::string& /*signature*/, - const quic::ProofVerifyContext* context, std::string* error_details, - std::unique_ptr* details, - std::unique_ptr callback) override { - if (VerifyCertChain(hostname, port, certs, "", cert_sct, context, error_details, details, - std::move(callback)) == quic::QUIC_SUCCESS) { - return quic::QUIC_SUCCESS; - } - return quic::QUIC_FAILURE; - } - - // Return success upon one arbitrary cert content. Otherwise failure. - quic::QuicAsyncStatus - VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, - const std::vector& certs, const std::string& /*ocsp_response*/, - const std::string& cert_sct, const quic::ProofVerifyContext* /*context*/, - std::string* /*error_details*/, - std::unique_ptr* /*details*/, - std::unique_ptr /*callback*/) override { - // Cert SCT support is not enabled for fake ProofSource. - if (cert_sct.empty() && certs.size() == 1) { - return quic::QUIC_SUCCESS; - } - return quic::QUIC_FAILURE; - } - - std::unique_ptr CreateDefaultContext() override { return nullptr; } -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index ffb567a4dbf3..6df2c6c0267b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -18,8 +18,10 @@ quic::QuicReferenceCountedPointer EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname) { + CertConfigWithFilterChain pair = + getTlsCertConfigAndFilterChain(server_address, client_address, hostname); absl::optional> cert_config_ref = - getTlsCertConfig(server_address, client_address, hostname); + pair.first; if (!cert_config_ref.has_value()) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); return nullptr; @@ -42,8 +44,10 @@ void EnvoyQuicProofSource::ComputeTlsSignature( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, std::unique_ptr callback) { + CertConfigWithFilterChain pair = + getTlsCertConfigAndFilterChain(server_address, client_address, hostname); absl::optional> cert_config_ref = - getTlsCertConfig(server_address, client_address, hostname); + pair.first; if (!cert_config_ref.has_value()) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); callback->Run(false, "", nullptr); @@ -60,19 +64,19 @@ void EnvoyQuicProofSource::ComputeTlsSignature( std::string sig = pem_key->Sign(in, signature_algorithm); bool success = !sig.empty(); - callback->Run(success, sig, nullptr); + ASSERT(pair.second.has_value()); + callback->Run(success, sig, std::make_unique(pair.second.value())); } -absl::optional> -EnvoyQuicProofSource::getTlsCertConfig(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname) { +EnvoyQuicProofSource::CertConfigWithFilterChain +EnvoyQuicProofSource::getTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname) { ENVOY_LOG(trace, "Getting cert chain for {}", hostname); Network::ConnectionSocketImpl connection_socket( - std::make_unique(listen_socket_->ioHandle()), + std::make_unique(listen_socket_.ioHandle()), quicAddressToEnvoyAddressInstance(server_address), quicAddressToEnvoyAddressInstance(client_address)); - connection_socket.setDetectedTransportProtocol( Extensions::TransportSockets::TransportProtocolNames::get().Quic); connection_socket.setRequestedServerName(hostname); @@ -81,7 +85,8 @@ EnvoyQuicProofSource::getTlsCertConfig(const quic::QuicSocketAddress& server_add filter_chain_manager_.findFilterChain(connection_socket); if (filter_chain == nullptr) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); - return absl::nullopt; + listener_stats_.no_filter_chain_match_.inc(); + return {absl::nullopt, absl::nullopt}; } const Network::TransportSocketFactory& transport_socket_factory = filter_chain->transportSocketFactory(); @@ -93,7 +98,7 @@ EnvoyQuicProofSource::getTlsCertConfig(const quic::QuicSocketAddress& server_add // 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()}; + return {tls_cert_configs[0].get(), DetailsWithFilterChain(*filter_chain)}; } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index 4204f4b13634..cdba29fec1d0 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -1,15 +1,34 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#pragma once + +#include "server/connection_handler_impl.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" #include "extensions/quic_listeners/quiche/quic_transport_socket_factory.h" namespace Envoy { namespace Quic { -class EnvoyQuicProofSource : public EnvoyQuicFakeProofSource, +class DetailsWithFilterChain : public quic::ProofSource::Details { +public: + explicit DetailsWithFilterChain(const Network::FilterChain& filter_chain) + : filter_chain_(filter_chain) {} + DetailsWithFilterChain(const DetailsWithFilterChain& other) + : filter_chain_(other.filter_chain_) {} + + const Network::FilterChain& filterChain() const { return filter_chain_; } + +private: + const Network::FilterChain& filter_chain_; +}; + +class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase, protected Logger::Loggable { public: - EnvoyQuicProofSource(Network::SocketSharedPtr listen_socket, - Network::FilterChainManager& filter_chain_manager) - : listen_socket_(std::move(listen_socket)), filter_chain_manager_(filter_chain_manager) {} + EnvoyQuicProofSource(Network::Socket& listen_socket, + Network::FilterChainManager& filter_chain_manager, + Server::ListenerStats& listener_stats) + : EnvoyQuicProofSourceBase(), listen_socket_(listen_socket), + filter_chain_manager_(filter_chain_manager), listener_stats_(listener_stats) {} ~EnvoyQuicProofSource() override = default; @@ -23,12 +42,18 @@ class EnvoyQuicProofSource : public EnvoyQuicFakeProofSource, std::unique_ptr callback) override; private: - absl::optional> - getTlsCertConfig(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, const std::string& hostname); + using CertConfigWithFilterChain = + std::pair>, + absl::optional>; + + CertConfigWithFilterChain + getTlsCertConfigAndFilterChain(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname); - Network::SocketSharedPtr listen_socket_; + Network::Socket& listen_socket_; Network::FilterChainManager& filter_chain_manager_; + Server::ListenerStats& listener_stats_; }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc new file mode 100644 index 000000000000..aed5eccacc31 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -0,0 +1,47 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" + +#pragma GCC diagnostic push + +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "quiche/quic/core/quic_data_writer.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, + const std::string& server_config, + quic::QuicTransportVersion /*transport_version*/, + quiche::QuicheStringPiece chlo_hash, + std::unique_ptr callback) { + quic::QuicReferenceCountedPointer chain = + GetCertChain(server_address, client_address, hostname); + + size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + + server_config.size(); + auto payload = std::make_unique(payload_size); + quic::QuicDataWriter payload_writer(payload_size, payload.get(), + quiche::Endianness::HOST_BYTE_ORDER); + bool success = + payload_writer.WriteBytes(quic::kProofSignatureLabel, sizeof(quic::kProofSignatureLabel)) && + payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && + payload_writer.WriteStringPiece(server_config); + if (!success) { + quic::QuicCryptoProof proof; + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } + + // TODO(danzh) Get the signature algorithm from leaf cert. + auto signature_callback = std::make_unique(std::move(callback), chain); + ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, + quiche::QuicheStringPiece(payload.get(), payload_size), + std::move(signature_callback)); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h similarity index 56% rename from source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h rename to source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index cddf10b7799c..3a5aadb7513f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -12,23 +12,24 @@ #pragma GCC diagnostic ignored "-Wunused-parameter" #include "quiche/quic/core/crypto/proof_source.h" #include "quiche/quic/core/quic_versions.h" - +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/platform/api/quic_reference_counted.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/common/platform/api/quiche_string_piece.h" #pragma GCC diagnostic pop #include "openssl/ssl.h" #include "envoy/network/filter.h" -#include "quiche/quic/platform/api/quic_reference_counted.h" -#include "quiche/quic/platform/api/quic_socket_address.h" -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "server/backtrace.h" namespace Envoy { namespace Quic { -// A fake implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). +// A partial implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). // TODO(danzh) Rename it to EnvoyQuicProofSource once it's fully implemented. -class EnvoyQuicFakeProofSource : public quic::ProofSource { +class EnvoyQuicProofSourceBase : public quic::ProofSource { public: - ~EnvoyQuicFakeProofSource() override = default; + ~EnvoyQuicProofSourceBase() override = default; // quic::ProofSource // Returns a certs chain and its fake SCT "Fake timestamp" and TLS signature wrapped @@ -36,41 +37,36 @@ class EnvoyQuicFakeProofSource : public quic::ProofSource { void GetProof(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece /*chlo_hash*/, - std::unique_ptr callback) override { - quic::QuicReferenceCountedPointer chain = - GetCertChain(server_address, client_address, hostname); - quic::QuicCryptoProof proof; - bool success = false; - // TODO(danzh) Get the signature algorithm from leaf cert. - auto signature_callback = std::make_unique(success, proof.signature); - ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, - server_config, std::move(signature_callback)); - ASSERT(success); - proof.leaf_cert_scts = "Fake timestamp"; - callback->Run(true, chain, proof, nullptr /* details */); - } + quiche::QuicheStringPiece chlo_hash, + std::unique_ptr callback) override; TicketCrypter* GetTicketCrypter() override { return nullptr; } private: - // Used by GetProof() to get fake signature. - class FakeSignatureCallback : public quic::ProofSource::SignatureCallback { + // Used by GetProof() to get signature. + class SignatureCallback : public quic::ProofSource::SignatureCallback { public: // TODO(danzh) Pass in Details to retain the certs chain, and quic::ProofSource::Callback to be // triggered in Run(). - FakeSignatureCallback(bool& success, std::string& signature) - : success_(success), signature_(signature) {} + SignatureCallback(std::unique_ptr callback, + quic::QuicReferenceCountedPointer chain) + : callback_(std::move(callback)), chain_(chain) {} // quic::ProofSource::SignatureCallback - void Run(bool ok, std::string signature, std::unique_ptr
/*details*/) override { - success_ = ok; - signature_ = signature; + void Run(bool ok, std::string signature, std::unique_ptr
details) override { + quic::QuicCryptoProof proof; + if (!ok) { + callback_->Run(false, chain_, proof, nullptr); + return; + } + proof.signature = signature; + proof.leaf_cert_scts = "Fake timestamp"; + callback_->Run(true, chain_, proof, std::move(details)); } private: - bool& success_; - std::string& signature_; + std::unique_ptr callback_; + quic::QuicReferenceCountedPointer chain_; }; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc new file mode 100644 index 000000000000..3e606633ad7d --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -0,0 +1,77 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" + +#include "quiche/quic/core/crypto/certificate_view.h" + +namespace Envoy { +namespace Quic { + +static X509* ParseDERCertificate(const std::string& der_bytes, std::string* error_details) { + const uint8_t* data; + const uint8_t* orig_data; + orig_data = data = reinterpret_cast(der_bytes.data()); + bssl::UniquePtr cert(d2i_X509(nullptr, &data, der_bytes.size())); + if (!cert.get()) { + *error_details = "d2i_X509"; + return nullptr; + } + if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { + // Trailing garbage. + return nullptr; + } + return cert.release(); +} + +quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( + const std::string& hostname, const uint16_t /*port*/, const std::vector& certs, + const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/, + const quic::ProofVerifyContext* /*context*/, std::string* error_details, + std::unique_ptr* /*details*/, + std::unique_ptr /*callback*/) { + if (certs.size() == 0) { + return quic::QUIC_FAILURE; + } + bssl::UniquePtr intermediates(sk_X509_new_null()); + bssl::UniquePtr leaf; + for (size_t i = 0; i < certs.size(); i++) { + X509* cert = ParseDERCertificate(certs[i], error_details); + if (!cert) { + return quic::QUIC_FAILURE; + } + if (i == 0) { + leaf.reset(cert); + } else { + sk_X509_push(intermediates.get(), cert); + } + } + + bssl::UniquePtr ctx(X509_STORE_CTX_new()); + X509_STORE* store = SSL_CTX_get_cert_store(context_impl_.chooseSslContexts()); + if (!X509_STORE_CTX_init(ctx.get(), store, leaf.get(), intermediates.get())) { + *error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; + return quic::QUIC_FAILURE; + } + + int res = context_impl_.doVerifyCertChain(ctx.get(), nullptr, std::move(leaf), nullptr); + if (res <= 0) { + const int n = X509_STORE_CTX_get_error(ctx.get()); + const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); + *error_details = absl::StrCat("X509_verify_cert: certificate verification error at depth ", + depth, ": ", X509_verify_cert_error_string(n)); + return quic::QUIC_FAILURE; + } + + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(certs[0]); + ASSERT(cert_view != nullptr); + for (const absl::string_view config_san : cert_view->subject_alt_name_domains()) { + std::cerr << "config_san " << config_san << "\n"; + if (config_san == hostname) { + return quic::QUIC_SUCCESS; + } + } + *error_details = absl::StrCat("Leaf certificate doesn't match hostname: ", hostname); + return quic::QUIC_FAILURE; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h new file mode 100644 index 000000000000..b8a2bd7d3c27 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h @@ -0,0 +1,27 @@ +#pragma once + +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" +#include "extensions/transport_sockets/tls/context_impl.h" + +namespace Envoy { +namespace Quic { + +class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { +public: + EnvoyQuicProofVerifier(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, + TimeSource& time_source) + : EnvoyQuicProofVerifierBase(), context_impl_(scope, config, time_source) {} + + quic::QuicAsyncStatus + VerifyCertChain(const std::string& hostname, const uint16_t port, + const std::vector& certs, const std::string& ocsp_response, + const std::string& cert_sct, const quic::ProofVerifyContext* context, + std::string* error_details, std::unique_ptr* details, + std::unique_ptr callback) override; + +private: + Extensions::TransportSockets::Tls::ClientContextImpl context_impl_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc new file mode 100644 index 000000000000..0a6ea3b22c11 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -0,0 +1,54 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" + +#include "openssl/ssl.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_data_writer.h" + +namespace Envoy { +namespace Quic { + +quic::QuicAsyncStatus EnvoyQuicProofVerifierBase::VerifyProof( + const std::string& hostname, const uint16_t port, const std::string& server_config, + quic::QuicTransportVersion /*quic_version*/, absl::string_view chlo_hash, + const std::vector& certs, const std::string& cert_sct, + const std::string& signature, const quic::ProofVerifyContext* context, + std::string* error_details, std::unique_ptr* details, + std::unique_ptr callback) { + quic::QuicAsyncStatus res = VerifyCertChain(hostname, port, certs, "", cert_sct, context, + error_details, details, std::move(callback)); + if (res == quic::QUIC_FAILURE) { + return quic::QUIC_FAILURE; + } + ASSERT(res != quic::QUIC_PENDING); + return verifySignature(server_config, chlo_hash, certs[0], signature) ? quic::QUIC_SUCCESS + : quic::QUIC_FAILURE; +} + +bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_config, + absl::string_view chlo_hash, + const std::string& cert, + const std::string& signature) { + + size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + + server_config.size(); + auto payload = std::make_unique(payload_size); + quic::QuicDataWriter payload_writer(payload_size, payload.get(), + quiche::Endianness::HOST_BYTE_ORDER); + bool success = + payload_writer.WriteBytes(quic::kProofSignatureLabel, sizeof(quic::kProofSignatureLabel)) && + payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && + payload_writer.WriteStringPiece(server_config); + if (!success) { + return false; + } + + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(cert); + ASSERT(cert_view != nullptr); + return cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), + signature, SSL_SIGN_RSA_PSS_RSAE_SHA256); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h new file mode 100644 index 000000000000..854407bbd87b --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h @@ -0,0 +1,42 @@ +#pragma once + +#include "absl/strings/str_cat.h" + +#pragma GCC diagnostic push + +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/quic_versions.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +// A partial implementation of quic::ProofVerifier which does signature +// verification using SSL_SIGN_RSA_PSS_RSAE_SHA256. +class EnvoyQuicProofVerifierBase : public quic::ProofVerifier { +public: + ~EnvoyQuicProofVerifierBase() override = default; + + // quic::ProofVerifier + // Return success if the certs chain is valid and signature of { + // server_config + chlo_hash} is valid. Otherwise failure. + quic::QuicAsyncStatus + VerifyProof(const std::string& hostname, const uint16_t port, const std::string& server_config, + quic::QuicTransportVersion /*quic_version*/, absl::string_view chlo_hash, + const std::vector& certs, const std::string& cert_sct, + const std::string& signature, const quic::ProofVerifyContext* context, + std::string* error_details, std::unique_ptr* details, + std::unique_ptr callback) override; + + virtual bool verifySignature(const std::string& server_config, absl::string_view chlo_hash, + const std::string& cert, const std::string& signature); + + std::unique_ptr CreateDefaultContext() override { return nullptr; } +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc index c8a18a45acfb..b8fa94221f05 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc @@ -13,17 +13,14 @@ EnvoyQuicServerConnection::EnvoyQuicServerConnection( const quic::QuicConnectionId& server_connection_id, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, - Network::ListenerConfig& listener_config, Server::ListenerStats& listener_stats, - Network::Socket& listen_socket) + const quic::ParsedQuicVersionVector& supported_versions, Network::Socket& listen_socket) : EnvoyQuicConnection(server_connection_id, initial_peer_address, helper, alarm_factory, writer, owns_writer, quic::Perspective::IS_SERVER, supported_versions, std::make_unique( // Wraps the real IoHandle instance so that if the connection socket // gets closed, the real IoHandle won't be affected. std::make_unique(listen_socket.ioHandle()), - nullptr, quicAddressToEnvoyAddressInstance(initial_peer_address))), - listener_config_(listener_config), listener_stats_(listener_stats) {} + nullptr, quicAddressToEnvoyAddressInstance(initial_peer_address))) {} bool EnvoyQuicServerConnection::OnPacketHeader(const quic::QuicPacketHeader& header) { if (!EnvoyQuicConnection::OnPacketHeader(header)) { @@ -33,27 +30,10 @@ bool EnvoyQuicServerConnection::OnPacketHeader(const quic::QuicPacketHeader& hea return true; } ASSERT(self_address().IsInitialized()); - // Self address should be initialized by now. It's time to install filters. + // Self address should be initialized by now. connectionSocket()->setLocalAddress(quicAddressToEnvoyAddressInstance(self_address())); connectionSocket()->setDetectedTransportProtocol( Extensions::TransportSockets::TransportProtocolNames::get().Quic); - ASSERT(filter_chain_ == nullptr); - filter_chain_ = listener_config_.filterChainManager().findFilterChain(*connectionSocket()); - if (filter_chain_ == nullptr) { - listener_stats_.no_filter_chain_match_.inc(); - CloseConnection(quic::QUIC_CRYPTO_INTERNAL_ERROR, - "closing connection: no matching filter chain found for handshake", - quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); - return false; - } - const bool empty_filter_chain = !listener_config_.filterChainFactory().createNetworkFilterChain( - envoyConnection(), filter_chain_->networkFilterFactories()); - if (empty_filter_chain) { - // TODO(danzh) check empty filter chain at config load time instead of here. - CloseConnection(quic::QUIC_CRYPTO_INTERNAL_ERROR, "closing connection: filter chain is empty", - quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); - return false; - } return true; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h index ad4614710750..7b7fac05e925 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h @@ -15,19 +15,11 @@ class EnvoyQuicServerConnection : public EnvoyQuicConnection { quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, const quic::ParsedQuicVersionVector& supported_versions, - Network::ListenerConfig& listener_config, - Server::ListenerStats& listener_stats, Network::Socket& listen_socket); + Network::Socket& listen_socket); // EnvoyQuicConnection // Overridden to set connection_socket_ with initialized self address and retrieve filter chain. bool OnPacketHeader(const quic::QuicPacketHeader& header) override; - -private: - Network::ListenerConfig& listener_config_; - Server::ListenerStats& listener_stats_; - // Latched to the corresponding quic FilterChain after connection_socket_ is - // initialized. - const Network::FilterChain* filter_chain_{nullptr}; }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 73a62a93d8b3..a7d0238243d3 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -1,15 +1,10 @@ #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/quic_crypto_server_stream.h" -#pragma GCC diagnostic pop +#include #include "common/common/assert.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" namespace Envoy { @@ -20,11 +15,11 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( std::unique_ptr connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) + uint32_t send_buffer_limit, Network::ListenerConfig& listener_config) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - quic_connection_(std::move(connection)) {} + quic_connection_(std::move(connection)), listener_config_(listener_config) {} EnvoyQuicServerSession::~EnvoyQuicServerSession() { ASSERT(!quic_connection_->connected()); @@ -39,8 +34,18 @@ std::unique_ptr EnvoyQuicServerSession::CreateQuicCryptoServerStream( const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache) { - return quic::CreateCryptoServerStream(crypto_config, compressed_certs_cache, this, - stream_helper()); + switch (connection()->version().handshake_protocol) { + case quic::PROTOCOL_QUIC_CRYPTO: + return std::make_unique(crypto_config, compressed_certs_cache, + this, stream_helper()); + case quic::PROTOCOL_TLS1_3: + return std::make_unique(this, *crypto_config); + case quic::PROTOCOL_UNSUPPORTED: + break; + } + QUIC_BUG << "Unknown handshake protocol: " + << static_cast(connection()->version().handshake_protocol); + return nullptr; } quic::QuicSpdyStream* EnvoyQuicServerSession::CreateIncomingStream(quic::QuicStreamId id) { @@ -97,9 +102,30 @@ void EnvoyQuicServerSession::OnCanWrite() { void EnvoyQuicServerSession::SetDefaultEncryptionLevel(quic::EncryptionLevel level) { quic::QuicServerSessionBase::SetDefaultEncryptionLevel(level); - if (level == quic::ENCRYPTION_FORWARD_SECURE) { - // This is only reached once, when handshake is done. - raiseConnectionEvent(Network::ConnectionEvent::Connected); + if (level != quic::ENCRYPTION_FORWARD_SECURE) { + return; + } + // This is only reached once, when handshake is done. + raiseConnectionEvent(Network::ConnectionEvent::Connected); + + const DetailsWithFilterChain* proof_source_detail = + dynamic_cast(GetCryptoStream())->proofSourceDetails(); + if (proof_source_detail == nullptr) { + // Unit tests using TestProofSource might not set ProofSource::Details. + std::cerr << "========= No proof source details\n"; + return; + } + createNetworkFilters(proof_source_detail->filterChain()); +} + +void EnvoyQuicServerSession::createNetworkFilters(const Network::FilterChain& filter_chain) { + const bool empty_filter_chain = !listener_config_.filterChainFactory().createNetworkFilterChain( + *this, filter_chain.networkFilterFactories()); + if (empty_filter_chain) { + // TODO(danzh) check empty filter chain at config load time instead of here. + connection()->CloseConnection(quic::QUIC_CRYPTO_INTERNAL_ERROR, + "closing connection: filter chain is empty", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); } } @@ -108,6 +134,15 @@ bool EnvoyQuicServerSession::hasDataToWrite() { return HasDataToWrite(); } void EnvoyQuicServerSession::OnOneRttKeysAvailable() { quic::QuicServerSessionBase::OnOneRttKeysAvailable(); raiseConnectionEvent(Network::ConnectionEvent::Connected); + + const DetailsWithFilterChain* details = + dynamic_cast(GetCryptoStream())->proofSourceDetails(); + if (details == nullptr) { + std::cerr << "========= No proof source details\n"; + + return; + } + createNetworkFilters(details->filterChain()); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index c0cbc334d8e3..83ba652c3a8f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -15,6 +15,7 @@ #include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h" namespace Envoy { namespace Quic { @@ -33,7 +34,8 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, quic::QuicCryptoServerStreamBase::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit, + Network::ListenerConfig& listener_config); ~EnvoyQuicServerSession() override; @@ -74,8 +76,10 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicServerStream& stream); + void createNetworkFilters(const Network::FilterChain& filter_chain); std::unique_ptr quic_connection_; + Network::ListenerConfig& listener_config_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h index 009af3008368..2ada9e2de17b 100644 --- a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h +++ b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h @@ -1,3 +1,5 @@ +#pragma once + #include "envoy/network/transport_socket.h" #include "envoy/server/transport_socket_config.h" #include "envoy/ssl/context_config.h" diff --git a/source/extensions/transport_sockets/tls/context_config_impl.h b/source/extensions/transport_sockets/tls/context_config_impl.h index 9cfaff0482fb..ad2d927d8231 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.h +++ b/source/extensions/transport_sockets/tls/context_config_impl.h @@ -98,6 +98,9 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::ClientContextConfig { public: + static const std::string DEFAULT_CIPHER_SUITES; + static const std::string DEFAULT_CURVES; + ClientContextConfigImpl( const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& config, absl::string_view sigalgs, @@ -116,8 +119,6 @@ class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Cli private: static const unsigned DEFAULT_MIN_VERSION; static const unsigned DEFAULT_MAX_VERSION; - static const std::string DEFAULT_CIPHER_SUITES; - static const std::string DEFAULT_CURVES; const std::string server_name_indication_; const bool allow_renegotiation_; diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 041c28b3da1f..c55873230816 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -525,49 +525,51 @@ int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { ContextImpl* impl = reinterpret_cast(arg); SSL* ssl = reinterpret_cast( X509_STORE_CTX_get_ex_data(store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - Envoy::Ssl::SslExtendedSocketInfo* sslExtendedInfo = + return impl->doVerifyCertChain( + store_ctx, reinterpret_cast( - SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())); + SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), + bssl::UniquePtr(SSL_get_peer_certificate(ssl)), + static_cast(SSL_get_app_data(ssl))); +} - if (impl->verify_trusted_ca_) { +int ContextImpl::doVerifyCertChain( + X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + bssl::UniquePtr leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) { + if (verify_trusted_ca_) { int ret = X509_verify_cert(store_ctx); - if (sslExtendedInfo) { - sslExtendedInfo->setCertificateValidationStatus( + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus( ret == 1 ? Envoy::Ssl::ClientValidationStatus::Validated : Envoy::Ssl::ClientValidationStatus::Failed); } if (ret <= 0) { - impl->stats_.fail_verify_error_.inc(); - return impl->allow_untrusted_certificate_ ? 1 : ret; + stats_.fail_verify_error_.inc(); + return allow_untrusted_certificate_ ? 1 : ret; } } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl)); - - const Network::TransportSocketOptions* transport_socket_options = - static_cast(SSL_get_app_data(ssl)); - - Envoy::Ssl::ClientValidationStatus validated = impl->verifyCertificate( - cert.get(), + Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( + leaf_cert.get(), transport_socket_options && !transport_socket_options->verifySubjectAltNameListOverride().empty() ? transport_socket_options->verifySubjectAltNameListOverride() - : impl->verify_subject_alt_name_list_, - impl->subject_alt_name_matchers_); + : verify_subject_alt_name_list_, + subject_alt_name_matchers_); - if (sslExtendedInfo) { - if (sslExtendedInfo->certificateValidationStatus() == + if (ssl_extended_info) { + if (ssl_extended_info->certificateValidationStatus() == Envoy::Ssl::ClientValidationStatus::NotValidated) { - sslExtendedInfo->setCertificateValidationStatus(validated); + ssl_extended_info->setCertificateValidationStatus(validated); } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { - sslExtendedInfo->setCertificateValidationStatus(validated); + ssl_extended_info->setCertificateValidationStatus(validated); } } - return impl->allow_untrusted_certificate_ - ? 1 - : (validated != Envoy::Ssl::ClientValidationStatus::Failed); + return allow_untrusted_certificate_ ? 1 + : (validated != Envoy::Ssl::ClientValidationStatus::Failed); } Envoy::Ssl::ClientValidationStatus ContextImpl::verifyCertificate( diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index b72168337d72..23ea415019c4 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -101,6 +101,13 @@ class ContextImpl : public virtual Envoy::Ssl::Context { std::vector getPrivateKeyMethodProviders(); + // Called by verifyCallback to do the actual cert chain verification. + int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + bssl::UniquePtr leaf_cert, + const Network::TransportSocketOptions* transport_socket_options); + + SSL_CTX* chooseSslContexts() const { return tls_contexts_[0].ssl_ctx_.get(); } + protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source); diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 37eb776630cf..ce45e7e6e4fa 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -49,6 +49,7 @@ envoy_cc_test( deps = [ "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/transport_sockets/tls:context_config_lib", "//test/mocks/network:network_mocks", "//test/mocks/ssl:ssl_mocks", "@com_googlesource_quiche//:quic_core_versions_lib", @@ -56,6 +57,19 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "envoy_quic_proof_verifier_test", + srcs = ["envoy_quic_proof_verifier_test.cc"], + external_deps = ["quiche_quic_platform"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/transport_sockets/tls:context_config_lib", + "//test/mocks/ssl:ssl_mocks", + "@com_googlesource_quiche//:quic_test_tools_test_certificates_lib", + ], +) + envoy_cc_test( name = "envoy_quic_server_stream_test", srcs = ["envoy_quic_server_stream_test.cc"], @@ -220,18 +234,27 @@ envoy_cc_test_library( hdrs = ["test_proof_source.h"], tags = ["nofips"], deps = [ - "//source/extensions/quic_listeners/quiche:envoy_quic_fake_proof_source_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_base_lib", "@com_googlesource_quiche//:quic_test_tools_test_certificates_lib", ], ) +envoy_cc_test_library( + name = "test_proof_verifier_lib", + hdrs = ["test_proof_verifier.h"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_base_lib", + ], +) + envoy_cc_test_library( name = "quic_test_utils_for_envoy_lib", srcs = ["crypto_test_utils_for_envoy.cc"], tags = ["nofips"], deps = [ ":test_proof_source_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + ":test_proof_verifier_lib", "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", ], ) diff --git a/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc index b18a58be2e63..5d57bb9ce92d 100644 --- a/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc +++ b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc @@ -132,52 +132,6 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { return config_factory.createActiveUdpListenerFactory(*config_proto, /*concurrency=*/1); } - void configureMocks(int connection_count) { - EXPECT_CALL(listener_config_, filterChainManager()) - .Times(connection_count) - .WillRepeatedly(ReturnRef(filter_chain_manager_)); - EXPECT_CALL(listener_config_, filterChainFactory()).Times(connection_count); - EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) - .Times(connection_count) - .WillRepeatedly(Invoke([](Network::Connection& connection, - const std::vector& filter_factories) { - EXPECT_EQ(1u, filter_factories.size()); - Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); - return true; - })); - if (!quic_version_.UsesTls()) { - EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::Connected)) - .Times(connection_count); - } - EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)) - .Times(connection_count); - - testing::Sequence seq; - for (int i = 0; i < connection_count; ++i) { - auto read_filter = std::make_shared(); - filter_factories_.push_back( - {Network::FilterFactoryCb([read_filter, this](Network::FilterManager& filter_manager) { - filter_manager.addReadFilter(read_filter); - read_filter->callbacks_->connection().addConnectionCallbacks( - network_connection_callbacks_); - })}); - // Stop iteration to avoid calling getRead/WriteBuffer(). - EXPECT_CALL(*read_filter, onNewConnection()) - .WillOnce(Return(Network::FilterStatus::StopIteration)); - read_filters_.push_back(std::move(read_filter)); - - filter_chains_.emplace_back(); - EXPECT_CALL(filter_chains_.back(), networkFilterFactories()) - .WillOnce(ReturnRef(filter_factories_.back())); - - // A Sequence must be used to allow multiple EXPECT_CALL().WillOnce() - // calls for the same object. - EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) - .InSequence(seq) - .WillOnce(Return(&filter_chains_.back())); - } - } - void sendCHLO(quic::QuicConnectionId connection_id) { client_sockets_.push_back(std::make_unique(local_address_, nullptr, /*bind*/ false)); Buffer::OwnedImpl payload = generateChloPacketToSend( @@ -281,7 +235,6 @@ TEST_P(ActiveQuicListenerTest, FailSocketOptionUponCreation) { .WillOnce(Return(false)); auto options = std::make_shared>(); options->emplace_back(std::move(option)); - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager_)); EXPECT_THROW_WITH_REGEX( std::make_unique( *dispatcher_, connection_handler_, listen_socket_, listener_config_, quic_config_, @@ -294,7 +247,6 @@ TEST_P(ActiveQuicListenerTest, FailSocketOptionUponCreation) { TEST_P(ActiveQuicListenerTest, ReceiveCHLO) { quic::QuicBufferedPacketStore* const buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(quic_dispatcher_); - configureMocks(/* connection_count = */ 1); sendCHLO(quic::test::TestConnectionId(1)); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); EXPECT_FALSE(buffered_packets->HasChlosBuffered()); @@ -305,7 +257,6 @@ TEST_P(ActiveQuicListenerTest, ReceiveCHLO) { TEST_P(ActiveQuicListenerTest, ProcessBufferedChlos) { quic::QuicBufferedPacketStore* const buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(quic_dispatcher_); - configureMocks(ActiveQuicListener::kNumSessionsToCreatePerLoop + 2); // Generate one more CHLO than can be processed immediately. for (size_t i = 1; i <= ActiveQuicListener::kNumSessionsToCreatePerLoop + 1; ++i) { @@ -345,7 +296,6 @@ TEST_P(ActiveQuicListenerTest, QuicProcessingDisabledAndEnabled) { EXPECT_TRUE(quic_dispatcher_->session_map().empty()); EXPECT_FALSE(ActiveQuicListenerPeer::enabled(*quic_listener_)); Runtime::LoaderSingleton::getExisting()->mergeValues({{"quic.enabled", " true"}}); - configureMocks(/* connection_count = */ 1); sendCHLO(quic::test::TestConnectionId(1)); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); EXPECT_FALSE(quic_dispatcher_->session_map().empty()); @@ -369,7 +319,6 @@ INSTANTIATE_TEST_SUITE_P(ActiveQuicListenerEmptyFlagConfigTests, TEST_P(ActiveQuicListenerEmptyFlagConfigTest, ReceiveFullQuicCHLO) { quic::QuicBufferedPacketStore* const buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(quic_dispatcher_); - configureMocks(/* connection_count = */ 1); sendCHLO(quic::test::TestConnectionId(1)); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); EXPECT_FALSE(buffered_packets->HasChlosBuffered()); diff --git a/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc b/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc index c5b7a11d70e3..cafdce0c6227 100644 --- a/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc +++ b/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc @@ -19,7 +19,7 @@ #endif #include -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "test/extensions/quic_listeners/quiche/test_proof_verifier.h" #include "test/extensions/quic_listeners/quiche/test_proof_source.h" namespace quic { @@ -32,7 +32,7 @@ std::unique_ptr ProofSourceForTesting() { // NOLINTNEXTLINE(readability-identifier-naming) std::unique_ptr ProofVerifierForTesting() { - return std::make_unique(); + return std::make_unique(); } // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc index 07f036571678..536a26435f4e 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc @@ -26,6 +26,7 @@ #include "extensions/quic_listeners/quiche/platform/envoy_quic_clock.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" #include "extensions/quic_listeners/quiche/envoy_quic_dispatcher.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" #include "test/extensions/quic_listeners/quiche/test_proof_source.h" #include "test/extensions/quic_listeners/quiche/test_utils.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" @@ -87,6 +88,7 @@ class EnvoyQuicDispatcherTest : public QuicMultiVersionTest, envoy_quic_dispatcher_.InitializeWithWriter(writer); EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)) .WillRepeatedly(Return(quic::WriteResult(quic::WRITE_STATUS_OK, 0))); + quic::SetVerbosityLogThreshold(1); } void SetUp() override { @@ -150,53 +152,10 @@ TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponCHLO) { ? quic::QuicIpAddress::Loopback4() : quic::QuicIpAddress::Loopback6(), 54321); - Network::MockFilterChain filter_chain; - Network::MockFilterChainManager filter_chain_manager; - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); - EXPECT_CALL(filter_chain_manager, findFilterChain(_)) - .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { - EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); - EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, - socket.detectedTransportProtocol()); - EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); - return &filter_chain; - })); - std::shared_ptr read_filter(new Network::MockReadFilter()); - Network::MockConnectionCallbacks network_connection_callbacks; - testing::StrictMock read_total; - testing::StrictMock read_current; - testing::StrictMock write_total; - testing::StrictMock write_current; - - std::vector filter_factory( - {[&](Network::FilterManager& filter_manager) { - filter_manager.addReadFilter(read_filter); - read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); - read_filter->callbacks_->connection().setConnectionStats( - {read_total, read_current, write_total, write_current, nullptr, nullptr}); - }}); - EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); - EXPECT_CALL(listener_config_, filterChainFactory()); - EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) - .WillOnce(Invoke([](Network::Connection& connection, - const std::vector& filter_factories) { - EXPECT_EQ(1u, filter_factories.size()); - Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); - return true; - })); - EXPECT_CALL(*read_filter, onNewConnection()) - // Stop iteration to avoid calling getRead/WriteBuffer(). - .WillOnce(Return(Network::FilterStatus::StopIteration)); - if (!quicVersionUsesTls()) { - // QUICHE doesn't support 0-RTT TLS1.3 handshake yet. - EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); - } - quic::QuicBufferedPacketStore* buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(&envoy_quic_dispatcher_); EXPECT_FALSE(buffered_packets->HasChlosBuffered()); EXPECT_FALSE(buffered_packets->HasBufferedPackets(connection_id_)); - // Set QuicDispatcher::new_sessions_allowed_per_event_loop_ to // |kNumSessionsToCreatePerLoopForTests| so that received CHLOs can be // processed immediately. @@ -210,16 +169,18 @@ TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponCHLO) { EXPECT_FALSE(buffered_packets->HasChlosBuffered()); EXPECT_FALSE(buffered_packets->HasBufferedPackets(connection_id_)); - // A new QUIC connection is created and its filter installed based on self and peer address. + // A new QUIC connection is created. EXPECT_EQ(1u, envoy_quic_dispatcher_.session_map().size()); - EXPECT_TRUE( - envoy_quic_dispatcher_.session_map().find(connection_id_)->second->IsEncryptionEstablished()); + quic::QuicSession* session = + envoy_quic_dispatcher_.session_map().find(connection_id_)->second.get(); + ASSERT(session != nullptr); + EXPECT_TRUE(session->IsEncryptionEstablished()); EXPECT_EQ(1u, connection_handler_.numConnections()); - EXPECT_EQ("test.example.org", read_filter->callbacks_->connection().requestedServerName()); - EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress( - read_filter->callbacks_->connection().remoteAddress())); - EXPECT_EQ(*listen_socket_->localAddress(), *read_filter->callbacks_->connection().localAddress()); - EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + auto envoy_connection = static_cast(session); + EXPECT_EQ("test.example.org", envoy_connection->requestedServerName()); + EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(envoy_connection->remoteAddress())); + ASSERT(envoy_connection->localAddress() != nullptr); + EXPECT_EQ(*listen_socket_->localAddress(), *envoy_connection->localAddress()); // Shutdown() to close the connection. envoy_quic_dispatcher_.Shutdown(); } @@ -229,46 +190,6 @@ TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponBufferedCHLO) { ? quic::QuicIpAddress::Loopback4() : quic::QuicIpAddress::Loopback6(), 54321); - Network::MockFilterChain filter_chain; - Network::MockFilterChainManager filter_chain_manager; - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); - EXPECT_CALL(filter_chain_manager, findFilterChain(_)) - .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { - EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); - EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, - socket.detectedTransportProtocol()); - EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); - return &filter_chain; - })); - std::shared_ptr read_filter(new Network::MockReadFilter()); - Network::MockConnectionCallbacks network_connection_callbacks; - testing::StrictMock read_total; - testing::StrictMock read_current; - testing::StrictMock write_total; - testing::StrictMock write_current; - - std::vector filter_factory( - {[&](Network::FilterManager& filter_manager) { - filter_manager.addReadFilter(read_filter); - read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); - read_filter->callbacks_->connection().setConnectionStats( - {read_total, read_current, write_total, write_current, nullptr, nullptr}); - }}); - EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); - EXPECT_CALL(listener_config_, filterChainFactory()); - EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) - .WillOnce(Invoke([](Network::Connection& connection, - const std::vector& filter_factories) { - EXPECT_EQ(1u, filter_factories.size()); - Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); - return true; - })); - EXPECT_CALL(*read_filter, onNewConnection()) - // Stop iteration to avoid calling getRead/WriteBuffer(). - .WillOnce(Return(Network::FilterStatus::StopIteration)); - if (!quicVersionUsesTls()) { - EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); - } quic::QuicBufferedPacketStore* buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(&envoy_quic_dispatcher_); EXPECT_FALSE(buffered_packets->HasChlosBuffered()); @@ -290,73 +211,17 @@ TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponBufferedCHLO) { // A new QUIC connection is created and its filter installed based on self and peer address. EXPECT_EQ(1u, envoy_quic_dispatcher_.session_map().size()); - EXPECT_TRUE( - envoy_quic_dispatcher_.session_map().find(connection_id_)->second->IsEncryptionEstablished()); + quic::QuicSession* session = + envoy_quic_dispatcher_.session_map().find(connection_id_)->second.get(); + EXPECT_TRUE(session->IsEncryptionEstablished()); EXPECT_EQ(1u, connection_handler_.numConnections()); - EXPECT_EQ("test.example.org", read_filter->callbacks_->connection().requestedServerName()); - EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress( - read_filter->callbacks_->connection().remoteAddress())); - EXPECT_EQ(*listen_socket_->localAddress(), *read_filter->callbacks_->connection().localAddress()); - EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + auto envoy_connection = static_cast(session); + EXPECT_EQ("test.example.org", envoy_connection->requestedServerName()); + EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(envoy_connection->remoteAddress())); + EXPECT_EQ(*listen_socket_->localAddress(), *envoy_connection->localAddress()); // Shutdown() to close the connection. envoy_quic_dispatcher_.Shutdown(); } -TEST_P(EnvoyQuicDispatcherTest, CloseConnectionDueToMissingFilterChain) { - quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 - ? quic::QuicIpAddress::Loopback4() - : quic::QuicIpAddress::Loopback6(), - 54321); - Network::MockFilterChainManager filter_chain_manager; - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); - EXPECT_CALL(filter_chain_manager, findFilterChain(_)) - .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { - EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); - EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); - return nullptr; - })); - std::unique_ptr received_packet = createChloReceivedPacket(peer_addr); - envoy_quic_dispatcher_.ProcessBufferedChlos(kNumSessionsToCreatePerLoopForTests); - envoy_quic_dispatcher_.ProcessPacket( - envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), peer_addr, - *received_packet); - EXPECT_EQ(0u, envoy_quic_dispatcher_.session_map().size()); - EXPECT_EQ(0u, connection_handler_.numConnections()); - EXPECT_TRUE(quic::test::QuicDispatcherPeer::GetTimeWaitListManager(&envoy_quic_dispatcher_) - ->IsConnectionIdInTimeWait(connection_id_)); - EXPECT_EQ(1u, listener_stats_.downstream_cx_total_.value()); - EXPECT_EQ(0u, listener_stats_.downstream_cx_active_.value()); - EXPECT_EQ(1u, listener_stats_.no_filter_chain_match_.value()); -} - -TEST_P(EnvoyQuicDispatcherTest, CloseConnectionDueToEmptyFilterChain) { - quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 - ? quic::QuicIpAddress::Loopback4() - : quic::QuicIpAddress::Loopback6(), - 54321); - Network::MockFilterChain filter_chain; - Network::MockFilterChainManager filter_chain_manager; - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); - EXPECT_CALL(filter_chain_manager, findFilterChain(_)) - .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { - EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); - EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); - return &filter_chain; - })); - // Empty filter_factory should cause connection close. - std::vector filter_factory; - EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); - - std::unique_ptr received_packet = createChloReceivedPacket(peer_addr); - envoy_quic_dispatcher_.ProcessBufferedChlos(kNumSessionsToCreatePerLoopForTests); - envoy_quic_dispatcher_.ProcessPacket( - envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), peer_addr, - *received_packet); - EXPECT_EQ(0u, envoy_quic_dispatcher_.session_map().size()); - EXPECT_EQ(0u, connection_handler_.numConnections()); - EXPECT_TRUE(quic::test::QuicDispatcherPeer::GetTimeWaitListManager(&envoy_quic_dispatcher_) - ->IsConnectionIdInTimeWait(connection_id_)); -} - } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 965b090fbf13..72669a6bf8be 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -2,9 +2,10 @@ #include #include -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -23,26 +24,85 @@ namespace Quic { class TestGetProofCallback : public quic::ProofSource::Callback { public: - TestGetProofCallback(bool& called, std::string leaf_cert_scts, const absl::string_view cert) - : called_(called), expected_leaf_certs_scts_(std::move(leaf_cert_scts)), - expected_leaf_cert_(cert) {} + TestGetProofCallback(bool& called, const std::string& server_config, + quic::QuicTransportVersion& version, quiche::QuicheStringPiece chlo_hash, + Network::FilterChain& filter_chain) + : called_(called), server_config_(server_config), version_(version), chlo_hash_(chlo_hash), + expected_filter_chain_(filter_chain) { + ON_CALL(client_context_config_, cipherSuites) + .WillByDefault(ReturnRef( + Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); + ON_CALL(client_context_config_, ecdhCurves) + .WillByDefault( + ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); + const std::string alpn("h2,http/1.1"); + ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); + ; + const std::string empty_string; + ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, signingAlgorithmsForTest()) + .WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, certificateValidationContext()) + .WillByDefault(Return(&cert_validation_ctx_config_)); + + // Getting the last cert in the chain as the root CA cert. + std::string cert_chain(quic::test::kTestCertificateChainPem); + const std::string& root_ca_cert = + cert_chain.substr(cert_chain.rfind("-----BEGIN CERTIFICATE-----")); + const std::string path_string("some_path"); + ON_CALL(cert_validation_ctx_config_, caCert()).WillByDefault(ReturnRef(root_ca_cert)); + ON_CALL(cert_validation_ctx_config_, caCertPath()).WillByDefault(ReturnRef(path_string)); + ON_CALL(cert_validation_ctx_config_, trustChainVerification) + .WillByDefault(Return(envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::VERIFY_TRUST_CHAIN)); + ON_CALL(cert_validation_ctx_config_, allowExpiredCertificate()).WillByDefault(Return(true)); + const std::string crl_list; + ON_CALL(cert_validation_ctx_config_, certificateRevocationList()) + .WillByDefault(ReturnRef(crl_list)); + ON_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) + .WillByDefault(ReturnRef(path_string)); + const std::vector empty_string_list; + ON_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) + .WillByDefault(ReturnRef(empty_string_list)); + const std::vector san_matchers; + ON_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) + .WillByDefault(ReturnRef(san_matchers)); + ON_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) + .WillByDefault(ReturnRef(empty_string_list)); + ON_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) + .WillByDefault(ReturnRef(empty_string_list)); + verifier_ = + std::make_unique(store_, client_context_config_, time_system_); + } // quic::ProofSource::Callback void Run(bool ok, const quic::QuicReferenceCountedPointer& chain, const quic::QuicCryptoProof& proof, std::unique_ptr details) override { EXPECT_TRUE(ok); - EXPECT_EQ(expected_leaf_certs_scts_, proof.leaf_cert_scts); EXPECT_EQ(2, chain->certs.size()); - EXPECT_EQ(expected_leaf_cert_, chain->certs[0]); - EXPECT_EQ(nullptr, details); + EXPECT_EQ(&expected_filter_chain_, + &static_cast(details.get())->filterChain()); + std::string error; + EXPECT_EQ(quic::QUIC_SUCCESS, + verifier_->VerifyProof("www.example.org", 54321, server_config_, version_, chlo_hash_, + chain->certs, proof.leaf_cert_scts, proof.signature, nullptr, + &error, nullptr, nullptr)) + << error; called_ = true; } private: bool& called_; - std::string expected_leaf_certs_scts_; - absl::string_view expected_leaf_cert_; + const std::string& server_config_; + const quic::QuicTransportVersion& version_; + quiche::QuicheStringPiece chlo_hash_; + Network::FilterChain& expected_filter_chain_; + NiceMock store_; + Event::GlobalTimeSystem time_system_; + NiceMock client_context_config_; + NiceMock cert_validation_ctx_config_; + std::unique_ptr verifier_; }; class EnvoyQuicProofSourceTest : public ::testing::Test { @@ -50,30 +110,34 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { EnvoyQuicProofSourceTest() : server_address_(quic::QuicIpAddress::Loopback4(), 12345), client_address_(quic::QuicIpAddress::Loopback4(), 54321), - listen_socket_(std::make_shared()), - proof_source_(listen_socket_, filter_chain_manager_) {} + 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_) {} protected: std::string hostname_{"www.fake.com"}; quic::QuicSocketAddress server_address_; quic::QuicSocketAddress client_address_; quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash_{""}; + quiche::QuicheStringPiece 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_; - std::shared_ptr listen_socket_; + Network::MockListenSocket listen_socket_; + testing::NiceMock listener_config_; + Server::ListenerStats listener_stats_; EnvoyQuicProofSource proof_source_; - EnvoyQuicFakeProofVerifier proof_verifier_; }; TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { bool called = false; - auto callback = std::make_unique(called, "Fake timestamp", - quic::test::kTestCertificate); - EXPECT_CALL(*listen_socket_, ioHandle()).Times(2); + auto callback = std::make_unique(called, server_config_, version_, + chlo_hash_, filter_chain_); + EXPECT_CALL(listen_socket_, ioHandle()).Times(2); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) .WillRepeatedly(Invoke([&](const Network::ConnectionSocket& connection_socket) { EXPECT_EQ(*quicAddressToEnvoyAddressInstance(server_address_), @@ -101,15 +165,6 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, chlo_hash_, std::move(callback)); EXPECT_TRUE(called); - - EXPECT_EQ(quic::QUIC_SUCCESS, - proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - {"Fake cert"}, "", "fake signature", nullptr, nullptr, - nullptr, nullptr)); - EXPECT_EQ(quic::QUIC_FAILURE, - proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - {"Fake cert", "Unexpected cert"}, "Fake timestamp", - "fake signature", nullptr, nullptr, nullptr, nullptr)); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc new file mode 100644 index 000000000000..5bd0089c1450 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -0,0 +1,136 @@ +#include +#include + +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" + +#include "test/mocks/ssl/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/test_time.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/test_tools/test_certificates.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Quic { + +class EnvoyQuicProofVerifierTest : public testing::Test { +public: + EnvoyQuicProofVerifierTest() + : leaf_cert_([=]() { + std::stringstream pem_stream(cert_chain_); + std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); + return chain[0]; + }()) { + ON_CALL(client_context_config_, cipherSuites) + .WillByDefault(ReturnRef( + Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); + ON_CALL(client_context_config_, ecdhCurves) + .WillByDefault( + ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); + const std::string alpn("h2,http/1.1"); + ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); + ; + const std::string empty_string; + ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, signingAlgorithmsForTest()) + .WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, certificateValidationContext()) + .WillByDefault(Return(&cert_validation_ctx_config_)); + } + + // Since this cert chain contains an expired cert, we can flip allow_expired_cert to test the code + // paths for BoringSSL cert verification success and failure. + void configCertVerificationDetails(bool allow_expired_cert) { + // Getting the last cert in the chain as the root CA cert. + const std::string& root_ca_cert = + cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----")); + const std::string path_string("some_path"); + EXPECT_CALL(cert_validation_ctx_config_, caCert()).WillRepeatedly(ReturnRef(root_ca_cert)); + EXPECT_CALL(cert_validation_ctx_config_, caCertPath()).WillRepeatedly(ReturnRef(path_string)); + EXPECT_CALL(cert_validation_ctx_config_, trustChainVerification) + .WillRepeatedly(Return(envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::VERIFY_TRUST_CHAIN)); + EXPECT_CALL(cert_validation_ctx_config_, allowExpiredCertificate()) + .WillRepeatedly(Return(allow_expired_cert)); + const std::string crl_list; + EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationList()) + .WillRepeatedly(ReturnRef(crl_list)); + EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) + .WillRepeatedly(ReturnRef(path_string)); + const std::vector empty_string_list; + EXPECT_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) + .WillRepeatedly(ReturnRef(empty_string_list)); + const std::vector san_matchers; + EXPECT_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) + .WillRepeatedly(ReturnRef(san_matchers)); + EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) + .WillRepeatedly(ReturnRef(empty_string_list)); + EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) + .WillRepeatedly(ReturnRef(empty_string_list)); + + verifier_ = + std::make_unique(store_, client_context_config_, time_system_); + } + +protected: + const std::string cert_chain_{quic::test::kTestCertificateChainPem}; + std::string leaf_cert_; + NiceMock store_; + Event::GlobalTimeSystem time_system_; + NiceMock client_context_config_; + Ssl::MockCertificateValidationContextConfig cert_validation_ctx_config_; + std::unique_ptr verifier_; +}; + +TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainSuccess) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_SUCCESS, + verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + {leaf_cert_}, ocsp_response, cert_sct, nullptr, + &error_details, nullptr, nullptr)) + << error_details; +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureFromSsl) { + configCertVerificationDetails(false); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + {leaf_cert_}, ocsp_response, cert_sct, nullptr, + &error_details, nullptr, nullptr)) + << error_details; + EXPECT_EQ("X509_verify_cert: certificate verification error at depth 1: certificate has expired", + error_details); +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureInvalidHost) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain("unknown.org", 54321, {leaf_cert_}, ocsp_response, cert_sct, + nullptr, &error_details, nullptr, nullptr)) + << error_details; + EXPECT_EQ("Leaf certificate doesn't match hostname: unknown.org", error_details); +} +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 5483d0d7c858..095a7da9f72c 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -57,12 +57,11 @@ class TestEnvoyQuicServerConnection : public EnvoyQuicServerConnection { quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter& writer, const quic::ParsedQuicVersionVector& supported_versions, - Network::ListenerConfig& listener_config, - Server::ListenerStats& stats, Network::Socket& listen_socket) + Network::Socket& listen_socket) : EnvoyQuicServerConnection(quic::test::TestConnectionId(), quic::QuicSocketAddress(quic::QuicIpAddress::Loopback4(), 12345), helper, alarm_factory, &writer, /*owns_writer=*/false, - supported_versions, listener_config, stats, listen_socket) {} + supported_versions, listen_socket) {} Network::Connection::ConnectionStats& connectionStats() const { return EnvoyQuicConnection::connectionStats(); @@ -84,17 +83,60 @@ class TestEnvoyQuicServerSession : public EnvoyQuicServerSession { } }; -class TestQuicCryptoServerStream : public quic::QuicCryptoServerStream { +class ProofSourceDetailsSetter { public: + virtual ~ProofSourceDetailsSetter(){}; + + virtual void setProofSourceDetails(std::unique_ptr details) = 0; +}; + +class TestQuicCryptoServerStream : public EnvoyQuicCryptoServerStream, + public ProofSourceDetailsSetter { +public: + ~TestQuicCryptoServerStream() override = default; + explicit TestQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, quic::QuicSession* session, quic::QuicCryptoServerStreamBase::Helper* helper) - : quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, session, helper) {} + : EnvoyQuicCryptoServerStream(crypto_config, compressed_certs_cache, session, helper) {} + + bool encryption_established() const override { return true; } + + const DetailsWithFilterChain* proofSourceDetails() const override { return details_.get(); } + + void setProofSourceDetails(std::unique_ptr details) override { + details_ = std::move(details); + } + +private: + std::unique_ptr details_; +}; + +class TestEnvoyQuicTlsServerHandshaker : public EnvoyQuicTlsServerHandshaker, + public ProofSourceDetailsSetter { +public: + ~TestEnvoyQuicTlsServerHandshaker() override = default; - using quic::QuicCryptoServerStream::QuicCryptoServerStream; + TestEnvoyQuicTlsServerHandshaker(quic::QuicSession* session, + const quic::QuicCryptoServerConfig& crypto_config) + : EnvoyQuicTlsServerHandshaker(session, crypto_config), + params_(new quic::QuicCryptoNegotiatedParameters) { + params_->cipher_suite = 1; + } bool encryption_established() const override { return true; } + const DetailsWithFilterChain* proofSourceDetails() const override { return details_.get(); } + void setProofSourceDetails(std::unique_ptr details) override { + details_ = std::move(details); + } + const quic::QuicCryptoNegotiatedParameters& crypto_negotiated_params() const override { + return *params_; + } + +private: + std::unique_ptr details_; + quic::QuicReferenceCountedPointer params_; }; class EnvoyQuicServerSessionTest : public testing::TestWithParam { @@ -108,19 +150,16 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { SetQuicReloadableFlag(quic_enable_version_draft_25_v3, GetParam()); return quic::ParsedVersionOfIndex(quic::CurrentSupportedVersions(), 0); }()), - listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), - POOL_GAUGE(listener_config_.listenerScope()), - POOL_HISTOGRAM(listener_config_.listenerScope()))}), quic_connection_(new TestEnvoyQuicServerConnection( - connection_helper_, alarm_factory_, writer_, quic_version_, listener_config_, - listener_stats_, *listener_config_.socket_)), + connection_helper_, alarm_factory_, writer_, quic_version_, *listener_config_.socket_)), crypto_config_(quic::QuicCryptoServerConfig::TESTING, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()), envoy_quic_session_(quic_config_, quic_version_, std::unique_ptr(quic_connection_), /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, &compressed_certs_cache_, *dispatcher_, - /*send_buffer_limit*/ quic::kDefaultFlowControlSendWindow * 1.5), + /*send_buffer_limit*/ quic::kDefaultFlowControlSendWindow * 1.5, + listener_config_), read_filter_(new Network::MockReadFilter()) { EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); @@ -143,13 +182,22 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { envoy_quic_session_.Initialize(); setQuicConfigWithDefaultValues(envoy_quic_session_.config()); envoy_quic_session_.OnConfigNegotiated(); - + quic::test::QuicConfigPeer::SetNegotiated(envoy_quic_session_.config(), true); // Switch to a encryption forward secure crypto stream. quic::test::QuicServerSessionBasePeer::SetCryptoStream(&envoy_quic_session_, nullptr); - quic::test::QuicServerSessionBasePeer::SetCryptoStream( - &envoy_quic_session_, - new TestQuicCryptoServerStream(&crypto_config_, &compressed_certs_cache_, - &envoy_quic_session_, &crypto_stream_helper_)); + quic::QuicCryptoServerStreamBase* crypto_stream = nullptr; + if (quic_version_[0].handshake_protocol == quic::PROTOCOL_QUIC_CRYPTO) { + auto test_crypto_stream = new TestQuicCryptoServerStream( + &crypto_config_, &compressed_certs_cache_, &envoy_quic_session_, &crypto_stream_helper_); + crypto_stream = test_crypto_stream; + crypto_stream_ = test_crypto_stream; + } else { + auto test_crypto_stream = + new TestEnvoyQuicTlsServerHandshaker(&envoy_quic_session_, crypto_config_); + crypto_stream = test_crypto_stream; + crypto_stream_ = test_crypto_stream; + } + quic::test::QuicServerSessionBasePeer::SetCryptoStream(&envoy_quic_session_, crypto_stream); quic_connection_->SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); quic_connection_->SetEncrypter( quic::ENCRYPTION_FORWARD_SECURE, @@ -212,11 +260,11 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { quic::ParsedQuicVersionVector quic_version_; testing::NiceMock writer_; testing::NiceMock listener_config_; - Server::ListenerStats listener_stats_; TestEnvoyQuicServerConnection* quic_connection_; quic::QuicConfig quic_config_; quic::QuicCryptoServerConfig crypto_config_; testing::NiceMock crypto_stream_helper_; + ProofSourceDetailsSetter* crypto_stream_; TestEnvoyQuicServerSession envoy_quic_session_; quic::QuicCompressedCertsCache compressed_certs_cache_{100}; std::shared_ptr read_filter_; @@ -717,31 +765,8 @@ TEST_P(EnvoyQuicServerSessionTest, GoAway) { } TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { - std::string packet_content("random payload"); - auto encrypted_packet = - std::unique_ptr(quic::test::ConstructEncryptedPacket( - quic_connection_->connection_id(), quic::EmptyQuicConnectionId(), /*version_flag=*/true, - /*reset_flag*/ false, /*packet_number=*/1, packet_content)); - - quic::QuicSocketAddress self_address( - envoyAddressInstanceToQuicSocketAddress(listener_config_.socket_->localAddress())); - auto packet = std::unique_ptr( - quic::test::ConstructReceivedPacket(*encrypted_packet, connection_helper_.GetClock()->Now())); - - // Receiving above packet should trigger filter chain retrieval. - Network::MockFilterChainManager filter_chain_manager; - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); Network::MockFilterChain filter_chain; - EXPECT_CALL(filter_chain_manager, findFilterChain(_)) - .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { - EXPECT_EQ(*quicAddressToEnvoyAddressInstance(quic_connection_->peer_address()), - *socket.remoteAddress()); - EXPECT_EQ(*quicAddressToEnvoyAddressInstance(self_address), *socket.localAddress()); - EXPECT_EQ(listener_config_.socket_->ioHandle().fd(), socket.ioHandle().fd()); - EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, - socket.detectedTransportProtocol()); - return &filter_chain; - })); + crypto_stream_->setProofSourceDetails(std::make_unique(filter_chain)); std::vector filter_factory{[this]( Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter_); @@ -760,11 +785,14 @@ TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); return true; })); - // Connection should be closed because this packet has invalid payload. - EXPECT_CALL(*quic_connection_, SendConnectionClosePacket(_, _)); - EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); - quic_connection_->ProcessUdpPacket(self_address, quic_connection_->peer_address(), *packet); - EXPECT_FALSE(quic_connection_->connected()); + if (quic_version_[0].handshake_protocol == quic::PROTOCOL_QUIC_CRYPTO) { + envoy_quic_session_.SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); + } else { + if (quic::VersionUsesHttp3(quic_version_[0].transport_version)) { + EXPECT_CALL(*quic_connection_, SendControlFrame(_)); + } + envoy_quic_session_.OnOneRttKeysAvailable(); + } EXPECT_EQ(nullptr, envoy_quic_session_.socketOptions()); EXPECT_TRUE(quic_connection_->connectionSocket()->ioHandle().isOpen()); EXPECT_TRUE(quic_connection_->connectionSocket()->ioHandle().close().ok()); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 84120cae913b..d1732c7ac350 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -41,8 +41,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_connection_(quic::test::TestConnectionId(), quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_, - *listener_config_.socket_), + /*owns_writer=*/false, {quic_version_}, *listener_config_.socket_), quic_session_(quic_config_, {quic_version_}, &quic_connection_, *dispatcher_, quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), stream_id_(VersionUsesHttp3(quic_version_.transport_version) ? 4u : 5u), diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index d7177aee7a04..feb256251a20 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -7,6 +7,7 @@ #include "test/config/utility.h" #include "test/integration/http_integration.h" +#include "test/integration/ssl_utility.h" #include "test/test_common/utility.h" #pragma GCC diagnostic push @@ -23,12 +24,14 @@ #include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" #include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/quic_transport_socket_factory.h" #include "test/extensions/quic_listeners/quiche/test_utils.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { namespace Quic { @@ -44,6 +47,43 @@ class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; }; +std::unique_ptr +createQuicClientTransportSocketFactory(const Ssl::ClientSslTransportOptions& options, + Api::Api& api) { + std::string yaml_plain = R"EOF( + common_tls_context: + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" +)EOF"; + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml_plain), tls_context); + auto* common_context = tls_context.mutable_common_tls_context(); + + if (options.alpn_) { + common_context->add_alpn_protocols("h3"); + } + if (options.san_) { + common_context->mutable_validation_context() + ->add_hidden_envoy_deprecated_verify_subject_alt_name("spiffe://lyft.com/backend-team"); + } + for (const std::string& cipher_suite : options.cipher_suites_) { + common_context->mutable_tls_params()->add_cipher_suites(cipher_suite); + } + if (!options.sni_.empty()) { + tls_context.set_sni(options.sni_); + } + + common_context->mutable_tls_params()->set_tls_minimum_protocol_version(options.tls_version_); + common_context->mutable_tls_params()->set_tls_maximum_protocol_version(options.tls_version_); + + NiceMock mock_factory_ctx; + ON_CALL(mock_factory_ctx, api()).WillByDefault(testing::ReturnRef(api)); + auto cfg = std::make_unique( + tls_context, options.sigalgs_, mock_factory_ctx); + return std::make_unique(std::move(cfg)); +} + class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVersionTest { public: QuicHttpIntegrationTest() @@ -59,8 +99,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers SetQuicReloadableFlag(quic_enable_version_draft_25_v3, use_http3); return quic::CurrentSupportedVersions(); }()), - crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), - alarm_factory_(*dispatcher_, *conn_helper_.GetClock()), + conn_helper_(*dispatcher_), alarm_factory_(*dispatcher_, *conn_helper_.GetClock()), injected_resource_filename_(TestEnvironment::temporaryPath("injected_resource")), file_updater_(injected_resource_filename_) {} @@ -81,7 +120,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); quic_connection_ = connection.get(); auto session = std::make_unique( - quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, + quic_config_, supported_versions_, std::move(connection), server_id_, crypto_config_.get(), &push_promise_index_, *dispatcher_, 0); session->Initialize(); return session; @@ -105,7 +144,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers } quic::QuicConnectionId getNextServerDesignatedConnectionId() { - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); // If the cached state indicates that we should use a server-designated // connection ID, then return that connection ID. quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() @@ -168,16 +207,23 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers updateResource(0); HttpIntegrationTest::initialize(); registerTestServerPorts({"http"}); + crypto_config_ = + std::make_unique(std::make_unique( + stats_store_, + createQuicClientTransportSocketFactory( + Ssl::ClientSslTransportOptions().setAlpn(true).setSan(true), *api_) + ->clientContextConfig(), + timeSystem())); } void updateResource(double pressure) { file_updater_.update(absl::StrCat(pressure)); } protected: quic::QuicConfig quic_config_; - quic::QuicServerId server_id_{"example.com", 443, false}; + quic::QuicServerId server_id_{"lyft.com", 443, false}; quic::QuicClientPushPromiseIndex push_promise_index_; quic::ParsedQuicVersionVector supported_versions_; - quic::QuicCryptoClientConfig crypto_config_; + std::unique_ptr crypto_config_; EnvoyQuicConnectionHelper conn_helper_; EnvoyQuicAlarmFactory alarm_factory_; CodecClientCallbacksForTest client_codec_callback_; @@ -287,7 +333,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicListenersWithBPF) { set_reuse_port_ = true; initialize(); std::vector codec_clients; - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter looks at the 1st word of connection id in the packet // header. And currently all QUIC versions support 8 bytes connection id. So @@ -330,7 +376,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicListenersNoBPF) { #undef SO_ATTACH_REUSEPORT_CBPF #endif std::vector codec_clients; - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter looks at the 1st byte of connection id in the packet // header. And currently all QUIC versions support 8 bytes connection id. So diff --git a/test/extensions/quic_listeners/quiche/test_proof_source.h b/test/extensions/quic_listeners/quiche/test_proof_source.h index 3df1aec6b23a..93f675793dff 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_source.h +++ b/test/extensions/quic_listeners/quiche/test_proof_source.h @@ -13,14 +13,14 @@ #endif #include -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" namespace Envoy { namespace Quic { // A test ProofSource which always provide a hard-coded test certificate in // QUICHE and a fake signature. -class TestProofSource : public Quic::EnvoyQuicFakeProofSource { +class TestProofSource : public EnvoyQuicProofSourceBase { public: quic::QuicReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& /*server_address*/, diff --git a/test/extensions/quic_listeners/quiche/test_proof_verifier.h b/test/extensions/quic_listeners/quiche/test_proof_verifier.h new file mode 100644 index 000000000000..8f5796be544a --- /dev/null +++ b/test/extensions/quic_listeners/quiche/test_proof_verifier.h @@ -0,0 +1,25 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" + +namespace Envoy { +namespace Quic { + +class TestProofVerifier : public EnvoyQuicProofVerifierBase { +public: + quic::QuicAsyncStatus + VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, + const std::vector& /*certs*/, const std::string& /*ocsp_response*/, + const std::string& /*cert_sct*/, const quic::ProofVerifyContext* /*context*/, + std::string* /*error_details*/, + std::unique_ptr* /*details*/, + std::unique_ptr /*callback*/) override { + return quic::QUIC_SUCCESS; + } + + bool verifySignature(const std::string& /*server_config*/, absl::string_view /*chlo_hash*/, + const std::string& /*cert*/, const std::string& /*signature*/) override { + return true; + } +}; + +} // namespace Quic +} // namespace Envoy diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 825822992a7c..d60011ca7f98 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -128,6 +128,23 @@ class MockTlsCertificateConfig : public TlsCertificateConfig { MOCK_METHOD(Envoy::Ssl::PrivateKeyMethodProviderSharedPtr, privateKeyMethod, (), (const)); }; +class MockCertificateValidationContextConfig : public CertificateValidationContextConfig { +public: + MOCK_METHOD(const std::string&, caCert, (), (const)); + MOCK_METHOD(const std::string&, caCertPath, (), (const)); + MOCK_METHOD(const std::string&, certificateRevocationList, (), (const)); + MOCK_METHOD(const std::string&, certificateRevocationListPath, (), (const)); + MOCK_METHOD(const std::vector&, verifySubjectAltNameList, (), (const)); + MOCK_METHOD(const std::vector&, subjectAltNameMatchers, + (), (const)); + MOCK_METHOD(const std::vector&, verifyCertificateHashList, (), (const)); + MOCK_METHOD(const std::vector&, verifyCertificateSpkiList, (), (const)); + MOCK_METHOD(bool, allowExpiredCertificate, (), (const)); + MOCK_METHOD(envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification, + trustChainVerification, (), (const)); +}; + class MockPrivateKeyMethodManager : public PrivateKeyMethodManager { public: MockPrivateKeyMethodManager(); From 105e7141fb8c491a226de92d3452918470ca0d9f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 1 Jul 2020 20:01:10 -0400 Subject: [PATCH 02/28] no empty filter chain Signed-off-by: Dan Zhang --- ...tificate_validation_context_config_impl.cc | 5 ++-- .../quiche/envoy_quic_crypto_server_stream.h | 7 ++--- .../quiche/envoy_quic_proof_verifier.h | 1 + .../quiche/envoy_quic_server_session.cc | 30 ++++++++++--------- .../quiche/active_quic_listener_test.cc | 5 ---- .../quiche/test_proof_verifier.h | 3 ++ 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index f17a2d81af94..2f4a1ac8bc84 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -39,9 +39,8 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl( certificateRevocationListPath())); } if (!subject_alt_name_matchers_.empty() || !verify_subject_alt_name_list_.empty()) { - throw EnvoyException(absl::StrCat("SAN-based verification of peer certificates without " - "trusted CA is insecure and not allowed ", - ca_cert_path_)); + throw EnvoyException("SAN-based verification of peer certificates without " + "trusted CA is insecure and not allowed"); } if (allow_expired_certificate_) { throw EnvoyException("Certificate validity period is always ignored without trusted CA"); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h index 520aca4eee13..55a271294a78 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_crypto_server_stream.h @@ -48,10 +48,6 @@ class EnvoyQuicCryptoServerStream : public quic::QuicCryptoServerStream, // Retain a copy of the proof source details after getting filter chain. parent_->details_ = std::make_unique( static_cast(*proof_source_details)); - } else { - ENVOY_LOG_MISC( - debug, - "ProofSource didn't provide ProofSource::Details. No filter chain will be installed."); } parent_->done_cb_wrapper_ = nullptr; parent_ = nullptr; @@ -96,6 +92,7 @@ class EnvoyQuicCryptoServerStream : public quic::QuicCryptoServerStream, std::move(done_cb_wrapper)); } + // EnvoyCryptoServerStream const DetailsWithFilterChain* proofSourceDetails() const override { return details_.get(); } private: @@ -103,6 +100,7 @@ class EnvoyQuicCryptoServerStream : public quic::QuicCryptoServerStream, std::unique_ptr details_; }; +// A dedicated stream to do TLS1.3 handshake. class EnvoyQuicTlsServerHandshaker : public quic::TlsServerHandshaker, public EnvoyCryptoServerStream { public: @@ -110,6 +108,7 @@ class EnvoyQuicTlsServerHandshaker : public quic::TlsServerHandshaker, const quic::QuicCryptoServerConfig& crypto_config) : quic::TlsServerHandshaker(session, crypto_config) {} + // EnvoyCryptoServerStream const DetailsWithFilterChain* proofSourceDetails() const override { return dynamic_cast(proof_source_details()); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h index b8a2bd7d3c27..5ad3ca1c57ce 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h @@ -12,6 +12,7 @@ class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { TimeSource& time_source) : EnvoyQuicProofVerifierBase(), context_impl_(scope, config, time_source) {} + // EnvoyQuicProofVerifierBase quic::QuicAsyncStatus VerifyCertChain(const std::string& hostname, const uint16_t port, const std::vector& certs, const std::string& ocsp_response, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index a7d0238243d3..e5b85e56e65f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -112,23 +112,16 @@ void EnvoyQuicServerSession::SetDefaultEncryptionLevel(quic::EncryptionLevel lev dynamic_cast(GetCryptoStream())->proofSourceDetails(); if (proof_source_detail == nullptr) { // Unit tests using TestProofSource might not set ProofSource::Details. - std::cerr << "========= No proof source details\n"; + ENVOY_CONN_LOG( + trace, + "ProofSource didn't provide ProofSource::Details. No filter chain will be installed.", + *this); + return; } createNetworkFilters(proof_source_detail->filterChain()); } -void EnvoyQuicServerSession::createNetworkFilters(const Network::FilterChain& filter_chain) { - const bool empty_filter_chain = !listener_config_.filterChainFactory().createNetworkFilterChain( - *this, filter_chain.networkFilterFactories()); - if (empty_filter_chain) { - // TODO(danzh) check empty filter chain at config load time instead of here. - connection()->CloseConnection(quic::QUIC_CRYPTO_INTERNAL_ERROR, - "closing connection: filter chain is empty", - quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); - } -} - bool EnvoyQuicServerSession::hasDataToWrite() { return HasDataToWrite(); } void EnvoyQuicServerSession::OnOneRttKeysAvailable() { @@ -138,12 +131,21 @@ void EnvoyQuicServerSession::OnOneRttKeysAvailable() { const DetailsWithFilterChain* details = dynamic_cast(GetCryptoStream())->proofSourceDetails(); if (details == nullptr) { - std::cerr << "========= No proof source details\n"; - + ENVOY_CONN_LOG( + trace, + "ProofSource didn't provide ProofSource::Details. No filter chain will be installed.", + *this); return; } createNetworkFilters(details->filterChain()); } +void EnvoyQuicServerSession::createNetworkFilters(const Network::FilterChain& filter_chain) { + const bool has_filter_initialized = + listener_config_.filterChainFactory().createNetworkFilterChain( + *this, filter_chain.networkFilterFactories()); + ASSERT(has_filter_initialized); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc index 5d57bb9ce92d..4d445a1cbc45 100644 --- a/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc +++ b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc @@ -217,12 +217,7 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { NiceMock validation_visitor_; std::list> client_sockets_; - std::list> read_filters_; Network::MockFilterChainManager filter_chain_manager_; - // The following two containers must guarantee pointer stability as addresses - // of elements are saved in expectations before new elements are added. - std::list> filter_factories_; - std::list filter_chains_; quic::ParsedQuicVersion quic_version_; }; diff --git a/test/extensions/quic_listeners/quiche/test_proof_verifier.h b/test/extensions/quic_listeners/quiche/test_proof_verifier.h index 8f5796be544a..2386f15c61ea 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_verifier.h +++ b/test/extensions/quic_listeners/quiche/test_proof_verifier.h @@ -3,8 +3,10 @@ namespace Envoy { namespace Quic { +// A test quic::ProofVerifier which always approves the certs and signature. class TestProofVerifier : public EnvoyQuicProofVerifierBase { public: + // quic::ProofVerifier quic::QuicAsyncStatus VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, const std::vector& /*certs*/, const std::string& /*ocsp_response*/, @@ -15,6 +17,7 @@ class TestProofVerifier : public EnvoyQuicProofVerifierBase { return quic::QUIC_SUCCESS; } + // EnvoyQuicProofVerifierBase bool verifySignature(const std::string& /*server_config*/, absl::string_view /*chlo_hash*/, const std::string& /*cert*/, const std::string& /*signature*/) override { return true; From a0d8975f6f9d1e27136983e353acbf31599cb635 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 3 Jul 2020 21:37:57 -0400 Subject: [PATCH 03/28] revert cert verification Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 28 +--- ..._base.h => envoy_quic_fake_proof_source.h} | 27 ++-- .../quiche/envoy_quic_fake_proof_verifier.h | 61 ++++++++ .../quiche/envoy_quic_proof_source.h | 6 +- .../quiche/envoy_quic_proof_source_base.cc | 47 ------ .../quiche/envoy_quic_proof_verifier.cc | 77 ---------- .../quiche/envoy_quic_proof_verifier.h | 28 ---- .../quiche/envoy_quic_proof_verifier_base.cc | 54 ------- .../quiche/envoy_quic_proof_verifier_base.h | 42 ------ .../tls/context_config_impl.h | 5 +- .../transport_sockets/tls/context_impl.cc | 48 +++---- .../transport_sockets/tls/context_impl.h | 7 - test/extensions/quic_listeners/quiche/BUILD | 27 +--- .../quiche/crypto_test_utils_for_envoy.cc | 4 +- .../quiche/envoy_quic_proof_source_test.cc | 90 +++--------- .../quiche/envoy_quic_proof_verifier_test.cc | 136 ------------------ .../integration/quic_http_integration_test.cc | 64 ++------- .../quic_listeners/quiche/test_proof_source.h | 4 +- .../quiche/test_proof_verifier.h | 28 ---- test/mocks/ssl/mocks.h | 17 --- 20 files changed, 147 insertions(+), 653 deletions(-) rename source/extensions/quic_listeners/quiche/{envoy_quic_proof_source_base.h => envoy_quic_fake_proof_source.h} (74%) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h delete mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc delete mode 100644 test/extensions/quic_listeners/quiche/test_proof_verifier.h diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index b144a2166ea9..2101cbf7eb63 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -62,16 +62,12 @@ envoy_cc_library( ) envoy_cc_library( - name = "envoy_quic_proof_source_base_lib", - srcs = ["envoy_quic_proof_source_base.cc"], - hdrs = ["envoy_quic_proof_source_base.h"], + name = "envoy_quic_fake_proof_source_lib", + hdrs = ["envoy_quic_fake_proof_source.h"], external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ - "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", - "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_crypto_proof_source_interface_lib", - "@com_googlesource_quiche//:quic_core_data_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], ) @@ -83,7 +79,7 @@ envoy_cc_library( external_deps = ["ssl"], tags = ["nofips"], deps = [ - ":envoy_quic_proof_source_base_lib", + ":envoy_quic_fake_proof_source_lib", ":envoy_quic_utils_lib", ":quic_io_handle_wrapper_lib", ":quic_transport_socket_factory_lib", @@ -95,30 +91,16 @@ envoy_cc_library( ) envoy_cc_library( - name = "envoy_quic_proof_verifier_base_lib", - srcs = ["envoy_quic_proof_verifier_base.cc"], - hdrs = ["envoy_quic_proof_verifier_base.h"], + name = "envoy_quic_proof_verifier_lib", + hdrs = ["envoy_quic_fake_proof_verifier.h"], external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ - "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], ) -envoy_cc_library( - name = "envoy_quic_proof_verifier_lib", - srcs = ["envoy_quic_proof_verifier.cc"], - hdrs = ["envoy_quic_proof_verifier.h"], - external_deps = ["quiche_quic_platform"], - tags = ["nofips"], - deps = [ - ":envoy_quic_proof_verifier_base_lib", - "//source/extensions/transport_sockets/tls:context_lib", - ], -) - envoy_cc_library( name = "spdy_server_push_utils_for_envoy_lib", srcs = ["spdy_server_push_utils_for_envoy.cc"], diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h similarity index 74% rename from source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h rename to source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h index 3a5aadb7513f..998b3c47b469 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h @@ -12,24 +12,23 @@ #pragma GCC diagnostic ignored "-Wunused-parameter" #include "quiche/quic/core/crypto/proof_source.h" #include "quiche/quic/core/quic_versions.h" -#include "quiche/quic/core/crypto/crypto_protocol.h" -#include "quiche/quic/platform/api/quic_reference_counted.h" -#include "quiche/quic/platform/api/quic_socket_address.h" -#include "quiche/common/platform/api/quiche_string_piece.h" + #pragma GCC diagnostic pop #include "openssl/ssl.h" #include "envoy/network/filter.h" -#include "server/backtrace.h" +#include "quiche/quic/platform/api/quic_reference_counted.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/common/platform/api/quiche_string_piece.h" namespace Envoy { namespace Quic { -// A partial implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). +// A fake implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). // TODO(danzh) Rename it to EnvoyQuicProofSource once it's fully implemented. -class EnvoyQuicProofSourceBase : public quic::ProofSource { +class EnvoyQuicFakeProofSource : public quic::ProofSource { public: - ~EnvoyQuicProofSourceBase() override = default; + ~EnvoyQuicFakeProofSource() override = default; // quic::ProofSource // Returns a certs chain and its fake SCT "Fake timestamp" and TLS signature wrapped @@ -37,8 +36,16 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource { void GetProof(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece chlo_hash, - std::unique_ptr callback) override; + quiche::QuicheStringPiece /*chlo_hash*/, + std::unique_ptr callback) override { + quic::QuicReferenceCountedPointer chain = + GetCertChain(server_address, client_address, hostname); + quic::QuicCryptoProof proof; + // TODO(danzh) Get the signature algorithm from leaf cert. + auto signature_callback = std::make_unique(std::move(callback), chain); + ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, + server_config, std::move(signature_callback)); + } TicketCrypter* GetTicketCrypter() override { return nullptr; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h new file mode 100644 index 000000000000..af107983317b --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -0,0 +1,61 @@ +#pragma once + +#include "absl/strings/str_cat.h" + +#pragma GCC diagnostic push + +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/quic_versions.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +// A fake implementation of quic::ProofVerifier which approves the certs and +// signature produced by EnvoyQuicFakeProofSource. +class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { +public: + ~EnvoyQuicFakeProofVerifier() override = default; + + // quic::ProofVerifier + // Return success if the certs chain is valid and signature is "Fake signature for { + // [server_config] }". Otherwise failure. + quic::QuicAsyncStatus + VerifyProof(const std::string& hostname, const uint16_t port, + const std::string& /*server_config*/, quic::QuicTransportVersion /*quic_version*/, + absl::string_view /*chlo_hash*/, const std::vector& certs, + const std::string& cert_sct, const std::string& /*signature*/, + const quic::ProofVerifyContext* context, std::string* error_details, + std::unique_ptr* details, + std::unique_ptr callback) override { + if (VerifyCertChain(hostname, port, certs, "", cert_sct, context, error_details, details, + std::move(callback)) == quic::QUIC_SUCCESS) { + return quic::QUIC_SUCCESS; + } + return quic::QUIC_FAILURE; + } + + // Return success upon one arbitrary cert content. Otherwise failure. + quic::QuicAsyncStatus + VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, + const std::vector& certs, const std::string& /*ocsp_response*/, + const std::string& cert_sct, const quic::ProofVerifyContext* /*context*/, + std::string* /*error_details*/, + std::unique_ptr* /*details*/, + std::unique_ptr /*callback*/) override { + // Cert SCT support is not enabled for fake ProofSource. + if (cert_sct.empty() && certs.size() == 1) { + return quic::QUIC_SUCCESS; + } + return quic::QUIC_FAILURE; + } + + std::unique_ptr CreateDefaultContext() override { return nullptr; } +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index cdba29fec1d0..c920467078c2 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -2,7 +2,7 @@ #include "server/connection_handler_impl.h" -#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" #include "extensions/quic_listeners/quiche/quic_transport_socket_factory.h" namespace Envoy { @@ -21,13 +21,13 @@ class DetailsWithFilterChain : public quic::ProofSource::Details { const Network::FilterChain& filter_chain_; }; -class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase, +class EnvoyQuicProofSource : public EnvoyQuicFakeProofSource, protected Logger::Loggable { public: EnvoyQuicProofSource(Network::Socket& listen_socket, Network::FilterChainManager& filter_chain_manager, Server::ListenerStats& listener_stats) - : EnvoyQuicProofSourceBase(), listen_socket_(listen_socket), + : EnvoyQuicFakeProofSource(), listen_socket_(listen_socket), filter_chain_manager_(filter_chain_manager), listener_stats_(listener_stats) {} ~EnvoyQuicProofSource() override = default; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc deleted file mode 100644 index aed5eccacc31..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ /dev/null @@ -1,47 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" - -#pragma GCC diagnostic push - -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -#include "quiche/quic/core/quic_data_writer.h" - -#pragma GCC diagnostic pop - -namespace Envoy { -namespace Quic { - -void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname, - const std::string& server_config, - quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece chlo_hash, - std::unique_ptr callback) { - quic::QuicReferenceCountedPointer chain = - GetCertChain(server_address, client_address, hostname); - - size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + - server_config.size(); - auto payload = std::make_unique(payload_size); - quic::QuicDataWriter payload_writer(payload_size, payload.get(), - quiche::Endianness::HOST_BYTE_ORDER); - bool success = - payload_writer.WriteBytes(quic::kProofSignatureLabel, sizeof(quic::kProofSignatureLabel)) && - payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && - payload_writer.WriteStringPiece(server_config); - if (!success) { - quic::QuicCryptoProof proof; - callback->Run(/*ok=*/false, nullptr, proof, nullptr); - return; - } - - // TODO(danzh) Get the signature algorithm from leaf cert. - auto signature_callback = std::make_unique(std::move(callback), chain); - ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, - quiche::QuicheStringPiece(payload.get(), payload_size), - std::move(signature_callback)); -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc deleted file mode 100644 index 3e606633ad7d..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ /dev/null @@ -1,77 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" - -#include "quiche/quic/core/crypto/certificate_view.h" - -namespace Envoy { -namespace Quic { - -static X509* ParseDERCertificate(const std::string& der_bytes, std::string* error_details) { - const uint8_t* data; - const uint8_t* orig_data; - orig_data = data = reinterpret_cast(der_bytes.data()); - bssl::UniquePtr cert(d2i_X509(nullptr, &data, der_bytes.size())); - if (!cert.get()) { - *error_details = "d2i_X509"; - return nullptr; - } - if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { - // Trailing garbage. - return nullptr; - } - return cert.release(); -} - -quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( - const std::string& hostname, const uint16_t /*port*/, const std::vector& certs, - const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/, - const quic::ProofVerifyContext* /*context*/, std::string* error_details, - std::unique_ptr* /*details*/, - std::unique_ptr /*callback*/) { - if (certs.size() == 0) { - return quic::QUIC_FAILURE; - } - bssl::UniquePtr intermediates(sk_X509_new_null()); - bssl::UniquePtr leaf; - for (size_t i = 0; i < certs.size(); i++) { - X509* cert = ParseDERCertificate(certs[i], error_details); - if (!cert) { - return quic::QUIC_FAILURE; - } - if (i == 0) { - leaf.reset(cert); - } else { - sk_X509_push(intermediates.get(), cert); - } - } - - bssl::UniquePtr ctx(X509_STORE_CTX_new()); - X509_STORE* store = SSL_CTX_get_cert_store(context_impl_.chooseSslContexts()); - if (!X509_STORE_CTX_init(ctx.get(), store, leaf.get(), intermediates.get())) { - *error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; - return quic::QUIC_FAILURE; - } - - int res = context_impl_.doVerifyCertChain(ctx.get(), nullptr, std::move(leaf), nullptr); - if (res <= 0) { - const int n = X509_STORE_CTX_get_error(ctx.get()); - const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); - *error_details = absl::StrCat("X509_verify_cert: certificate verification error at depth ", - depth, ": ", X509_verify_cert_error_string(n)); - return quic::QUIC_FAILURE; - } - - std::unique_ptr cert_view = - quic::CertificateView::ParseSingleCertificate(certs[0]); - ASSERT(cert_view != nullptr); - for (const absl::string_view config_san : cert_view->subject_alt_name_domains()) { - std::cerr << "config_san " << config_san << "\n"; - if (config_san == hostname) { - return quic::QUIC_SUCCESS; - } - } - *error_details = absl::StrCat("Leaf certificate doesn't match hostname: ", hostname); - return quic::QUIC_FAILURE; -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h deleted file mode 100644 index 5ad3ca1c57ce..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" -#include "extensions/transport_sockets/tls/context_impl.h" - -namespace Envoy { -namespace Quic { - -class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { -public: - EnvoyQuicProofVerifier(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, - TimeSource& time_source) - : EnvoyQuicProofVerifierBase(), context_impl_(scope, config, time_source) {} - - // EnvoyQuicProofVerifierBase - quic::QuicAsyncStatus - VerifyCertChain(const std::string& hostname, const uint16_t port, - const std::vector& certs, const std::string& ocsp_response, - const std::string& cert_sct, const quic::ProofVerifyContext* context, - std::string* error_details, std::unique_ptr* details, - std::unique_ptr callback) override; - -private: - Extensions::TransportSockets::Tls::ClientContextImpl context_impl_; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc deleted file mode 100644 index 0a6ea3b22c11..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc +++ /dev/null @@ -1,54 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" - -#include "openssl/ssl.h" -#include "quiche/quic/core/crypto/certificate_view.h" -#include "quiche/quic/core/crypto/crypto_protocol.h" -#include "quiche/quic/core/quic_data_writer.h" - -namespace Envoy { -namespace Quic { - -quic::QuicAsyncStatus EnvoyQuicProofVerifierBase::VerifyProof( - const std::string& hostname, const uint16_t port, const std::string& server_config, - quic::QuicTransportVersion /*quic_version*/, absl::string_view chlo_hash, - const std::vector& certs, const std::string& cert_sct, - const std::string& signature, const quic::ProofVerifyContext* context, - std::string* error_details, std::unique_ptr* details, - std::unique_ptr callback) { - quic::QuicAsyncStatus res = VerifyCertChain(hostname, port, certs, "", cert_sct, context, - error_details, details, std::move(callback)); - if (res == quic::QUIC_FAILURE) { - return quic::QUIC_FAILURE; - } - ASSERT(res != quic::QUIC_PENDING); - return verifySignature(server_config, chlo_hash, certs[0], signature) ? quic::QUIC_SUCCESS - : quic::QUIC_FAILURE; -} - -bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_config, - absl::string_view chlo_hash, - const std::string& cert, - const std::string& signature) { - - size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + - server_config.size(); - auto payload = std::make_unique(payload_size); - quic::QuicDataWriter payload_writer(payload_size, payload.get(), - quiche::Endianness::HOST_BYTE_ORDER); - bool success = - payload_writer.WriteBytes(quic::kProofSignatureLabel, sizeof(quic::kProofSignatureLabel)) && - payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && - payload_writer.WriteStringPiece(server_config); - if (!success) { - return false; - } - - std::unique_ptr cert_view = - quic::CertificateView::ParseSingleCertificate(cert); - ASSERT(cert_view != nullptr); - return cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), - signature, SSL_SIGN_RSA_PSS_RSAE_SHA256); -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h deleted file mode 100644 index 854407bbd87b..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "absl/strings/str_cat.h" - -#pragma GCC diagnostic push - -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" - -#include "quiche/quic/core/crypto/proof_verifier.h" -#include "quiche/quic/core/quic_versions.h" - -#pragma GCC diagnostic pop - -namespace Envoy { -namespace Quic { - -// A partial implementation of quic::ProofVerifier which does signature -// verification using SSL_SIGN_RSA_PSS_RSAE_SHA256. -class EnvoyQuicProofVerifierBase : public quic::ProofVerifier { -public: - ~EnvoyQuicProofVerifierBase() override = default; - - // quic::ProofVerifier - // Return success if the certs chain is valid and signature of { - // server_config + chlo_hash} is valid. Otherwise failure. - quic::QuicAsyncStatus - VerifyProof(const std::string& hostname, const uint16_t port, const std::string& server_config, - quic::QuicTransportVersion /*quic_version*/, absl::string_view chlo_hash, - const std::vector& certs, const std::string& cert_sct, - const std::string& signature, const quic::ProofVerifyContext* context, - std::string* error_details, std::unique_ptr* details, - std::unique_ptr callback) override; - - virtual bool verifySignature(const std::string& server_config, absl::string_view chlo_hash, - const std::string& cert, const std::string& signature); - - std::unique_ptr CreateDefaultContext() override { return nullptr; } -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/context_config_impl.h b/source/extensions/transport_sockets/tls/context_config_impl.h index ad2d927d8231..9cfaff0482fb 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.h +++ b/source/extensions/transport_sockets/tls/context_config_impl.h @@ -98,9 +98,6 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::ClientContextConfig { public: - static const std::string DEFAULT_CIPHER_SUITES; - static const std::string DEFAULT_CURVES; - ClientContextConfigImpl( const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& config, absl::string_view sigalgs, @@ -119,6 +116,8 @@ class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Cli private: static const unsigned DEFAULT_MIN_VERSION; static const unsigned DEFAULT_MAX_VERSION; + static const std::string DEFAULT_CIPHER_SUITES; + static const std::string DEFAULT_CURVES; const std::string server_name_indication_; const bool allow_renegotiation_; diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 818e51728715..ff8021f72558 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -525,51 +525,49 @@ int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { ContextImpl* impl = reinterpret_cast(arg); SSL* ssl = reinterpret_cast( X509_STORE_CTX_get_ex_data(store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - return impl->doVerifyCertChain( - store_ctx, + Envoy::Ssl::SslExtendedSocketInfo* sslExtendedInfo = reinterpret_cast( - SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), - bssl::UniquePtr(SSL_get_peer_certificate(ssl)), - static_cast(SSL_get_app_data(ssl))); -} + SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())); -int ContextImpl::doVerifyCertChain( - X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, - bssl::UniquePtr leaf_cert, - const Network::TransportSocketOptions* transport_socket_options) { - if (verify_trusted_ca_) { + if (impl->verify_trusted_ca_) { int ret = X509_verify_cert(store_ctx); - if (ssl_extended_info) { - ssl_extended_info->setCertificateValidationStatus( + if (sslExtendedInfo) { + sslExtendedInfo->setCertificateValidationStatus( ret == 1 ? Envoy::Ssl::ClientValidationStatus::Validated : Envoy::Ssl::ClientValidationStatus::Failed); } if (ret <= 0) { - stats_.fail_verify_error_.inc(); - return allow_untrusted_certificate_ ? 1 : ret; + impl->stats_.fail_verify_error_.inc(); + return impl->allow_untrusted_certificate_ ? 1 : ret; } } - Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( - leaf_cert.get(), + bssl::UniquePtr cert(SSL_get_peer_certificate(ssl)); + + const Network::TransportSocketOptions* transport_socket_options = + static_cast(SSL_get_app_data(ssl)); + + Envoy::Ssl::ClientValidationStatus validated = impl->verifyCertificate( + cert.get(), transport_socket_options && !transport_socket_options->verifySubjectAltNameListOverride().empty() ? transport_socket_options->verifySubjectAltNameListOverride() - : verify_subject_alt_name_list_, - subject_alt_name_matchers_); + : impl->verify_subject_alt_name_list_, + impl->subject_alt_name_matchers_); - if (ssl_extended_info) { - if (ssl_extended_info->certificateValidationStatus() == + if (sslExtendedInfo) { + if (sslExtendedInfo->certificateValidationStatus() == Envoy::Ssl::ClientValidationStatus::NotValidated) { - ssl_extended_info->setCertificateValidationStatus(validated); + sslExtendedInfo->setCertificateValidationStatus(validated); } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { - ssl_extended_info->setCertificateValidationStatus(validated); + sslExtendedInfo->setCertificateValidationStatus(validated); } } - return allow_untrusted_certificate_ ? 1 - : (validated != Envoy::Ssl::ClientValidationStatus::Failed); + return impl->allow_untrusted_certificate_ + ? 1 + : (validated != Envoy::Ssl::ClientValidationStatus::Failed); } Envoy::Ssl::ClientValidationStatus ContextImpl::verifyCertificate( diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 4b07e9cb696e..407dd45f86f8 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -101,13 +101,6 @@ class ContextImpl : public virtual Envoy::Ssl::Context { std::vector getPrivateKeyMethodProviders(); - // Called by verifyCallback to do the actual cert chain verification. - int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, - bssl::UniquePtr leaf_cert, - const Network::TransportSocketOptions* transport_socket_options); - - SSL_CTX* chooseSslContexts() const { return tls_contexts_[0].ssl_ctx_.get(); } - protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source); diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index ce45e7e6e4fa..37eb776630cf 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -49,7 +49,6 @@ envoy_cc_test( deps = [ "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", - "//source/extensions/transport_sockets/tls:context_config_lib", "//test/mocks/network:network_mocks", "//test/mocks/ssl:ssl_mocks", "@com_googlesource_quiche//:quic_core_versions_lib", @@ -57,19 +56,6 @@ envoy_cc_test( ], ) -envoy_cc_test( - name = "envoy_quic_proof_verifier_test", - srcs = ["envoy_quic_proof_verifier_test.cc"], - external_deps = ["quiche_quic_platform"], - tags = ["nofips"], - deps = [ - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", - "//source/extensions/transport_sockets/tls:context_config_lib", - "//test/mocks/ssl:ssl_mocks", - "@com_googlesource_quiche//:quic_test_tools_test_certificates_lib", - ], -) - envoy_cc_test( name = "envoy_quic_server_stream_test", srcs = ["envoy_quic_server_stream_test.cc"], @@ -234,27 +220,18 @@ envoy_cc_test_library( hdrs = ["test_proof_source.h"], tags = ["nofips"], deps = [ - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_base_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_fake_proof_source_lib", "@com_googlesource_quiche//:quic_test_tools_test_certificates_lib", ], ) -envoy_cc_test_library( - name = "test_proof_verifier_lib", - hdrs = ["test_proof_verifier.h"], - tags = ["nofips"], - deps = [ - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_base_lib", - ], -) - envoy_cc_test_library( name = "quic_test_utils_for_envoy_lib", srcs = ["crypto_test_utils_for_envoy.cc"], tags = ["nofips"], deps = [ ":test_proof_source_lib", - ":test_proof_verifier_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", ], ) diff --git a/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc b/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc index cafdce0c6227..c5b7a11d70e3 100644 --- a/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc +++ b/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc @@ -19,7 +19,7 @@ #endif #include -#include "test/extensions/quic_listeners/quiche/test_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" #include "test/extensions/quic_listeners/quiche/test_proof_source.h" namespace quic { @@ -32,7 +32,7 @@ std::unique_ptr ProofSourceForTesting() { // NOLINTNEXTLINE(readability-identifier-naming) std::unique_ptr ProofVerifierForTesting() { - return std::make_unique(); + return std::make_unique(); } // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 72669a6bf8be..fa46665fa7b6 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -2,10 +2,9 @@ #include #include +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_proof_source.h" -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/transport_sockets/tls/context_config_impl.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -24,85 +23,29 @@ namespace Quic { class TestGetProofCallback : public quic::ProofSource::Callback { public: - TestGetProofCallback(bool& called, const std::string& server_config, - quic::QuicTransportVersion& version, quiche::QuicheStringPiece chlo_hash, + TestGetProofCallback(bool& called, std::string leaf_cert_scts, const absl::string_view cert, Network::FilterChain& filter_chain) - : called_(called), server_config_(server_config), version_(version), chlo_hash_(chlo_hash), - expected_filter_chain_(filter_chain) { - ON_CALL(client_context_config_, cipherSuites) - .WillByDefault(ReturnRef( - Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); - ON_CALL(client_context_config_, ecdhCurves) - .WillByDefault( - ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); - const std::string alpn("h2,http/1.1"); - ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); - ; - const std::string empty_string; - ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); - ON_CALL(client_context_config_, signingAlgorithmsForTest()) - .WillByDefault(ReturnRef(empty_string)); - ON_CALL(client_context_config_, certificateValidationContext()) - .WillByDefault(Return(&cert_validation_ctx_config_)); - - // Getting the last cert in the chain as the root CA cert. - std::string cert_chain(quic::test::kTestCertificateChainPem); - const std::string& root_ca_cert = - cert_chain.substr(cert_chain.rfind("-----BEGIN CERTIFICATE-----")); - const std::string path_string("some_path"); - ON_CALL(cert_validation_ctx_config_, caCert()).WillByDefault(ReturnRef(root_ca_cert)); - ON_CALL(cert_validation_ctx_config_, caCertPath()).WillByDefault(ReturnRef(path_string)); - ON_CALL(cert_validation_ctx_config_, trustChainVerification) - .WillByDefault(Return(envoy::extensions::transport_sockets::tls::v3:: - CertificateValidationContext::VERIFY_TRUST_CHAIN)); - ON_CALL(cert_validation_ctx_config_, allowExpiredCertificate()).WillByDefault(Return(true)); - const std::string crl_list; - ON_CALL(cert_validation_ctx_config_, certificateRevocationList()) - .WillByDefault(ReturnRef(crl_list)); - ON_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) - .WillByDefault(ReturnRef(path_string)); - const std::vector empty_string_list; - ON_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) - .WillByDefault(ReturnRef(empty_string_list)); - const std::vector san_matchers; - ON_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) - .WillByDefault(ReturnRef(san_matchers)); - ON_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) - .WillByDefault(ReturnRef(empty_string_list)); - ON_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) - .WillByDefault(ReturnRef(empty_string_list)); - verifier_ = - std::make_unique(store_, client_context_config_, time_system_); - } + : called_(called), expected_leaf_certs_scts_(std::move(leaf_cert_scts)), + expected_leaf_cert_(cert), expected_filter_chain_(filter_chain) {} // quic::ProofSource::Callback void Run(bool ok, const quic::QuicReferenceCountedPointer& chain, const quic::QuicCryptoProof& proof, std::unique_ptr details) override { EXPECT_TRUE(ok); + EXPECT_EQ(expected_leaf_certs_scts_, proof.leaf_cert_scts); EXPECT_EQ(2, chain->certs.size()); + EXPECT_EQ(expected_leaf_cert_, chain->certs[0]); EXPECT_EQ(&expected_filter_chain_, &static_cast(details.get())->filterChain()); - std::string error; - EXPECT_EQ(quic::QUIC_SUCCESS, - verifier_->VerifyProof("www.example.org", 54321, server_config_, version_, chlo_hash_, - chain->certs, proof.leaf_cert_scts, proof.signature, nullptr, - &error, nullptr, nullptr)) - << error; called_ = true; } private: bool& called_; - const std::string& server_config_; - const quic::QuicTransportVersion& version_; - quiche::QuicheStringPiece chlo_hash_; + std::string expected_leaf_certs_scts_; + absl::string_view expected_leaf_cert_; Network::FilterChain& expected_filter_chain_; - NiceMock store_; - Event::GlobalTimeSystem time_system_; - NiceMock client_context_config_; - NiceMock cert_validation_ctx_config_; - std::unique_ptr verifier_; }; class EnvoyQuicProofSourceTest : public ::testing::Test { @@ -113,7 +56,6 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { 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_) {} protected: @@ -121,7 +63,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { quic::QuicSocketAddress server_address_; quic::QuicSocketAddress client_address_; quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash_{"aaaaa"}; + quiche::QuicheStringPiece chlo_hash_{""}; std::string server_config_{"Server Config"}; std::string expected_certs_{quic::test::kTestCertificateChainPem}; std::string pkey_{quic::test::kTestCertificatePrivateKeyPem}; @@ -131,12 +73,13 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; EnvoyQuicProofSource proof_source_; + EnvoyQuicFakeProofVerifier proof_verifier_; }; TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { bool called = false; - auto callback = std::make_unique(called, server_config_, version_, - chlo_hash_, filter_chain_); + auto callback = std::make_unique( + called, "Fake timestamp", quic::test::kTestCertificate, filter_chain_); EXPECT_CALL(listen_socket_, ioHandle()).Times(2); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) .WillRepeatedly(Invoke([&](const Network::ConnectionSocket& connection_socket) { @@ -165,6 +108,15 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, chlo_hash_, std::move(callback)); EXPECT_TRUE(called); + + EXPECT_EQ(quic::QUIC_SUCCESS, + proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, + {"Fake cert"}, "", "fake signature", nullptr, nullptr, + nullptr, nullptr)); + EXPECT_EQ(quic::QUIC_FAILURE, + proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, + {"Fake cert", "Unexpected cert"}, "Fake timestamp", + "fake signature", nullptr, nullptr, nullptr, nullptr)); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc deleted file mode 100644 index 5bd0089c1450..000000000000 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include - -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" -#include "extensions/transport_sockets/tls/context_config_impl.h" - -#include "test/mocks/ssl/mocks.h" -#include "test/mocks/stats/mocks.h" -#include "test/test_common/test_time.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "quiche/quic/core/crypto/certificate_view.h" -#include "quiche/quic/test_tools/test_certificates.h" - -using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; - -namespace Envoy { -namespace Quic { - -class EnvoyQuicProofVerifierTest : public testing::Test { -public: - EnvoyQuicProofVerifierTest() - : leaf_cert_([=]() { - std::stringstream pem_stream(cert_chain_); - std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); - return chain[0]; - }()) { - ON_CALL(client_context_config_, cipherSuites) - .WillByDefault(ReturnRef( - Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); - ON_CALL(client_context_config_, ecdhCurves) - .WillByDefault( - ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); - const std::string alpn("h2,http/1.1"); - ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); - ; - const std::string empty_string; - ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); - ON_CALL(client_context_config_, signingAlgorithmsForTest()) - .WillByDefault(ReturnRef(empty_string)); - ON_CALL(client_context_config_, certificateValidationContext()) - .WillByDefault(Return(&cert_validation_ctx_config_)); - } - - // Since this cert chain contains an expired cert, we can flip allow_expired_cert to test the code - // paths for BoringSSL cert verification success and failure. - void configCertVerificationDetails(bool allow_expired_cert) { - // Getting the last cert in the chain as the root CA cert. - const std::string& root_ca_cert = - cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----")); - const std::string path_string("some_path"); - EXPECT_CALL(cert_validation_ctx_config_, caCert()).WillRepeatedly(ReturnRef(root_ca_cert)); - EXPECT_CALL(cert_validation_ctx_config_, caCertPath()).WillRepeatedly(ReturnRef(path_string)); - EXPECT_CALL(cert_validation_ctx_config_, trustChainVerification) - .WillRepeatedly(Return(envoy::extensions::transport_sockets::tls::v3:: - CertificateValidationContext::VERIFY_TRUST_CHAIN)); - EXPECT_CALL(cert_validation_ctx_config_, allowExpiredCertificate()) - .WillRepeatedly(Return(allow_expired_cert)); - const std::string crl_list; - EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationList()) - .WillRepeatedly(ReturnRef(crl_list)); - EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) - .WillRepeatedly(ReturnRef(path_string)); - const std::vector empty_string_list; - EXPECT_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) - .WillRepeatedly(ReturnRef(empty_string_list)); - const std::vector san_matchers; - EXPECT_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) - .WillRepeatedly(ReturnRef(san_matchers)); - EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) - .WillRepeatedly(ReturnRef(empty_string_list)); - EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) - .WillRepeatedly(ReturnRef(empty_string_list)); - - verifier_ = - std::make_unique(store_, client_context_config_, time_system_); - } - -protected: - const std::string cert_chain_{quic::test::kTestCertificateChainPem}; - std::string leaf_cert_; - NiceMock store_; - Event::GlobalTimeSystem time_system_; - NiceMock client_context_config_; - Ssl::MockCertificateValidationContextConfig cert_validation_ctx_config_; - std::unique_ptr verifier_; -}; - -TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainSuccess) { - configCertVerificationDetails(true); - std::unique_ptr cert_view = - quic::CertificateView::ParseSingleCertificate(leaf_cert_); - const std::string ocsp_response; - const std::string cert_sct; - std::string error_details; - EXPECT_EQ(quic::QUIC_SUCCESS, - verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, - {leaf_cert_}, ocsp_response, cert_sct, nullptr, - &error_details, nullptr, nullptr)) - << error_details; -} - -TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureFromSsl) { - configCertVerificationDetails(false); - std::unique_ptr cert_view = - quic::CertificateView::ParseSingleCertificate(leaf_cert_); - const std::string ocsp_response; - const std::string cert_sct; - std::string error_details; - EXPECT_EQ(quic::QUIC_FAILURE, - verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, - {leaf_cert_}, ocsp_response, cert_sct, nullptr, - &error_details, nullptr, nullptr)) - << error_details; - EXPECT_EQ("X509_verify_cert: certificate verification error at depth 1: certificate has expired", - error_details); -} - -TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureInvalidHost) { - configCertVerificationDetails(true); - std::unique_ptr cert_view = - quic::CertificateView::ParseSingleCertificate(leaf_cert_); - const std::string ocsp_response; - const std::string cert_sct; - std::string error_details; - EXPECT_EQ(quic::QUIC_FAILURE, - verifier_->VerifyCertChain("unknown.org", 54321, {leaf_cert_}, ocsp_response, cert_sct, - nullptr, &error_details, nullptr, nullptr)) - << error_details; - EXPECT_EQ("Leaf certificate doesn't match hostname: unknown.org", error_details); -} -} // namespace Quic -} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index feb256251a20..d7177aee7a04 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -7,7 +7,6 @@ #include "test/config/utility.h" #include "test/integration/http_integration.h" -#include "test/integration/ssl_utility.h" #include "test/test_common/utility.h" #pragma GCC diagnostic push @@ -24,14 +23,12 @@ #include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" #include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/quic_listeners/quiche/quic_transport_socket_factory.h" #include "test/extensions/quic_listeners/quiche/test_utils.h" -#include "extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { namespace Quic { @@ -47,43 +44,6 @@ class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; }; -std::unique_ptr -createQuicClientTransportSocketFactory(const Ssl::ClientSslTransportOptions& options, - Api::Api& api) { - std::string yaml_plain = R"EOF( - common_tls_context: - validation_context: - trusted_ca: - filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" -)EOF"; - envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; - TestUtility::loadFromYaml(TestEnvironment::substitute(yaml_plain), tls_context); - auto* common_context = tls_context.mutable_common_tls_context(); - - if (options.alpn_) { - common_context->add_alpn_protocols("h3"); - } - if (options.san_) { - common_context->mutable_validation_context() - ->add_hidden_envoy_deprecated_verify_subject_alt_name("spiffe://lyft.com/backend-team"); - } - for (const std::string& cipher_suite : options.cipher_suites_) { - common_context->mutable_tls_params()->add_cipher_suites(cipher_suite); - } - if (!options.sni_.empty()) { - tls_context.set_sni(options.sni_); - } - - common_context->mutable_tls_params()->set_tls_minimum_protocol_version(options.tls_version_); - common_context->mutable_tls_params()->set_tls_maximum_protocol_version(options.tls_version_); - - NiceMock mock_factory_ctx; - ON_CALL(mock_factory_ctx, api()).WillByDefault(testing::ReturnRef(api)); - auto cfg = std::make_unique( - tls_context, options.sigalgs_, mock_factory_ctx); - return std::make_unique(std::move(cfg)); -} - class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVersionTest { public: QuicHttpIntegrationTest() @@ -99,7 +59,8 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers SetQuicReloadableFlag(quic_enable_version_draft_25_v3, use_http3); return quic::CurrentSupportedVersions(); }()), - conn_helper_(*dispatcher_), alarm_factory_(*dispatcher_, *conn_helper_.GetClock()), + crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *conn_helper_.GetClock()), injected_resource_filename_(TestEnvironment::temporaryPath("injected_resource")), file_updater_(injected_resource_filename_) {} @@ -120,7 +81,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); quic_connection_ = connection.get(); auto session = std::make_unique( - quic_config_, supported_versions_, std::move(connection), server_id_, crypto_config_.get(), + quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, &push_promise_index_, *dispatcher_, 0); session->Initialize(); return session; @@ -144,7 +105,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers } quic::QuicConnectionId getNextServerDesignatedConnectionId() { - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); // If the cached state indicates that we should use a server-designated // connection ID, then return that connection ID. quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() @@ -207,23 +168,16 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers updateResource(0); HttpIntegrationTest::initialize(); registerTestServerPorts({"http"}); - crypto_config_ = - std::make_unique(std::make_unique( - stats_store_, - createQuicClientTransportSocketFactory( - Ssl::ClientSslTransportOptions().setAlpn(true).setSan(true), *api_) - ->clientContextConfig(), - timeSystem())); } void updateResource(double pressure) { file_updater_.update(absl::StrCat(pressure)); } protected: quic::QuicConfig quic_config_; - quic::QuicServerId server_id_{"lyft.com", 443, false}; + quic::QuicServerId server_id_{"example.com", 443, false}; quic::QuicClientPushPromiseIndex push_promise_index_; quic::ParsedQuicVersionVector supported_versions_; - std::unique_ptr crypto_config_; + quic::QuicCryptoClientConfig crypto_config_; EnvoyQuicConnectionHelper conn_helper_; EnvoyQuicAlarmFactory alarm_factory_; CodecClientCallbacksForTest client_codec_callback_; @@ -333,7 +287,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicListenersWithBPF) { set_reuse_port_ = true; initialize(); std::vector codec_clients; - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter looks at the 1st word of connection id in the packet // header. And currently all QUIC versions support 8 bytes connection id. So @@ -376,7 +330,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicListenersNoBPF) { #undef SO_ATTACH_REUSEPORT_CBPF #endif std::vector codec_clients; - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter looks at the 1st byte of connection id in the packet // header. And currently all QUIC versions support 8 bytes connection id. So diff --git a/test/extensions/quic_listeners/quiche/test_proof_source.h b/test/extensions/quic_listeners/quiche/test_proof_source.h index 93f675793dff..3df1aec6b23a 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_source.h +++ b/test/extensions/quic_listeners/quiche/test_proof_source.h @@ -13,14 +13,14 @@ #endif #include -#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" namespace Envoy { namespace Quic { // A test ProofSource which always provide a hard-coded test certificate in // QUICHE and a fake signature. -class TestProofSource : public EnvoyQuicProofSourceBase { +class TestProofSource : public Quic::EnvoyQuicFakeProofSource { public: quic::QuicReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& /*server_address*/, diff --git a/test/extensions/quic_listeners/quiche/test_proof_verifier.h b/test/extensions/quic_listeners/quiche/test_proof_verifier.h deleted file mode 100644 index 2386f15c61ea..000000000000 --- a/test/extensions/quic_listeners/quiche/test_proof_verifier.h +++ /dev/null @@ -1,28 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" - -namespace Envoy { -namespace Quic { - -// A test quic::ProofVerifier which always approves the certs and signature. -class TestProofVerifier : public EnvoyQuicProofVerifierBase { -public: - // quic::ProofVerifier - quic::QuicAsyncStatus - VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, - const std::vector& /*certs*/, const std::string& /*ocsp_response*/, - const std::string& /*cert_sct*/, const quic::ProofVerifyContext* /*context*/, - std::string* /*error_details*/, - std::unique_ptr* /*details*/, - std::unique_ptr /*callback*/) override { - return quic::QUIC_SUCCESS; - } - - // EnvoyQuicProofVerifierBase - bool verifySignature(const std::string& /*server_config*/, absl::string_view /*chlo_hash*/, - const std::string& /*cert*/, const std::string& /*signature*/) override { - return true; - } -}; - -} // namespace Quic -} // namespace Envoy diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index d60011ca7f98..825822992a7c 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -128,23 +128,6 @@ class MockTlsCertificateConfig : public TlsCertificateConfig { MOCK_METHOD(Envoy::Ssl::PrivateKeyMethodProviderSharedPtr, privateKeyMethod, (), (const)); }; -class MockCertificateValidationContextConfig : public CertificateValidationContextConfig { -public: - MOCK_METHOD(const std::string&, caCert, (), (const)); - MOCK_METHOD(const std::string&, caCertPath, (), (const)); - MOCK_METHOD(const std::string&, certificateRevocationList, (), (const)); - MOCK_METHOD(const std::string&, certificateRevocationListPath, (), (const)); - MOCK_METHOD(const std::vector&, verifySubjectAltNameList, (), (const)); - MOCK_METHOD(const std::vector&, subjectAltNameMatchers, - (), (const)); - MOCK_METHOD(const std::vector&, verifyCertificateHashList, (), (const)); - MOCK_METHOD(const std::vector&, verifyCertificateSpkiList, (), (const)); - MOCK_METHOD(bool, allowExpiredCertificate, (), (const)); - MOCK_METHOD(envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: - TrustChainVerification, - trustChainVerification, (), (const)); -}; - class MockPrivateKeyMethodManager : public PrivateKeyMethodManager { public: MockPrivateKeyMethodManager(); From 6e599bfb54d973c32a3f1e73c07f5b0e96102d7e Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 3 Jul 2020 22:23:51 -0400 Subject: [PATCH 04/28] add back cert verification Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 28 +++- .../quiche/envoy_quic_fake_proof_verifier.h | 61 -------- .../quiche/envoy_quic_proof_source.h | 6 +- .../quiche/envoy_quic_proof_source_base.cc | 47 ++++++ ...ource.h => envoy_quic_proof_source_base.h} | 27 ++-- .../quiche/envoy_quic_proof_verifier.cc | 77 ++++++++++ .../quiche/envoy_quic_proof_verifier.h | 28 ++++ .../quiche/envoy_quic_proof_verifier_base.cc | 54 +++++++ .../quiche/envoy_quic_proof_verifier_base.h | 43 ++++++ .../tls/context_config_impl.h | 5 +- .../transport_sockets/tls/context_impl.cc | 48 ++++--- .../transport_sockets/tls/context_impl.h | 7 + test/extensions/quic_listeners/quiche/BUILD | 27 +++- .../quiche/crypto_test_utils_for_envoy.cc | 4 +- .../quiche/envoy_quic_proof_source_test.cc | 90 +++++++++--- .../quiche/envoy_quic_proof_verifier_test.cc | 136 ++++++++++++++++++ .../integration/quic_http_integration_test.cc | 64 +++++++-- .../quic_listeners/quiche/test_proof_source.h | 4 +- .../quiche/test_proof_verifier.h | 29 ++++ test/mocks/ssl/mocks.h | 17 +++ 20 files changed, 655 insertions(+), 147 deletions(-) delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc rename source/extensions/quic_listeners/quiche/{envoy_quic_fake_proof_source.h => envoy_quic_proof_source_base.h} (74%) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc create mode 100644 test/extensions/quic_listeners/quiche/test_proof_verifier.h diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 2101cbf7eb63..b144a2166ea9 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -62,12 +62,16 @@ envoy_cc_library( ) envoy_cc_library( - name = "envoy_quic_fake_proof_source_lib", - hdrs = ["envoy_quic_fake_proof_source.h"], + name = "envoy_quic_proof_source_base_lib", + srcs = ["envoy_quic_proof_source_base.cc"], + hdrs = ["envoy_quic_proof_source_base.h"], external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ + "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", + "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_crypto_proof_source_interface_lib", + "@com_googlesource_quiche//:quic_core_data_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], ) @@ -79,7 +83,7 @@ envoy_cc_library( external_deps = ["ssl"], tags = ["nofips"], deps = [ - ":envoy_quic_fake_proof_source_lib", + ":envoy_quic_proof_source_base_lib", ":envoy_quic_utils_lib", ":quic_io_handle_wrapper_lib", ":quic_transport_socket_factory_lib", @@ -91,16 +95,30 @@ envoy_cc_library( ) envoy_cc_library( - name = "envoy_quic_proof_verifier_lib", - hdrs = ["envoy_quic_fake_proof_verifier.h"], + name = "envoy_quic_proof_verifier_base_lib", + srcs = ["envoy_quic_proof_verifier_base.cc"], + hdrs = ["envoy_quic_proof_verifier_base.h"], external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ + "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], ) +envoy_cc_library( + name = "envoy_quic_proof_verifier_lib", + srcs = ["envoy_quic_proof_verifier.cc"], + hdrs = ["envoy_quic_proof_verifier.h"], + external_deps = ["quiche_quic_platform"], + tags = ["nofips"], + deps = [ + ":envoy_quic_proof_verifier_base_lib", + "//source/extensions/transport_sockets/tls:context_lib", + ], +) + envoy_cc_library( name = "spdy_server_push_utils_for_envoy_lib", srcs = ["spdy_server_push_utils_for_envoy.cc"], diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h deleted file mode 100644 index af107983317b..000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "absl/strings/str_cat.h" - -#pragma GCC diagnostic push - -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" - -#include "quiche/quic/core/crypto/proof_verifier.h" -#include "quiche/quic/core/quic_versions.h" - -#pragma GCC diagnostic pop - -namespace Envoy { -namespace Quic { - -// A fake implementation of quic::ProofVerifier which approves the certs and -// signature produced by EnvoyQuicFakeProofSource. -class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { -public: - ~EnvoyQuicFakeProofVerifier() override = default; - - // quic::ProofVerifier - // Return success if the certs chain is valid and signature is "Fake signature for { - // [server_config] }". Otherwise failure. - quic::QuicAsyncStatus - VerifyProof(const std::string& hostname, const uint16_t port, - const std::string& /*server_config*/, quic::QuicTransportVersion /*quic_version*/, - absl::string_view /*chlo_hash*/, const std::vector& certs, - const std::string& cert_sct, const std::string& /*signature*/, - const quic::ProofVerifyContext* context, std::string* error_details, - std::unique_ptr* details, - std::unique_ptr callback) override { - if (VerifyCertChain(hostname, port, certs, "", cert_sct, context, error_details, details, - std::move(callback)) == quic::QUIC_SUCCESS) { - return quic::QUIC_SUCCESS; - } - return quic::QUIC_FAILURE; - } - - // Return success upon one arbitrary cert content. Otherwise failure. - quic::QuicAsyncStatus - VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, - const std::vector& certs, const std::string& /*ocsp_response*/, - const std::string& cert_sct, const quic::ProofVerifyContext* /*context*/, - std::string* /*error_details*/, - std::unique_ptr* /*details*/, - std::unique_ptr /*callback*/) override { - // Cert SCT support is not enabled for fake ProofSource. - if (cert_sct.empty() && certs.size() == 1) { - return quic::QUIC_SUCCESS; - } - return quic::QUIC_FAILURE; - } - - std::unique_ptr CreateDefaultContext() override { return nullptr; } -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index c920467078c2..cdba29fec1d0 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -2,7 +2,7 @@ #include "server/connection_handler_impl.h" -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" #include "extensions/quic_listeners/quiche/quic_transport_socket_factory.h" namespace Envoy { @@ -21,13 +21,13 @@ class DetailsWithFilterChain : public quic::ProofSource::Details { const Network::FilterChain& filter_chain_; }; -class EnvoyQuicProofSource : public EnvoyQuicFakeProofSource, +class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase, protected Logger::Loggable { public: EnvoyQuicProofSource(Network::Socket& listen_socket, Network::FilterChainManager& filter_chain_manager, Server::ListenerStats& listener_stats) - : EnvoyQuicFakeProofSource(), listen_socket_(listen_socket), + : EnvoyQuicProofSourceBase(), listen_socket_(listen_socket), filter_chain_manager_(filter_chain_manager), listener_stats_(listener_stats) {} ~EnvoyQuicProofSource() override = default; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc new file mode 100644 index 000000000000..aed5eccacc31 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -0,0 +1,47 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" + +#pragma GCC diagnostic push + +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "quiche/quic/core/quic_data_writer.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, + const std::string& server_config, + quic::QuicTransportVersion /*transport_version*/, + quiche::QuicheStringPiece chlo_hash, + std::unique_ptr callback) { + quic::QuicReferenceCountedPointer chain = + GetCertChain(server_address, client_address, hostname); + + size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + + server_config.size(); + auto payload = std::make_unique(payload_size); + quic::QuicDataWriter payload_writer(payload_size, payload.get(), + quiche::Endianness::HOST_BYTE_ORDER); + bool success = + payload_writer.WriteBytes(quic::kProofSignatureLabel, sizeof(quic::kProofSignatureLabel)) && + payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && + payload_writer.WriteStringPiece(server_config); + if (!success) { + quic::QuicCryptoProof proof; + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } + + // TODO(danzh) Get the signature algorithm from leaf cert. + auto signature_callback = std::make_unique(std::move(callback), chain); + ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, + quiche::QuicheStringPiece(payload.get(), payload_size), + std::move(signature_callback)); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h similarity index 74% rename from source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h rename to source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index 998b3c47b469..3a5aadb7513f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -12,23 +12,24 @@ #pragma GCC diagnostic ignored "-Wunused-parameter" #include "quiche/quic/core/crypto/proof_source.h" #include "quiche/quic/core/quic_versions.h" - +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/platform/api/quic_reference_counted.h" +#include "quiche/quic/platform/api/quic_socket_address.h" +#include "quiche/common/platform/api/quiche_string_piece.h" #pragma GCC diagnostic pop #include "openssl/ssl.h" #include "envoy/network/filter.h" -#include "quiche/quic/platform/api/quic_reference_counted.h" -#include "quiche/quic/platform/api/quic_socket_address.h" -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "server/backtrace.h" namespace Envoy { namespace Quic { -// A fake implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). +// A partial implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). // TODO(danzh) Rename it to EnvoyQuicProofSource once it's fully implemented. -class EnvoyQuicFakeProofSource : public quic::ProofSource { +class EnvoyQuicProofSourceBase : public quic::ProofSource { public: - ~EnvoyQuicFakeProofSource() override = default; + ~EnvoyQuicProofSourceBase() override = default; // quic::ProofSource // Returns a certs chain and its fake SCT "Fake timestamp" and TLS signature wrapped @@ -36,16 +37,8 @@ class EnvoyQuicFakeProofSource : public quic::ProofSource { void GetProof(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece /*chlo_hash*/, - std::unique_ptr callback) override { - quic::QuicReferenceCountedPointer chain = - GetCertChain(server_address, client_address, hostname); - quic::QuicCryptoProof proof; - // TODO(danzh) Get the signature algorithm from leaf cert. - auto signature_callback = std::make_unique(std::move(callback), chain); - ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, - server_config, std::move(signature_callback)); - } + quiche::QuicheStringPiece chlo_hash, + std::unique_ptr callback) override; TicketCrypter* GetTicketCrypter() override { return nullptr; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc new file mode 100644 index 000000000000..3e606633ad7d --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -0,0 +1,77 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" + +#include "quiche/quic/core/crypto/certificate_view.h" + +namespace Envoy { +namespace Quic { + +static X509* ParseDERCertificate(const std::string& der_bytes, std::string* error_details) { + const uint8_t* data; + const uint8_t* orig_data; + orig_data = data = reinterpret_cast(der_bytes.data()); + bssl::UniquePtr cert(d2i_X509(nullptr, &data, der_bytes.size())); + if (!cert.get()) { + *error_details = "d2i_X509"; + return nullptr; + } + if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { + // Trailing garbage. + return nullptr; + } + return cert.release(); +} + +quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( + const std::string& hostname, const uint16_t /*port*/, const std::vector& certs, + const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/, + const quic::ProofVerifyContext* /*context*/, std::string* error_details, + std::unique_ptr* /*details*/, + std::unique_ptr /*callback*/) { + if (certs.size() == 0) { + return quic::QUIC_FAILURE; + } + bssl::UniquePtr intermediates(sk_X509_new_null()); + bssl::UniquePtr leaf; + for (size_t i = 0; i < certs.size(); i++) { + X509* cert = ParseDERCertificate(certs[i], error_details); + if (!cert) { + return quic::QUIC_FAILURE; + } + if (i == 0) { + leaf.reset(cert); + } else { + sk_X509_push(intermediates.get(), cert); + } + } + + bssl::UniquePtr ctx(X509_STORE_CTX_new()); + X509_STORE* store = SSL_CTX_get_cert_store(context_impl_.chooseSslContexts()); + if (!X509_STORE_CTX_init(ctx.get(), store, leaf.get(), intermediates.get())) { + *error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; + return quic::QUIC_FAILURE; + } + + int res = context_impl_.doVerifyCertChain(ctx.get(), nullptr, std::move(leaf), nullptr); + if (res <= 0) { + const int n = X509_STORE_CTX_get_error(ctx.get()); + const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); + *error_details = absl::StrCat("X509_verify_cert: certificate verification error at depth ", + depth, ": ", X509_verify_cert_error_string(n)); + return quic::QUIC_FAILURE; + } + + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(certs[0]); + ASSERT(cert_view != nullptr); + for (const absl::string_view config_san : cert_view->subject_alt_name_domains()) { + std::cerr << "config_san " << config_san << "\n"; + if (config_san == hostname) { + return quic::QUIC_SUCCESS; + } + } + *error_details = absl::StrCat("Leaf certificate doesn't match hostname: ", hostname); + return quic::QUIC_FAILURE; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h new file mode 100644 index 000000000000..5ad3ca1c57ce --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h @@ -0,0 +1,28 @@ +#pragma once + +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" +#include "extensions/transport_sockets/tls/context_impl.h" + +namespace Envoy { +namespace Quic { + +class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { +public: + EnvoyQuicProofVerifier(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, + TimeSource& time_source) + : EnvoyQuicProofVerifierBase(), context_impl_(scope, config, time_source) {} + + // EnvoyQuicProofVerifierBase + quic::QuicAsyncStatus + VerifyCertChain(const std::string& hostname, const uint16_t port, + const std::vector& certs, const std::string& ocsp_response, + const std::string& cert_sct, const quic::ProofVerifyContext* context, + std::string* error_details, std::unique_ptr* details, + std::unique_ptr callback) override; + +private: + Extensions::TransportSockets::Tls::ClientContextImpl context_impl_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc new file mode 100644 index 000000000000..0a6ea3b22c11 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -0,0 +1,54 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" + +#include "openssl/ssl.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/core/quic_data_writer.h" + +namespace Envoy { +namespace Quic { + +quic::QuicAsyncStatus EnvoyQuicProofVerifierBase::VerifyProof( + const std::string& hostname, const uint16_t port, const std::string& server_config, + quic::QuicTransportVersion /*quic_version*/, absl::string_view chlo_hash, + const std::vector& certs, const std::string& cert_sct, + const std::string& signature, const quic::ProofVerifyContext* context, + std::string* error_details, std::unique_ptr* details, + std::unique_ptr callback) { + quic::QuicAsyncStatus res = VerifyCertChain(hostname, port, certs, "", cert_sct, context, + error_details, details, std::move(callback)); + if (res == quic::QUIC_FAILURE) { + return quic::QUIC_FAILURE; + } + ASSERT(res != quic::QUIC_PENDING); + return verifySignature(server_config, chlo_hash, certs[0], signature) ? quic::QUIC_SUCCESS + : quic::QUIC_FAILURE; +} + +bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_config, + absl::string_view chlo_hash, + const std::string& cert, + const std::string& signature) { + + size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + + server_config.size(); + auto payload = std::make_unique(payload_size); + quic::QuicDataWriter payload_writer(payload_size, payload.get(), + quiche::Endianness::HOST_BYTE_ORDER); + bool success = + payload_writer.WriteBytes(quic::kProofSignatureLabel, sizeof(quic::kProofSignatureLabel)) && + payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && + payload_writer.WriteStringPiece(server_config); + if (!success) { + return false; + } + + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(cert); + ASSERT(cert_view != nullptr); + return cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), + signature, SSL_SIGN_RSA_PSS_RSAE_SHA256); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h new file mode 100644 index 000000000000..f42ad37e145f --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h @@ -0,0 +1,43 @@ +#pragma once + +#include "absl/strings/str_cat.h" + +#pragma GCC diagnostic push + +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#include "quiche/quic/core/crypto/proof_verifier.h" +#include "quiche/quic/core/quic_versions.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +// A partial implementation of quic::ProofVerifier which does signature +// verification using SSL_SIGN_RSA_PSS_RSAE_SHA256. +class EnvoyQuicProofVerifierBase : public quic::ProofVerifier { +public: + ~EnvoyQuicProofVerifierBase() override = default; + + // quic::ProofVerifier + // Return success if the certs chain is valid and signature of { + // server_config + chlo_hash} is valid. Otherwise failure. + quic::QuicAsyncStatus + VerifyProof(const std::string& hostname, const uint16_t port, const std::string& server_config, + quic::QuicTransportVersion /*quic_version*/, absl::string_view chlo_hash, + const std::vector& certs, const std::string& cert_sct, + const std::string& signature, const quic::ProofVerifyContext* context, + std::string* error_details, std::unique_ptr* details, + std::unique_ptr callback) override; + + std::unique_ptr CreateDefaultContext() override { return nullptr; } + +protected: + virtual bool verifySignature(const std::string& server_config, absl::string_view chlo_hash, + const std::string& cert, const std::string& signature); +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/context_config_impl.h b/source/extensions/transport_sockets/tls/context_config_impl.h index 9cfaff0482fb..ad2d927d8231 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.h +++ b/source/extensions/transport_sockets/tls/context_config_impl.h @@ -98,6 +98,9 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::ClientContextConfig { public: + static const std::string DEFAULT_CIPHER_SUITES; + static const std::string DEFAULT_CURVES; + ClientContextConfigImpl( const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& config, absl::string_view sigalgs, @@ -116,8 +119,6 @@ class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Cli private: static const unsigned DEFAULT_MIN_VERSION; static const unsigned DEFAULT_MAX_VERSION; - static const std::string DEFAULT_CIPHER_SUITES; - static const std::string DEFAULT_CURVES; const std::string server_name_indication_; const bool allow_renegotiation_; diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index ff8021f72558..818e51728715 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -525,49 +525,51 @@ int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { ContextImpl* impl = reinterpret_cast(arg); SSL* ssl = reinterpret_cast( X509_STORE_CTX_get_ex_data(store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - Envoy::Ssl::SslExtendedSocketInfo* sslExtendedInfo = + return impl->doVerifyCertChain( + store_ctx, reinterpret_cast( - SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())); + SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), + bssl::UniquePtr(SSL_get_peer_certificate(ssl)), + static_cast(SSL_get_app_data(ssl))); +} - if (impl->verify_trusted_ca_) { +int ContextImpl::doVerifyCertChain( + X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + bssl::UniquePtr leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) { + if (verify_trusted_ca_) { int ret = X509_verify_cert(store_ctx); - if (sslExtendedInfo) { - sslExtendedInfo->setCertificateValidationStatus( + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus( ret == 1 ? Envoy::Ssl::ClientValidationStatus::Validated : Envoy::Ssl::ClientValidationStatus::Failed); } if (ret <= 0) { - impl->stats_.fail_verify_error_.inc(); - return impl->allow_untrusted_certificate_ ? 1 : ret; + stats_.fail_verify_error_.inc(); + return allow_untrusted_certificate_ ? 1 : ret; } } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl)); - - const Network::TransportSocketOptions* transport_socket_options = - static_cast(SSL_get_app_data(ssl)); - - Envoy::Ssl::ClientValidationStatus validated = impl->verifyCertificate( - cert.get(), + Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( + leaf_cert.get(), transport_socket_options && !transport_socket_options->verifySubjectAltNameListOverride().empty() ? transport_socket_options->verifySubjectAltNameListOverride() - : impl->verify_subject_alt_name_list_, - impl->subject_alt_name_matchers_); + : verify_subject_alt_name_list_, + subject_alt_name_matchers_); - if (sslExtendedInfo) { - if (sslExtendedInfo->certificateValidationStatus() == + if (ssl_extended_info) { + if (ssl_extended_info->certificateValidationStatus() == Envoy::Ssl::ClientValidationStatus::NotValidated) { - sslExtendedInfo->setCertificateValidationStatus(validated); + ssl_extended_info->setCertificateValidationStatus(validated); } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { - sslExtendedInfo->setCertificateValidationStatus(validated); + ssl_extended_info->setCertificateValidationStatus(validated); } } - return impl->allow_untrusted_certificate_ - ? 1 - : (validated != Envoy::Ssl::ClientValidationStatus::Failed); + return allow_untrusted_certificate_ ? 1 + : (validated != Envoy::Ssl::ClientValidationStatus::Failed); } Envoy::Ssl::ClientValidationStatus ContextImpl::verifyCertificate( diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 407dd45f86f8..4b07e9cb696e 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -101,6 +101,13 @@ class ContextImpl : public virtual Envoy::Ssl::Context { std::vector getPrivateKeyMethodProviders(); + // Called by verifyCallback to do the actual cert chain verification. + int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + bssl::UniquePtr leaf_cert, + const Network::TransportSocketOptions* transport_socket_options); + + SSL_CTX* chooseSslContexts() const { return tls_contexts_[0].ssl_ctx_.get(); } + protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source); diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 37eb776630cf..ce45e7e6e4fa 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -49,6 +49,7 @@ envoy_cc_test( deps = [ "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/transport_sockets/tls:context_config_lib", "//test/mocks/network:network_mocks", "//test/mocks/ssl:ssl_mocks", "@com_googlesource_quiche//:quic_core_versions_lib", @@ -56,6 +57,19 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "envoy_quic_proof_verifier_test", + srcs = ["envoy_quic_proof_verifier_test.cc"], + external_deps = ["quiche_quic_platform"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/transport_sockets/tls:context_config_lib", + "//test/mocks/ssl:ssl_mocks", + "@com_googlesource_quiche//:quic_test_tools_test_certificates_lib", + ], +) + envoy_cc_test( name = "envoy_quic_server_stream_test", srcs = ["envoy_quic_server_stream_test.cc"], @@ -220,18 +234,27 @@ envoy_cc_test_library( hdrs = ["test_proof_source.h"], tags = ["nofips"], deps = [ - "//source/extensions/quic_listeners/quiche:envoy_quic_fake_proof_source_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_base_lib", "@com_googlesource_quiche//:quic_test_tools_test_certificates_lib", ], ) +envoy_cc_test_library( + name = "test_proof_verifier_lib", + hdrs = ["test_proof_verifier.h"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_base_lib", + ], +) + envoy_cc_test_library( name = "quic_test_utils_for_envoy_lib", srcs = ["crypto_test_utils_for_envoy.cc"], tags = ["nofips"], deps = [ ":test_proof_source_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + ":test_proof_verifier_lib", "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", ], ) diff --git a/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc b/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc index c5b7a11d70e3..cafdce0c6227 100644 --- a/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc +++ b/test/extensions/quic_listeners/quiche/crypto_test_utils_for_envoy.cc @@ -19,7 +19,7 @@ #endif #include -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "test/extensions/quic_listeners/quiche/test_proof_verifier.h" #include "test/extensions/quic_listeners/quiche/test_proof_source.h" namespace quic { @@ -32,7 +32,7 @@ std::unique_ptr ProofSourceForTesting() { // NOLINTNEXTLINE(readability-identifier-naming) std::unique_ptr ProofVerifierForTesting() { - return std::make_unique(); + return std::make_unique(); } // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index fa46665fa7b6..72669a6bf8be 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -2,9 +2,10 @@ #include #include -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -23,29 +24,85 @@ namespace Quic { class TestGetProofCallback : public quic::ProofSource::Callback { public: - TestGetProofCallback(bool& called, std::string leaf_cert_scts, const absl::string_view cert, + TestGetProofCallback(bool& called, const std::string& server_config, + quic::QuicTransportVersion& version, quiche::QuicheStringPiece chlo_hash, Network::FilterChain& filter_chain) - : called_(called), expected_leaf_certs_scts_(std::move(leaf_cert_scts)), - expected_leaf_cert_(cert), expected_filter_chain_(filter_chain) {} + : called_(called), server_config_(server_config), version_(version), chlo_hash_(chlo_hash), + expected_filter_chain_(filter_chain) { + ON_CALL(client_context_config_, cipherSuites) + .WillByDefault(ReturnRef( + Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); + ON_CALL(client_context_config_, ecdhCurves) + .WillByDefault( + ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); + const std::string alpn("h2,http/1.1"); + ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); + ; + const std::string empty_string; + ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, signingAlgorithmsForTest()) + .WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, certificateValidationContext()) + .WillByDefault(Return(&cert_validation_ctx_config_)); + + // Getting the last cert in the chain as the root CA cert. + std::string cert_chain(quic::test::kTestCertificateChainPem); + const std::string& root_ca_cert = + cert_chain.substr(cert_chain.rfind("-----BEGIN CERTIFICATE-----")); + const std::string path_string("some_path"); + ON_CALL(cert_validation_ctx_config_, caCert()).WillByDefault(ReturnRef(root_ca_cert)); + ON_CALL(cert_validation_ctx_config_, caCertPath()).WillByDefault(ReturnRef(path_string)); + ON_CALL(cert_validation_ctx_config_, trustChainVerification) + .WillByDefault(Return(envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::VERIFY_TRUST_CHAIN)); + ON_CALL(cert_validation_ctx_config_, allowExpiredCertificate()).WillByDefault(Return(true)); + const std::string crl_list; + ON_CALL(cert_validation_ctx_config_, certificateRevocationList()) + .WillByDefault(ReturnRef(crl_list)); + ON_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) + .WillByDefault(ReturnRef(path_string)); + const std::vector empty_string_list; + ON_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) + .WillByDefault(ReturnRef(empty_string_list)); + const std::vector san_matchers; + ON_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) + .WillByDefault(ReturnRef(san_matchers)); + ON_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) + .WillByDefault(ReturnRef(empty_string_list)); + ON_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) + .WillByDefault(ReturnRef(empty_string_list)); + verifier_ = + std::make_unique(store_, client_context_config_, time_system_); + } // quic::ProofSource::Callback void Run(bool ok, const quic::QuicReferenceCountedPointer& chain, const quic::QuicCryptoProof& proof, std::unique_ptr details) override { EXPECT_TRUE(ok); - EXPECT_EQ(expected_leaf_certs_scts_, proof.leaf_cert_scts); EXPECT_EQ(2, chain->certs.size()); - EXPECT_EQ(expected_leaf_cert_, chain->certs[0]); EXPECT_EQ(&expected_filter_chain_, &static_cast(details.get())->filterChain()); + std::string error; + EXPECT_EQ(quic::QUIC_SUCCESS, + verifier_->VerifyProof("www.example.org", 54321, server_config_, version_, chlo_hash_, + chain->certs, proof.leaf_cert_scts, proof.signature, nullptr, + &error, nullptr, nullptr)) + << error; called_ = true; } private: bool& called_; - std::string expected_leaf_certs_scts_; - absl::string_view expected_leaf_cert_; + const std::string& server_config_; + const quic::QuicTransportVersion& version_; + quiche::QuicheStringPiece chlo_hash_; Network::FilterChain& expected_filter_chain_; + NiceMock store_; + Event::GlobalTimeSystem time_system_; + NiceMock client_context_config_; + NiceMock cert_validation_ctx_config_; + std::unique_ptr verifier_; }; class EnvoyQuicProofSourceTest : public ::testing::Test { @@ -56,6 +113,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { 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_) {} protected: @@ -63,7 +121,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { quic::QuicSocketAddress server_address_; quic::QuicSocketAddress client_address_; quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash_{""}; + quiche::QuicheStringPiece chlo_hash_{"aaaaa"}; std::string server_config_{"Server Config"}; std::string expected_certs_{quic::test::kTestCertificateChainPem}; std::string pkey_{quic::test::kTestCertificatePrivateKeyPem}; @@ -73,13 +131,12 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; EnvoyQuicProofSource proof_source_; - EnvoyQuicFakeProofVerifier proof_verifier_; }; TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { bool called = false; - auto callback = std::make_unique( - called, "Fake timestamp", quic::test::kTestCertificate, filter_chain_); + auto callback = std::make_unique(called, server_config_, version_, + chlo_hash_, filter_chain_); EXPECT_CALL(listen_socket_, ioHandle()).Times(2); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) .WillRepeatedly(Invoke([&](const Network::ConnectionSocket& connection_socket) { @@ -108,15 +165,6 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, chlo_hash_, std::move(callback)); EXPECT_TRUE(called); - - EXPECT_EQ(quic::QUIC_SUCCESS, - proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - {"Fake cert"}, "", "fake signature", nullptr, nullptr, - nullptr, nullptr)); - EXPECT_EQ(quic::QUIC_FAILURE, - proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - {"Fake cert", "Unexpected cert"}, "Fake timestamp", - "fake signature", nullptr, nullptr, nullptr, nullptr)); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc new file mode 100644 index 000000000000..5bd0089c1450 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -0,0 +1,136 @@ +#include +#include + +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" + +#include "test/mocks/ssl/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/test_time.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "quiche/quic/core/crypto/certificate_view.h" +#include "quiche/quic/test_tools/test_certificates.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Quic { + +class EnvoyQuicProofVerifierTest : public testing::Test { +public: + EnvoyQuicProofVerifierTest() + : leaf_cert_([=]() { + std::stringstream pem_stream(cert_chain_); + std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); + return chain[0]; + }()) { + ON_CALL(client_context_config_, cipherSuites) + .WillByDefault(ReturnRef( + Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); + ON_CALL(client_context_config_, ecdhCurves) + .WillByDefault( + ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); + const std::string alpn("h2,http/1.1"); + ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); + ; + const std::string empty_string; + ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, signingAlgorithmsForTest()) + .WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, certificateValidationContext()) + .WillByDefault(Return(&cert_validation_ctx_config_)); + } + + // Since this cert chain contains an expired cert, we can flip allow_expired_cert to test the code + // paths for BoringSSL cert verification success and failure. + void configCertVerificationDetails(bool allow_expired_cert) { + // Getting the last cert in the chain as the root CA cert. + const std::string& root_ca_cert = + cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----")); + const std::string path_string("some_path"); + EXPECT_CALL(cert_validation_ctx_config_, caCert()).WillRepeatedly(ReturnRef(root_ca_cert)); + EXPECT_CALL(cert_validation_ctx_config_, caCertPath()).WillRepeatedly(ReturnRef(path_string)); + EXPECT_CALL(cert_validation_ctx_config_, trustChainVerification) + .WillRepeatedly(Return(envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::VERIFY_TRUST_CHAIN)); + EXPECT_CALL(cert_validation_ctx_config_, allowExpiredCertificate()) + .WillRepeatedly(Return(allow_expired_cert)); + const std::string crl_list; + EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationList()) + .WillRepeatedly(ReturnRef(crl_list)); + EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) + .WillRepeatedly(ReturnRef(path_string)); + const std::vector empty_string_list; + EXPECT_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) + .WillRepeatedly(ReturnRef(empty_string_list)); + const std::vector san_matchers; + EXPECT_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) + .WillRepeatedly(ReturnRef(san_matchers)); + EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) + .WillRepeatedly(ReturnRef(empty_string_list)); + EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) + .WillRepeatedly(ReturnRef(empty_string_list)); + + verifier_ = + std::make_unique(store_, client_context_config_, time_system_); + } + +protected: + const std::string cert_chain_{quic::test::kTestCertificateChainPem}; + std::string leaf_cert_; + NiceMock store_; + Event::GlobalTimeSystem time_system_; + NiceMock client_context_config_; + Ssl::MockCertificateValidationContextConfig cert_validation_ctx_config_; + std::unique_ptr verifier_; +}; + +TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainSuccess) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_SUCCESS, + verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + {leaf_cert_}, ocsp_response, cert_sct, nullptr, + &error_details, nullptr, nullptr)) + << error_details; +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureFromSsl) { + configCertVerificationDetails(false); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + {leaf_cert_}, ocsp_response, cert_sct, nullptr, + &error_details, nullptr, nullptr)) + << error_details; + EXPECT_EQ("X509_verify_cert: certificate verification error at depth 1: certificate has expired", + error_details); +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureInvalidHost) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain("unknown.org", 54321, {leaf_cert_}, ocsp_response, cert_sct, + nullptr, &error_details, nullptr, nullptr)) + << error_details; + EXPECT_EQ("Leaf certificate doesn't match hostname: unknown.org", error_details); +} +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index d7177aee7a04..feb256251a20 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -7,6 +7,7 @@ #include "test/config/utility.h" #include "test/integration/http_integration.h" +#include "test/integration/ssl_utility.h" #include "test/test_common/utility.h" #pragma GCC diagnostic push @@ -23,12 +24,14 @@ #include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" #include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/quic_transport_socket_factory.h" #include "test/extensions/quic_listeners/quiche/test_utils.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { namespace Quic { @@ -44,6 +47,43 @@ class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; }; +std::unique_ptr +createQuicClientTransportSocketFactory(const Ssl::ClientSslTransportOptions& options, + Api::Api& api) { + std::string yaml_plain = R"EOF( + common_tls_context: + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" +)EOF"; + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml_plain), tls_context); + auto* common_context = tls_context.mutable_common_tls_context(); + + if (options.alpn_) { + common_context->add_alpn_protocols("h3"); + } + if (options.san_) { + common_context->mutable_validation_context() + ->add_hidden_envoy_deprecated_verify_subject_alt_name("spiffe://lyft.com/backend-team"); + } + for (const std::string& cipher_suite : options.cipher_suites_) { + common_context->mutable_tls_params()->add_cipher_suites(cipher_suite); + } + if (!options.sni_.empty()) { + tls_context.set_sni(options.sni_); + } + + common_context->mutable_tls_params()->set_tls_minimum_protocol_version(options.tls_version_); + common_context->mutable_tls_params()->set_tls_maximum_protocol_version(options.tls_version_); + + NiceMock mock_factory_ctx; + ON_CALL(mock_factory_ctx, api()).WillByDefault(testing::ReturnRef(api)); + auto cfg = std::make_unique( + tls_context, options.sigalgs_, mock_factory_ctx); + return std::make_unique(std::move(cfg)); +} + class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVersionTest { public: QuicHttpIntegrationTest() @@ -59,8 +99,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers SetQuicReloadableFlag(quic_enable_version_draft_25_v3, use_http3); return quic::CurrentSupportedVersions(); }()), - crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), - alarm_factory_(*dispatcher_, *conn_helper_.GetClock()), + conn_helper_(*dispatcher_), alarm_factory_(*dispatcher_, *conn_helper_.GetClock()), injected_resource_filename_(TestEnvironment::temporaryPath("injected_resource")), file_updater_(injected_resource_filename_) {} @@ -81,7 +120,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); quic_connection_ = connection.get(); auto session = std::make_unique( - quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, + quic_config_, supported_versions_, std::move(connection), server_id_, crypto_config_.get(), &push_promise_index_, *dispatcher_, 0); session->Initialize(); return session; @@ -105,7 +144,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers } quic::QuicConnectionId getNextServerDesignatedConnectionId() { - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); // If the cached state indicates that we should use a server-designated // connection ID, then return that connection ID. quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() @@ -168,16 +207,23 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers updateResource(0); HttpIntegrationTest::initialize(); registerTestServerPorts({"http"}); + crypto_config_ = + std::make_unique(std::make_unique( + stats_store_, + createQuicClientTransportSocketFactory( + Ssl::ClientSslTransportOptions().setAlpn(true).setSan(true), *api_) + ->clientContextConfig(), + timeSystem())); } void updateResource(double pressure) { file_updater_.update(absl::StrCat(pressure)); } protected: quic::QuicConfig quic_config_; - quic::QuicServerId server_id_{"example.com", 443, false}; + quic::QuicServerId server_id_{"lyft.com", 443, false}; quic::QuicClientPushPromiseIndex push_promise_index_; quic::ParsedQuicVersionVector supported_versions_; - quic::QuicCryptoClientConfig crypto_config_; + std::unique_ptr crypto_config_; EnvoyQuicConnectionHelper conn_helper_; EnvoyQuicAlarmFactory alarm_factory_; CodecClientCallbacksForTest client_codec_callback_; @@ -287,7 +333,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicListenersWithBPF) { set_reuse_port_ = true; initialize(); std::vector codec_clients; - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter looks at the 1st word of connection id in the packet // header. And currently all QUIC versions support 8 bytes connection id. So @@ -330,7 +376,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicListenersNoBPF) { #undef SO_ATTACH_REUSEPORT_CBPF #endif std::vector codec_clients; - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter looks at the 1st byte of connection id in the packet // header. And currently all QUIC versions support 8 bytes connection id. So diff --git a/test/extensions/quic_listeners/quiche/test_proof_source.h b/test/extensions/quic_listeners/quiche/test_proof_source.h index 3df1aec6b23a..93f675793dff 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_source.h +++ b/test/extensions/quic_listeners/quiche/test_proof_source.h @@ -13,14 +13,14 @@ #endif #include -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h" namespace Envoy { namespace Quic { // A test ProofSource which always provide a hard-coded test certificate in // QUICHE and a fake signature. -class TestProofSource : public Quic::EnvoyQuicFakeProofSource { +class TestProofSource : public EnvoyQuicProofSourceBase { public: quic::QuicReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& /*server_address*/, diff --git a/test/extensions/quic_listeners/quiche/test_proof_verifier.h b/test/extensions/quic_listeners/quiche/test_proof_verifier.h new file mode 100644 index 000000000000..0ef2a7a31c6f --- /dev/null +++ b/test/extensions/quic_listeners/quiche/test_proof_verifier.h @@ -0,0 +1,29 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" + +namespace Envoy { +namespace Quic { + +// A test quic::ProofVerifier which always approves the certs and signature. +class TestProofVerifier : public EnvoyQuicProofVerifierBase { +public: + // quic::ProofVerifier + quic::QuicAsyncStatus + VerifyCertChain(const std::string& /*hostname*/, const uint16_t /*port*/, + const std::vector& /*certs*/, const std::string& /*ocsp_response*/, + const std::string& /*cert_sct*/, const quic::ProofVerifyContext* /*context*/, + std::string* /*error_details*/, + std::unique_ptr* /*details*/, + std::unique_ptr /*callback*/) override { + return quic::QUIC_SUCCESS; + } + +protected: + // EnvoyQuicProofVerifierBase + bool verifySignature(const std::string& /*server_config*/, absl::string_view /*chlo_hash*/, + const std::string& /*cert*/, const std::string& /*signature*/) override { + return true; + } +}; + +} // namespace Quic +} // namespace Envoy diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 825822992a7c..d60011ca7f98 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -128,6 +128,23 @@ class MockTlsCertificateConfig : public TlsCertificateConfig { MOCK_METHOD(Envoy::Ssl::PrivateKeyMethodProviderSharedPtr, privateKeyMethod, (), (const)); }; +class MockCertificateValidationContextConfig : public CertificateValidationContextConfig { +public: + MOCK_METHOD(const std::string&, caCert, (), (const)); + MOCK_METHOD(const std::string&, caCertPath, (), (const)); + MOCK_METHOD(const std::string&, certificateRevocationList, (), (const)); + MOCK_METHOD(const std::string&, certificateRevocationListPath, (), (const)); + MOCK_METHOD(const std::vector&, verifySubjectAltNameList, (), (const)); + MOCK_METHOD(const std::vector&, subjectAltNameMatchers, + (), (const)); + MOCK_METHOD(const std::vector&, verifyCertificateHashList, (), (const)); + MOCK_METHOD(const std::vector&, verifyCertificateSpkiList, (), (const)); + MOCK_METHOD(bool, allowExpiredCertificate, (), (const)); + MOCK_METHOD(envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification, + trustChainVerification, (), (const)); +}; + class MockPrivateKeyMethodManager : public PrivateKeyMethodManager { public: MockPrivateKeyMethodManager(); From 67fad576508c052a1ab3703679d92ddd6a2603d4 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 3 Jul 2020 22:44:35 -0400 Subject: [PATCH 05/28] comment Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier.cc | 2 ++ source/extensions/transport_sockets/tls/context_impl.h | 1 + 2 files changed, 3 insertions(+) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index 3e606633ad7d..1771ed428b43 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -45,6 +45,8 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( } bssl::UniquePtr ctx(X509_STORE_CTX_new()); + // It doesn't matter which SSL context is used, because they share the same + // cert validation config. X509_STORE* store = SSL_CTX_get_cert_store(context_impl_.chooseSslContexts()); if (!X509_STORE_CTX_init(ctx.get(), store, leaf.get(), intermediates.get())) { *error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 4b07e9cb696e..74d9f9b459ed 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -106,6 +106,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { bssl::UniquePtr leaf_cert, const Network::TransportSocketOptions* transport_socket_options); + // Always return the first SSL context in the config. SSL_CTX* chooseSslContexts() const { return tls_contexts_[0].ssl_ctx_.get(); } protected: From 010661c769e2675631569ec870274a59c2d289c0 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 13 Jul 2020 16:40:13 -0400 Subject: [PATCH 06/28] add comment Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_source.h | 15 ++------------- .../quiche/envoy_quic_proof_verifier.h | 2 ++ .../quiche/envoy_quic_proof_source_test.cc | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index 3c537ff91a2b..d2ab6f355550 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -8,19 +8,7 @@ namespace Envoy { namespace Quic { -class DetailsWithFilterChain : public quic::ProofSource::Details { -public: - explicit DetailsWithFilterChain(const Network::FilterChain& filter_chain) - : filter_chain_(filter_chain) {} - DetailsWithFilterChain(const DetailsWithFilterChain& other) - : filter_chain_(other.filter_chain_) {} - - const Network::FilterChain& filterChain() const { return filter_chain_; } - -private: - const Network::FilterChain& filter_chain_; -}; - +// A ProofSource implementation which supplies a proof instance with certs from filter chain. class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase, protected Logger::Loggable { public: @@ -32,6 +20,7 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase, ~EnvoyQuicProofSource() override = default; + // quic::ProofSource quic::QuicReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname) override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h index 5ad3ca1c57ce..7530a57b8279 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h @@ -6,6 +6,8 @@ namespace Envoy { namespace Quic { +// A quic::ProofVerifier implementation which verifies cert chain using SSL +// client context config. class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { public: EnvoyQuicProofVerifier(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 3d8fefb9fffc..67de5d1d8bc0 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -82,7 +82,7 @@ class TestGetProofCallback : public quic::ProofSource::Callback { EXPECT_TRUE(ok); EXPECT_EQ(2, chain->certs.size()); EXPECT_EQ(&expected_filter_chain_, - &static_cast(details.get())->filterChain()); + &static_cast(details.get())->filterChain()); std::string error; EXPECT_EQ(quic::QUIC_SUCCESS, verifier_->VerifyProof("www.example.org", 54321, server_config_, version_, chlo_hash_, From 7ac6b81781e8c094181e59020286fd7715d40737 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 14 Jul 2020 12:04:07 -0400 Subject: [PATCH 07/28] spell Signed-off-by: Dan Zhang --- tools/spelling/spelling_dictionary.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 4b8f9a058a24..de931274b6fe 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -231,6 +231,7 @@ PostCBs PREBIND PRNG PROT +PSS Postgre Postgres Prereq @@ -256,6 +257,7 @@ RLS RNG RPC RSA +RSAE RST RTDS RTTI From 562bbd23728356b53f16d45b7e9091bfef437743 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 14 Jul 2020 12:15:18 -0400 Subject: [PATCH 08/28] remove redudant check Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_source_test.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 67de5d1d8bc0..00927499480a 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -81,8 +81,6 @@ class TestGetProofCallback : public quic::ProofSource::Callback { std::unique_ptr details) override { EXPECT_TRUE(ok); EXPECT_EQ(2, chain->certs.size()); - EXPECT_EQ(&expected_filter_chain_, - &static_cast(details.get())->filterChain()); std::string error; EXPECT_EQ(quic::QUIC_SUCCESS, verifier_->VerifyProof("www.example.org", 54321, server_config_, version_, chlo_hash_, From 66e0383fdc28f31f13df942dbd71fed6824398d6 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 14 Jul 2020 14:48:44 -0400 Subject: [PATCH 09/28] fix asan Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier.cc | 1 - .../quic_listeners/quiche/envoy_quic_proof_verifier_test.cc | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index 1771ed428b43..ed222234b705 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -66,7 +66,6 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( quic::CertificateView::ParseSingleCertificate(certs[0]); ASSERT(cert_view != nullptr); for (const absl::string_view config_san : cert_view->subject_alt_name_domains()) { - std::cerr << "config_san " << config_san << "\n"; if (config_san == hostname) { return quic::QUIC_SUCCESS; } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 5bd0089c1450..ee0b445b26bf 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -34,8 +34,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { ON_CALL(client_context_config_, ecdhCurves) .WillByDefault( ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); - const std::string alpn("h2,http/1.1"); - ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); + ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn_)); ; const std::string empty_string; ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); @@ -80,6 +79,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { } protected: + const std::string alpn_{"h2,http/1.1"}; const std::string cert_chain_{quic::test::kTestCertificateChainPem}; std::string leaf_cert_; NiceMock store_; From 5c0dd47562e4c12e26387c67ba2bfd6b4f824df8 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 14 Jul 2020 16:27:28 -0400 Subject: [PATCH 10/28] fix sigalgs Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier.cc | 6 +++--- .../quic_listeners/quiche/envoy_quic_proof_verifier.h | 2 +- .../quic_listeners/quiche/envoy_quic_proof_verifier_test.cc | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index ed222234b705..178b466898cd 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -5,7 +5,7 @@ namespace Envoy { namespace Quic { -static X509* ParseDERCertificate(const std::string& der_bytes, std::string* error_details) { +static X509* parseDERCertificate(const std::string& der_bytes, std::string* error_details) { const uint8_t* data; const uint8_t* orig_data; orig_data = data = reinterpret_cast(der_bytes.data()); @@ -27,13 +27,13 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( const quic::ProofVerifyContext* /*context*/, std::string* error_details, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) { - if (certs.size() == 0) { + if (certs.empty()) { return quic::QUIC_FAILURE; } bssl::UniquePtr intermediates(sk_X509_new_null()); bssl::UniquePtr leaf; for (size_t i = 0; i < certs.size(); i++) { - X509* cert = ParseDERCertificate(certs[i], error_details); + X509* cert = parseDERCertificate(certs[i], error_details); if (!cert) { return quic::QUIC_FAILURE; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h index 7530a57b8279..a29eb999119f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h @@ -12,7 +12,7 @@ class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { public: EnvoyQuicProofVerifier(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, TimeSource& time_source) - : EnvoyQuicProofVerifierBase(), context_impl_(scope, config, time_source) {} + : context_impl_(scope, config, time_source) {} // EnvoyQuicProofVerifierBase quic::QuicAsyncStatus diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index ee0b445b26bf..2d9543e5a171 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -39,7 +39,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { const std::string empty_string; ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); ON_CALL(client_context_config_, signingAlgorithmsForTest()) - .WillByDefault(ReturnRef(empty_string)); + .WillByDefault(ReturnRef(sig_algs_)); ON_CALL(client_context_config_, certificateValidationContext()) .WillByDefault(Return(&cert_validation_ctx_config_)); } @@ -80,6 +80,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { protected: const std::string alpn_{"h2,http/1.1"}; + const std::string sig_algs_{"rsa_pss_rsae_sha256"}; const std::string cert_chain_{quic::test::kTestCertificateChainPem}; std::string leaf_cert_; NiceMock store_; From ae756994237a33cb0fd2a23661aadd9bd69ca04e Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 14 Jul 2020 18:14:22 -0400 Subject: [PATCH 11/28] format Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 2d9543e5a171..8ea2944f7e1c 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -38,8 +38,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { ; const std::string empty_string; ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); - ON_CALL(client_context_config_, signingAlgorithmsForTest()) - .WillByDefault(ReturnRef(sig_algs_)); + ON_CALL(client_context_config_, signingAlgorithmsForTest()).WillByDefault(ReturnRef(sig_algs_)); ON_CALL(client_context_config_, certificateValidationContext()) .WillByDefault(Return(&cert_validation_ctx_config_)); } From 1b85e44d3b2efa0ac4ae3e7429a21aa4b9918e3e Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 15 Jul 2020 13:15:43 -0400 Subject: [PATCH 12/28] fix windows Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_verifier_test.cc | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 8ea2944f7e1c..cbd90eb53e09 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -49,37 +49,36 @@ class EnvoyQuicProofVerifierTest : public testing::Test { // Getting the last cert in the chain as the root CA cert. const std::string& root_ca_cert = cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----")); - const std::string path_string("some_path"); EXPECT_CALL(cert_validation_ctx_config_, caCert()).WillRepeatedly(ReturnRef(root_ca_cert)); - EXPECT_CALL(cert_validation_ctx_config_, caCertPath()).WillRepeatedly(ReturnRef(path_string)); + EXPECT_CALL(cert_validation_ctx_config_, caCertPath()).WillRepeatedly(ReturnRef(path_string_)); EXPECT_CALL(cert_validation_ctx_config_, trustChainVerification) .WillRepeatedly(Return(envoy::extensions::transport_sockets::tls::v3:: CertificateValidationContext::VERIFY_TRUST_CHAIN)); EXPECT_CALL(cert_validation_ctx_config_, allowExpiredCertificate()) .WillRepeatedly(Return(allow_expired_cert)); - const std::string crl_list; EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationList()) - .WillRepeatedly(ReturnRef(crl_list)); + .WillRepeatedly(ReturnRef(crl_list_)); EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) - .WillRepeatedly(ReturnRef(path_string)); - const std::vector empty_string_list; + .WillRepeatedly(ReturnRef(path_string_)); EXPECT_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) - .WillRepeatedly(ReturnRef(empty_string_list)); - const std::vector san_matchers; + .WillRepeatedly(ReturnRef(empty_string_list_)); EXPECT_CALL(cert_validation_ctx_config_, subjectAltNameMatchers()) - .WillRepeatedly(ReturnRef(san_matchers)); + .WillRepeatedly(ReturnRef(san_matchers_)); EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateHashList()) - .WillRepeatedly(ReturnRef(empty_string_list)); + .WillRepeatedly(ReturnRef(empty_string_list_)); EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) - .WillRepeatedly(ReturnRef(empty_string_list)); - + .WillRepeatedly(ReturnRef(empty_string_list_)); verifier_ = std::make_unique(store_, client_context_config_, time_system_); } protected: + const std::string path_string_{"some_path"}; const std::string alpn_{"h2,http/1.1"}; const std::string sig_algs_{"rsa_pss_rsae_sha256"}; + const std::vector san_matchers_; + const std::string crl_list_; + const std::vector empty_string_list_; const std::string cert_chain_{quic::test::kTestCertificateChainPem}; std::string leaf_cert_; NiceMock store_; @@ -89,7 +88,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { std::unique_ptr verifier_; }; -TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainSuccess) { +TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainSuccess) { configCertVerificationDetails(true); std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); @@ -103,7 +102,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainSuccess) { << error_details; } -TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureFromSsl) { +TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureFromSsl) { configCertVerificationDetails(false); std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); @@ -119,7 +118,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureFromSsl) { error_details); } -TEST_F(EnvoyQuicProofVerifierTest, VerifyFilterChainFailureInvalidHost) { +TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureInvalidHost) { configCertVerificationDetails(true); std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); From bcad52c2e384eb00c7e30595e9317c20faac0ec9 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 15 Jul 2020 18:46:36 -0400 Subject: [PATCH 13/28] sign alg Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 8 ++- .../quiche/envoy_quic_proof_source.h | 3 +- .../quiche/envoy_quic_proof_source_base.cc | 22 ++++++- .../quiche/envoy_quic_proof_source_base.h | 4 +- .../quiche/envoy_quic_proof_verifier.cc | 24 ++------ .../quiche/envoy_quic_proof_verifier_base.cc | 11 +++- .../quiche/envoy_quic_proof_verifier_base.h | 5 +- .../quic_listeners/quiche/envoy_quic_utils.cc | 60 +++++++++++++++++++ .../quic_listeners/quiche/envoy_quic_utils.h | 11 ++++ .../quiche/envoy_quic_proof_verifier_test.cc | 18 +++--- 10 files changed, 129 insertions(+), 37 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index ff30c2e85cb2..12878d1cd385 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -68,6 +68,7 @@ envoy_cc_library( external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ + ":envoy_quic_utils_lib", "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_crypto_proof_source_interface_lib", @@ -101,6 +102,7 @@ envoy_cc_library( external_deps = ["quiche_quic_platform"], tags = ["nofips"], deps = [ + ":envoy_quic_utils_lib", "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", "@com_googlesource_quiche//:quic_core_versions_lib", @@ -115,6 +117,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_proof_verifier_base_lib", + ":envoy_quic_utils_lib", "//source/extensions/transport_sockets/tls:context_lib", ], ) @@ -335,7 +338,10 @@ envoy_cc_library( name = "envoy_quic_utils_lib", srcs = ["envoy_quic_utils.cc"], hdrs = ["envoy_quic_utils.h"], - external_deps = ["quiche_quic_platform"], + external_deps = [ + "quiche_quic_platform", + "ssl", + ], tags = ["nofips"], deps = [ "//include/envoy/http:codec_interface", diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index d2ab6f355550..3eb71315ac46 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -9,8 +9,7 @@ namespace Envoy { namespace Quic { // A ProofSource implementation which supplies a proof instance with certs from filter chain. -class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase, - protected Logger::Loggable { +class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { public: EnvoyQuicProofSource(Network::Socket& listen_socket, Network::FilterChainManager& filter_chain_manager, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc index aed5eccacc31..e19058946437 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -8,6 +8,8 @@ #pragma GCC diagnostic pop +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + namespace Envoy { namespace Quic { @@ -36,9 +38,27 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad return; } + std::string error_details; + bssl::UniquePtr cert = parseDERCertificate(chain->certs[0], &error_details); + if (cert == nullptr) { + ENVOY_LOG(warn, absl::StrCat("Invalid leaf cert: ", error_details)); + quic::QuicCryptoProof proof; + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } + + 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, error_details); + quic::QuicCryptoProof proof; + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } + // TODO(danzh) Get the signature algorithm from leaf cert. auto signature_callback = std::make_unique(std::move(callback), chain); - ComputeTlsSignature(server_address, client_address, hostname, SSL_SIGN_RSA_PSS_RSAE_SHA256, + ComputeTlsSignature(server_address, client_address, hostname, sign_alg, quiche::QuicheStringPiece(payload.get(), payload_size), std::move(signature_callback)); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index 214c2742b606..20318f53edfd 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -21,6 +21,7 @@ #include "openssl/ssl.h" #include "envoy/network/filter.h" #include "server/backtrace.h" +#include "common/common/logger.h" namespace Envoy { namespace Quic { @@ -40,7 +41,8 @@ class EnvoyQuicProofSourceDetails : public quic::ProofSource::Details { }; // A partial implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). -class EnvoyQuicProofSourceBase : public quic::ProofSource { +class EnvoyQuicProofSourceBase : public quic::ProofSource, + protected Logger::Loggable { public: ~EnvoyQuicProofSourceBase() override = default; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index 178b466898cd..9212a24ae854 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -1,26 +1,12 @@ #include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + #include "quiche/quic/core/crypto/certificate_view.h" namespace Envoy { namespace Quic { -static X509* parseDERCertificate(const std::string& der_bytes, std::string* error_details) { - const uint8_t* data; - const uint8_t* orig_data; - orig_data = data = reinterpret_cast(der_bytes.data()); - bssl::UniquePtr cert(d2i_X509(nullptr, &data, der_bytes.size())); - if (!cert.get()) { - *error_details = "d2i_X509"; - return nullptr; - } - if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { - // Trailing garbage. - return nullptr; - } - return cert.release(); -} - quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( const std::string& hostname, const uint16_t /*port*/, const std::vector& certs, const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/, @@ -33,14 +19,14 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( bssl::UniquePtr intermediates(sk_X509_new_null()); bssl::UniquePtr leaf; for (size_t i = 0; i < certs.size(); i++) { - X509* cert = parseDERCertificate(certs[i], error_details); + bssl::UniquePtr cert = parseDERCertificate(certs[i], error_details); if (!cert) { return quic::QUIC_FAILURE; } if (i == 0) { - leaf.reset(cert); + leaf = std::move(cert); } else { - sk_X509_push(intermediates.get(), cert); + sk_X509_push(intermediates.get(), cert.release()); } } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc index 0a6ea3b22c11..623e5e607e61 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -1,5 +1,7 @@ #include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + #include "openssl/ssl.h" #include "quiche/quic/core/crypto/certificate_view.h" #include "quiche/quic/core/crypto/crypto_protocol.h" @@ -29,7 +31,6 @@ bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_confi absl::string_view chlo_hash, const std::string& cert, const std::string& signature) { - size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + server_config.size(); auto payload = std::make_unique(payload_size); @@ -46,8 +47,14 @@ bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_confi std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(cert); ASSERT(cert_view != nullptr); + std::string error; + int sign_alg = deduceSignatureAlgorithmFromPublicKey(cert_view->public_key(), &error); + if (sign_alg == 0) { + ENVOY_LOG(warn, absl::StrCat("Invalid leaf cert, ", error)); + return false; + } return cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), - signature, SSL_SIGN_RSA_PSS_RSAE_SHA256); + signature, sign_alg); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h index f42ad37e145f..8bf6bdd61a2a 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h @@ -12,12 +12,15 @@ #pragma GCC diagnostic pop +#include "common/common/logger.h" + namespace Envoy { namespace Quic { // A partial implementation of quic::ProofVerifier which does signature // verification using SSL_SIGN_RSA_PSS_RSAE_SHA256. -class EnvoyQuicProofVerifierBase : public quic::ProofVerifier { +class EnvoyQuicProofVerifierBase : public quic::ProofVerifier, + protected Logger::Loggable { public: ~EnvoyQuicProofVerifierBase() override = default; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 611cf7b7b721..27c30d926161 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -127,5 +127,65 @@ createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, return connection_socket; } +bssl::UniquePtr parseDERCertificate(const std::string& der_bytes, + std::string* error_details) { + const uint8_t* data; + const uint8_t* orig_data; + orig_data = data = reinterpret_cast(der_bytes.data()); + bssl::UniquePtr cert(d2i_X509(nullptr, &data, der_bytes.size())); + if (!cert.get()) { + *error_details = "d2i_X509"; + return nullptr; + } + if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { + // Trailing garbage. + return nullptr; + } + return cert; +} + +int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::string* error_details) { + int sign_alg = 0; + const int pkey_id = EVP_PKEY_id(public_key); + switch (pkey_id) { + case EVP_PKEY_EC: { + // We only support P-256 ECDSA today. + const EC_KEY* ecdsa_public_key = EVP_PKEY_get0_EC_KEY(public_key); + // Since we checked the key type above, this should be valid. + ASSERT(ecdsa_public_key != nullptr); + const EC_GROUP* ecdsa_group = EC_KEY_get0_group(ecdsa_public_key); + if (ecdsa_group == nullptr || EC_GROUP_get_curve_name(ecdsa_group) != NID_X9_62_prime256v1) { + *error_details = "Invalid leaf cert, only P-256 ECDSA certificates are supported"; + } + sign_alg = SSL_SIGN_ECDSA_SECP256R1_SHA256; + } break; + case EVP_PKEY_RSA: { + // We require RSA certificates with 2048-bit or larger keys. + const RSA* rsa_public_key = EVP_PKEY_get0_RSA(public_key); + // Since we checked the key type above, this should be valid. + ASSERT(rsa_public_key != nullptr); + const unsigned rsa_key_length = RSA_size(rsa_public_key); +#ifdef BORINGSSL_FIPS + if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8) { + *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit or 3072-bit keys " + "are supported in FIPS mode"; + } +#else + if (rsa_key_length < 2048 / 8) { + *error_details = + "Invalid leaf cert, only RSA certificates with 2048-bit or larger keys are supported"; + } +#endif + sign_alg = SSL_SIGN_RSA_PSS_RSAE_SHA256; + } break; +#ifdef BORINGSSL_FIPS + default: + *error_details = + "Invalid leaf cert, only RSA and ECDSA certificates are supported in FIPS mode"; +#endif + } + return sign_alg; +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index f5714ef15b83..34dce87d836b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -24,6 +24,8 @@ #include "quiche/quic/platform/api/quic_ip_address.h" #include "quiche/quic/platform/api/quic_socket_address.h" +#include "openssl/ssl.h" + namespace Envoy { namespace Quic { @@ -80,5 +82,14 @@ createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, Network::Address::InstanceConstSharedPtr& local_addr, const Network::ConnectionSocket::OptionsSharedPtr& options); +// Convert a cert in string form to X509 object. +// Return nullptr if the bytes passed cannot be passed. +bssl::UniquePtr parseDERCertificate(const std::string& der_bytes, std::string* error_details); + +// Deduce the suitable signature algorithm according to the public key. +// Return the sign algorithm id works with the public key; If the public key is +// not supported, return 0 with error_details populated correspondingly. +int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::string* error_details); + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index cbd90eb53e09..697a444b3783 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -23,7 +23,8 @@ namespace Quic { class EnvoyQuicProofVerifierTest : public testing::Test { public: EnvoyQuicProofVerifierTest() - : leaf_cert_([=]() { + : root_ca_cert_(cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----"))), + leaf_cert_([=]() { std::stringstream pem_stream(cert_chain_); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); return chain[0]; @@ -35,9 +36,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { .WillByDefault( ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn_)); - ; - const std::string empty_string; - ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); + ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string_)); ON_CALL(client_context_config_, signingAlgorithmsForTest()).WillByDefault(ReturnRef(sig_algs_)); ON_CALL(client_context_config_, certificateValidationContext()) .WillByDefault(Return(&cert_validation_ctx_config_)); @@ -47,9 +46,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { // paths for BoringSSL cert verification success and failure. void configCertVerificationDetails(bool allow_expired_cert) { // Getting the last cert in the chain as the root CA cert. - const std::string& root_ca_cert = - cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----")); - EXPECT_CALL(cert_validation_ctx_config_, caCert()).WillRepeatedly(ReturnRef(root_ca_cert)); + EXPECT_CALL(cert_validation_ctx_config_, caCert()).WillRepeatedly(ReturnRef(root_ca_cert_)); EXPECT_CALL(cert_validation_ctx_config_, caCertPath()).WillRepeatedly(ReturnRef(path_string_)); EXPECT_CALL(cert_validation_ctx_config_, trustChainVerification) .WillRepeatedly(Return(envoy::extensions::transport_sockets::tls::v3:: @@ -57,7 +54,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { EXPECT_CALL(cert_validation_ctx_config_, allowExpiredCertificate()) .WillRepeatedly(Return(allow_expired_cert)); EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationList()) - .WillRepeatedly(ReturnRef(crl_list_)); + .WillRepeatedly(ReturnRef(empty_string_)); EXPECT_CALL(cert_validation_ctx_config_, certificateRevocationListPath()) .WillRepeatedly(ReturnRef(path_string_)); EXPECT_CALL(cert_validation_ctx_config_, verifySubjectAltNameList()) @@ -77,10 +74,11 @@ class EnvoyQuicProofVerifierTest : public testing::Test { const std::string alpn_{"h2,http/1.1"}; const std::string sig_algs_{"rsa_pss_rsae_sha256"}; const std::vector san_matchers_; - const std::string crl_list_; + const std::string empty_string_; const std::vector empty_string_list_; const std::string cert_chain_{quic::test::kTestCertificateChainPem}; - std::string leaf_cert_; + const std::string root_ca_cert_; + const std::string leaf_cert_; NiceMock store_; Event::GlobalTimeSystem time_system_; NiceMock client_context_config_; From 26754613d08bc14068a0e7617073f84d7f7f4218 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 20 Jul 2020 16:21:13 -0400 Subject: [PATCH 14/28] share sign function Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_source.cc | 2 +- .../quiche/envoy_quic_proof_source.h | 12 +++++++----- .../quiche/envoy_quic_proof_source_base.cc | 16 ++++++++++++---- .../quiche/envoy_quic_proof_source_base.h | 13 +++++++++++++ .../quic_listeners/quiche/test_proof_source.h | 16 ++++++++-------- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index 66fe7017436d..e38a0505f209 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -40,7 +40,7 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address new quic::ProofSource::Chain(chain)); } -void EnvoyQuicProofSource::ComputeTlsSignature( +void EnvoyQuicProofSource::signPayload( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, std::unique_ptr callback) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index 3eb71315ac46..6e1c74c9234c 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -23,11 +23,13 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { quic::QuicReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname) override; - void ComputeTlsSignature(const quic::QuicSocketAddress& server_address, - const quic::QuicSocketAddress& client_address, - const std::string& hostname, uint16_t signature_algorithm, - quiche::QuicheStringPiece in, - std::unique_ptr callback) override; + +protected: + // quic::ProofSource + void signPayload(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, const std::string& hostname, + uint16_t signature_algorithm, quiche::QuicheStringPiece in, + std::unique_ptr callback) override; private: struct CertConfigWithFilterChain { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc index e19058946437..29fb0901ff3f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -56,11 +56,19 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad return; } - // TODO(danzh) Get the signature algorithm from leaf cert. auto signature_callback = std::make_unique(std::move(callback), chain); - ComputeTlsSignature(server_address, client_address, hostname, sign_alg, - quiche::QuicheStringPiece(payload.get(), payload_size), - std::move(signature_callback)); + + signPayload(server_address, client_address, hostname, sign_alg, + quiche::QuicheStringPiece(payload.get(), payload_size), + std::move(signature_callback)); +} + +void EnvoyQuicProofSourceBase::ComputeTlsSignature( + const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, + const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, + std::unique_ptr callback) { + signPayload(server_address, client_address, hostname, signature_algorithm, in, + std::move(callback)); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index 20318f53edfd..de75e0aeaeab 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -57,6 +57,19 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource, TicketCrypter* GetTicketCrypter() override { return nullptr; } + void ComputeTlsSignature(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, uint16_t signature_algorithm, + quiche::QuicheStringPiece in, + std::unique_ptr callback) override; + +protected: + virtual void signPayload(const quic::QuicSocketAddress& server_address, + const quic::QuicSocketAddress& client_address, + const std::string& hostname, uint16_t signature_algorithm, + quiche::QuicheStringPiece in, + std::unique_ptr callback) = 0; + private: // Used by GetProof() to get signature. class SignatureCallback : public quic::ProofSource::SignatureCallback { diff --git a/test/extensions/quic_listeners/quiche/test_proof_source.h b/test/extensions/quic_listeners/quiche/test_proof_source.h index 35464fd1963a..8b1baf920d69 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_source.h +++ b/test/extensions/quic_listeners/quiche/test_proof_source.h @@ -31,18 +31,18 @@ class TestProofSource : public EnvoyQuicProofSourceBase { return cert_chain_; } - void - ComputeTlsSignature(const quic::QuicSocketAddress& /*server_address*/, - const quic::QuicSocketAddress& /*client_address*/, - const std::string& /*hostname*/, uint16_t /*signature_algorithm*/, - quiche::QuicheStringPiece in, - std::unique_ptr callback) override { + const Network::MockFilterChain& filterChain() const { return filter_chain_; } + +protected: + void signPayload(const quic::QuicSocketAddress& /*server_address*/, + const quic::QuicSocketAddress& /*client_address*/, + const std::string& /*hostname*/, uint16_t /*signature_algorithm*/, + quiche::QuicheStringPiece in, + std::unique_ptr callback) override { callback->Run(true, absl::StrCat("Fake signature for { ", in, " }"), std::make_unique(filter_chain_)); } - const Network::MockFilterChain& filterChain() const { return filter_chain_; } - private: quic::QuicReferenceCountedPointer cert_chain_{ new quic::ProofSource::Chain( From f19000a74fa21f32238f35e70ac9244674097800 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 20 Jul 2020 17:01:45 -0400 Subject: [PATCH 15/28] wildcard Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index 9212a24ae854..5a6a9bc8926e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -51,8 +51,9 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(certs[0]); ASSERT(cert_view != nullptr); + std::string wildcard = absl::StrCat("*", hostname.substr(hostname.find_first_of('.'))); for (const absl::string_view config_san : cert_view->subject_alt_name_domains()) { - if (config_san == hostname) { + if (config_san == hostname || config_san == wildcard) { return quic::QUIC_SUCCESS; } } From 4c2d222cebd3caf2c6ad24127b56ec68efecf926 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 20 Jul 2020 17:08:07 -0400 Subject: [PATCH 16/28] update comment Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier_base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h index 8bf6bdd61a2a..1716936667c1 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h @@ -18,7 +18,7 @@ namespace Envoy { namespace Quic { // A partial implementation of quic::ProofVerifier which does signature -// verification using SSL_SIGN_RSA_PSS_RSAE_SHA256. +// verification. class EnvoyQuicProofVerifierBase : public quic::ProofVerifier, protected Logger::Loggable { public: From 80a24d9af403c175e4045178fd1a3e02cb903533 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 23 Jul 2020 18:14:01 -0400 Subject: [PATCH 17/28] move around X509 stuff Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_source_base.cc | 3 ++- .../quiche/envoy_quic_proof_verifier.cc | 19 +++------------ .../quiche/envoy_quic_proof_verifier_base.cc | 10 ++++---- .../quic_listeners/quiche/envoy_quic_utils.cc | 5 ++-- .../transport_sockets/tls/context_impl.cc | 23 +++++++++++++++++++ .../transport_sockets/tls/context_impl.h | 14 +++++------ tools/spelling/spelling_dictionary.txt | 2 -- 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc index 29fb0901ff3f..74d9d0a62195 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -50,7 +50,8 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad 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, error_details); + ENVOY_LOG(warn, absl::StrCat("Failed to deduce signature algorithm from public key: ", + error_details)); quic::QuicCryptoProof proof; callback->Run(/*ok=*/false, nullptr, proof, nullptr); return; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index 5a6a9bc8926e..c341e7a8f449 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -29,22 +29,9 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( sk_X509_push(intermediates.get(), cert.release()); } } - - bssl::UniquePtr ctx(X509_STORE_CTX_new()); - // It doesn't matter which SSL context is used, because they share the same - // cert validation config. - X509_STORE* store = SSL_CTX_get_cert_store(context_impl_.chooseSslContexts()); - if (!X509_STORE_CTX_init(ctx.get(), store, leaf.get(), intermediates.get())) { - *error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; - return quic::QUIC_FAILURE; - } - - int res = context_impl_.doVerifyCertChain(ctx.get(), nullptr, std::move(leaf), nullptr); - if (res <= 0) { - const int n = X509_STORE_CTX_get_error(ctx.get()); - const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); - *error_details = absl::StrCat("X509_verify_cert: certificate verification error at depth ", - depth, ": ", X509_verify_cert_error_string(n)); + bool success = + context_impl_.verifyCertChain(std::move(leaf), std::move(intermediates), *error_details); + if (!success) { return quic::QUIC_FAILURE; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc index 623e5e607e61..21dc30c2e72a 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -17,14 +17,12 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifierBase::VerifyProof( const std::string& signature, const quic::ProofVerifyContext* context, std::string* error_details, std::unique_ptr* details, std::unique_ptr callback) { - quic::QuicAsyncStatus res = VerifyCertChain(hostname, port, certs, "", cert_sct, context, - error_details, details, std::move(callback)); - if (res == quic::QUIC_FAILURE) { + if (!verifySignature(server_config, chlo_hash, certs[0], signature)) { return quic::QUIC_FAILURE; } - ASSERT(res != quic::QUIC_PENDING); - return verifySignature(server_config, chlo_hash, certs[0], signature) ? quic::QUIC_SUCCESS - : quic::QUIC_FAILURE; + + return VerifyCertChain(hostname, port, certs, "", cert_sct, context, error_details, details, + std::move(callback)); } bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_config, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 27c30d926161..84ad8fc61ae8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -134,11 +134,11 @@ bssl::UniquePtr parseDERCertificate(const std::string& der_bytes, orig_data = data = reinterpret_cast(der_bytes.data()); bssl::UniquePtr cert(d2i_X509(nullptr, &data, der_bytes.size())); if (!cert.get()) { - *error_details = "d2i_X509"; + *error_details = "d2i_X509: fail to parse DER"; return nullptr; } if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { - // Trailing garbage. + *error_details = "There is railing garbage in DER."; return nullptr; } return cert; @@ -157,6 +157,7 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin if (ecdsa_group == nullptr || EC_GROUP_get_curve_name(ecdsa_group) != NID_X9_62_prime256v1) { *error_details = "Invalid leaf cert, only P-256 ECDSA certificates are supported"; } + // QUICHE uses SHA-256 as hash function in cert signature. sign_alg = SSL_SIGN_ECDSA_SECP256R1_SHA256; } break; case EVP_PKEY_RSA: { diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 22d35b4172c8..fe6b915579fd 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -1395,6 +1395,29 @@ bool ServerContextImpl::TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t return false; } +bool ContextImpl::verifyCertChain(bssl::UniquePtr leaf_cert, + bssl::UniquePtr intermediates, + std::string& error_details) { + bssl::UniquePtr ctx(X509_STORE_CTX_new()); + // It doesn't matter which SSL context is used, because they share the same + // cert validation config. + X509_STORE* store = SSL_CTX_get_cert_store(tls_contexts_[0].ssl_ctx_.get()); + if (!X509_STORE_CTX_init(ctx.get(), store, leaf_cert.get(), intermediates.get())) { + error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; + return false; + } + + int res = doVerifyCertChain(ctx.get(), nullptr, std::move(leaf_cert), nullptr); + if (res <= 0) { + const int n = X509_STORE_CTX_get_error(ctx.get()); + const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); + error_details = absl::StrCat("X509_verify_cert: certificate verification error at depth ", + depth, ": ", X509_verify_cert_error_string(n)); + return false; + } + return true; +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 74d9f9b459ed..87157b7167e5 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -101,13 +101,8 @@ class ContextImpl : public virtual Envoy::Ssl::Context { std::vector getPrivateKeyMethodProviders(); - // Called by verifyCallback to do the actual cert chain verification. - int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, - bssl::UniquePtr leaf_cert, - const Network::TransportSocketOptions* transport_socket_options); - - // Always return the first SSL context in the config. - SSL_CTX* chooseSslContexts() const { return tls_contexts_[0].ssl_ctx_.get(); } + bool verifyCertChain(bssl::UniquePtr leaf_cert, + bssl::UniquePtr intermediates, std::string& error_details); protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, @@ -125,6 +120,11 @@ class ContextImpl : public virtual Envoy::Ssl::Context { // A SSL_CTX_set_cert_verify_callback for custom cert validation. static int verifyCallback(X509_STORE_CTX* store_ctx, void* arg); + // Called by verifyCallback to do the actual cert chain verification. + int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + bssl::UniquePtr leaf_cert, + const Network::TransportSocketOptions* transport_socket_options); + Envoy::Ssl::ClientValidationStatus verifyCertificate(X509* cert, const std::vector& verify_san_list, const std::vector& subject_alt_name_matchers); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index de931274b6fe..4b8f9a058a24 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -231,7 +231,6 @@ PostCBs PREBIND PRNG PROT -PSS Postgre Postgres Prereq @@ -257,7 +256,6 @@ RLS RNG RPC RSA -RSAE RST RTDS RTTI From ca7978f7c13e926687e38bf32b0580d2a8a24118 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 28 Jul 2020 13:56:09 -0400 Subject: [PATCH 18/28] use dnsNameMatch Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_verifier.cc | 6 ++--- .../quic_listeners/quiche/envoy_quic_utils.cc | 4 +-- .../transport_sockets/tls/context_impl.cc | 26 +++++++++---------- .../transport_sockets/tls/context_impl.h | 7 +++-- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index c341e7a8f449..e66ce0b84743 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -29,8 +29,7 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( sk_X509_push(intermediates.get(), cert.release()); } } - bool success = - context_impl_.verifyCertChain(std::move(leaf), std::move(intermediates), *error_details); + bool success = context_impl_.verifyCertChain(*leaf, *intermediates, *error_details); if (!success) { return quic::QUIC_FAILURE; } @@ -38,9 +37,8 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(certs[0]); ASSERT(cert_view != nullptr); - std::string wildcard = absl::StrCat("*", hostname.substr(hostname.find_first_of('.'))); for (const absl::string_view config_san : cert_view->subject_alt_name_domains()) { - if (config_san == hostname || config_san == wildcard) { + if (Extensions::TransportSockets::Tls::ContextImpl::dnsNameMatch(hostname, config_san)) { return quic::QUIC_SUCCESS; } } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index f5291b82c410..e3ae8fdc65ef 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -135,7 +135,7 @@ bssl::UniquePtr parseDERCertificate(const std::string& der_bytes, return nullptr; } if (data < orig_data || static_cast(data - orig_data) != der_bytes.size()) { - *error_details = "There is railing garbage in DER."; + *error_details = "There is trailing garbage in DER."; return nullptr; } return cert; @@ -176,11 +176,9 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin #endif sign_alg = SSL_SIGN_RSA_PSS_RSAE_SHA256; } break; -#ifdef BORINGSSL_FIPS default: *error_details = "Invalid leaf cert, only RSA and ECDSA certificates are supported in FIPS mode"; -#endif } return sign_alg; } diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index fe6b915579fd..00f60c76d7f5 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -526,17 +526,16 @@ int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { ContextImpl* impl = reinterpret_cast(arg); SSL* ssl = reinterpret_cast( X509_STORE_CTX_get_ex_data(store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + auto cert = bssl::UniquePtr(SSL_get_peer_certificate(ssl)); return impl->doVerifyCertChain( store_ctx, reinterpret_cast( SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), - bssl::UniquePtr(SSL_get_peer_certificate(ssl)), - static_cast(SSL_get_app_data(ssl))); + *cert, static_cast(SSL_get_app_data(ssl))); } int ContextImpl::doVerifyCertChain( - X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, - bssl::UniquePtr leaf_cert, + X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, X509& leaf_cert, const Network::TransportSocketOptions* transport_socket_options) { if (verify_trusted_ca_) { int ret = X509_verify_cert(store_ctx); @@ -553,7 +552,7 @@ int ContextImpl::doVerifyCertChain( } Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( - leaf_cert.get(), + &leaf_cert, transport_socket_options && !transport_socket_options->verifySubjectAltNameListOverride().empty() ? transport_socket_options->verifySubjectAltNameListOverride() @@ -676,7 +675,7 @@ bool ContextImpl::matchSubjectAltName( if (general_name->type == GEN_DNS && config_san_matcher.matcher().match_pattern_case() == envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact - ? dnsNameMatch(config_san_matcher.matcher().exact(), san.c_str()) + ? dnsNameMatch(config_san_matcher.matcher().exact(), std::string_view(san.c_str())) : config_san_matcher.match(san)) { return true; } @@ -704,20 +703,20 @@ bool ContextImpl::verifySubjectAltName(X509* cert, return false; } -bool ContextImpl::dnsNameMatch(const std::string& dns_name, const char* pattern) { +bool ContextImpl::dnsNameMatch(const std::string& dns_name, const std::string_view pattern) { if (dns_name == pattern) { return true; } - size_t pattern_len = strlen(pattern); + size_t pattern_len = pattern.length(); if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') { if (dns_name.length() > pattern_len - 1) { const size_t off = dns_name.length() - pattern_len + 1; if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fix_wildcard_matching")) { return dns_name.substr(0, off).find('.') == std::string::npos && - dns_name.compare(off, pattern_len - 1, pattern + 1) == 0; + dns_name.compare(off, pattern_len - 1, pattern.data() + 1, pattern_len - 1) == 0; } else { - return dns_name.compare(off, pattern_len - 1, pattern + 1) == 0; + return dns_name.compare(off, pattern_len - 1, pattern.data() + 1, pattern_len - 1) == 0; } } } @@ -1395,19 +1394,18 @@ bool ServerContextImpl::TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t return false; } -bool ContextImpl::verifyCertChain(bssl::UniquePtr leaf_cert, - bssl::UniquePtr intermediates, +bool ContextImpl::verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediates, std::string& error_details) { bssl::UniquePtr ctx(X509_STORE_CTX_new()); // It doesn't matter which SSL context is used, because they share the same // cert validation config. X509_STORE* store = SSL_CTX_get_cert_store(tls_contexts_[0].ssl_ctx_.get()); - if (!X509_STORE_CTX_init(ctx.get(), store, leaf_cert.get(), intermediates.get())) { + if (!X509_STORE_CTX_init(ctx.get(), store, &leaf_cert, &intermediates)) { error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; return false; } - int res = doVerifyCertChain(ctx.get(), nullptr, std::move(leaf_cert), nullptr); + int res = doVerifyCertChain(ctx.get(), nullptr, leaf_cert, nullptr); if (res <= 0) { const int n = X509_STORE_CTX_get_error(ctx.get()); const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 87157b7167e5..adef7ecc197e 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -84,7 +84,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { * @param pattern the pattern to match against (*.example.com) * @return true if the san matches pattern */ - static bool dnsNameMatch(const std::string& dns_name, const char* pattern); + static bool dnsNameMatch(const std::string& dns_name, const std::string_view pattern); SslStats& stats() { return stats_; } @@ -101,8 +101,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { std::vector getPrivateKeyMethodProviders(); - bool verifyCertChain(bssl::UniquePtr leaf_cert, - bssl::UniquePtr intermediates, std::string& error_details); + bool verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediates, std::string& error_details); protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, @@ -122,7 +121,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { // Called by verifyCallback to do the actual cert chain verification. int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, - bssl::UniquePtr leaf_cert, + X509& leaf_cert, const Network::TransportSocketOptions* transport_socket_options); Envoy::Ssl::ClientValidationStatus From cf628823f7584f24681644c23c71f09983d68960 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 28 Jul 2020 16:29:08 -0400 Subject: [PATCH 19/28] refactor string compare Signed-off-by: Dan Zhang --- source/extensions/transport_sockets/tls/context_impl.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 00f60c76d7f5..6b84a30f80ad 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -675,7 +675,7 @@ bool ContextImpl::matchSubjectAltName( if (general_name->type == GEN_DNS && config_san_matcher.matcher().match_pattern_case() == envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact - ? dnsNameMatch(config_san_matcher.matcher().exact(), std::string_view(san.c_str())) + ? dnsNameMatch(config_san_matcher.matcher().exact(), std::string_view(san)) : config_san_matcher.match(san)) { return true; } @@ -714,9 +714,9 @@ bool ContextImpl::dnsNameMatch(const std::string& dns_name, const std::string_vi const size_t off = dns_name.length() - pattern_len + 1; if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fix_wildcard_matching")) { return dns_name.substr(0, off).find('.') == std::string::npos && - dns_name.compare(off, pattern_len - 1, pattern.data() + 1, pattern_len - 1) == 0; + dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); } else { - return dns_name.compare(off, pattern_len - 1, pattern.data() + 1, pattern_len - 1) == 0; + return dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); } } } From bb4ec8aa7980b8125fc49cbddbf0b6ccad4240d7 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 28 Jul 2020 18:09:04 -0400 Subject: [PATCH 20/28] fail upon invalid pkey Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_source.cc | 8 +++-- .../quiche/envoy_quic_proof_source_test.cc | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index e38a0505f209..7b3ce20e7368 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -50,7 +50,7 @@ void EnvoyQuicProofSource::signPayload( res.cert_config_; if (!cert_config_ref.has_value()) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); - callback->Run(false, "", nullptr); + callback->Run(false, "No matching filter chain found for handshake.", nullptr); return; } auto& cert_config = cert_config_ref.value().get(); @@ -59,7 +59,11 @@ void EnvoyQuicProofSource::signPayload( 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; + } // Sign. std::string sig = pem_key->Sign(in, signature_algorithm); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 00927499480a..1a5d621bb196 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -105,6 +105,19 @@ class TestGetProofCallback : public quic::ProofSource::Callback { std::unique_ptr verifier_; }; +class TestSignatureCallback : public quic::ProofSource::SignatureCallback { +public: + TestSignatureCallback(bool expect_success) : expect_success_(expect_success) {} + + // quic::ProofSource::SignatureCallback + void Run(bool ok, std::string, std::unique_ptr) override { + EXPECT_EQ(expect_success_, ok); + } + +private: + bool expect_success_; +}; + class EnvoyQuicProofSourceTest : public ::testing::Test { public: EnvoyQuicProofSourceTest() @@ -166,5 +179,28 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { EXPECT_TRUE(called); } +TEST_F(EnvoyQuicProofSourceTest, InvalidPrivateKey) { + EXPECT_CALL(listen_socket_, ioHandle()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) + .WillOnce(Invoke( + [&](const Network::ConnectionSocket& connection_socket) { return &filter_chain_; })); + auto server_context_config = std::make_unique(); + auto server_context_config_ptr = server_context_config.get(); + QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); + 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(*server_context_config_ptr, tlsCertificates()) + .WillRepeatedly(Return(tls_cert_configs)); + std::string invalid_pkey("abcdefg"); + EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(invalid_pkey)); + proof_source_.ComputeTlsSignature(server_address_, client_address_, hostname_, + SSL_SIGN_RSA_PSS_RSAE_SHA256, "payload", + std::make_unique(false)); +} + } // namespace Quic } // namespace Envoy From c60059a0ae5167b4e183b62e25102b42509e3f26 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 29 Jul 2020 11:30:21 -0400 Subject: [PATCH 21/28] fix unuse param Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_source_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 1a5d621bb196..25f4e9e72c15 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -182,8 +182,7 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { TEST_F(EnvoyQuicProofSourceTest, InvalidPrivateKey) { EXPECT_CALL(listen_socket_, ioHandle()); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) - .WillOnce(Invoke( - [&](const Network::ConnectionSocket& connection_socket) { return &filter_chain_; })); + .WillOnce(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); auto server_context_config = std::make_unique(); auto server_context_config_ptr = server_context_config.get(); QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); From 29e1f290a44d6ba67e0ffc6cef564f8c07a8351e Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 29 Jul 2020 18:28:38 -0400 Subject: [PATCH 22/28] fix sig alg deduction Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/envoy_quic_utils.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index e3ae8fdc65ef..909b3226425d 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -153,6 +153,7 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin const EC_GROUP* ecdsa_group = EC_KEY_get0_group(ecdsa_public_key); if (ecdsa_group == nullptr || EC_GROUP_get_curve_name(ecdsa_group) != NID_X9_62_prime256v1) { *error_details = "Invalid leaf cert, only P-256 ECDSA certificates are supported"; + break; } // QUICHE uses SHA-256 as hash function in cert signature. sign_alg = SSL_SIGN_ECDSA_SECP256R1_SHA256; @@ -167,11 +168,13 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8) { *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit or 3072-bit keys " "are supported in FIPS mode"; + break; } #else if (rsa_key_length < 2048 / 8) { *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit or larger keys are supported"; + break; } #endif sign_alg = SSL_SIGN_RSA_PSS_RSAE_SHA256; From bb9b501562e3256ac4d123e89a0ebbd4e7994c91 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 30 Jul 2020 14:45:54 -0400 Subject: [PATCH 23/28] use string_view Signed-off-by: Dan Zhang --- source/extensions/transport_sockets/tls/context_impl.cc | 2 +- source/extensions/transport_sockets/tls/context_impl.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 9299483f6736..ba354b5098cc 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -704,7 +704,7 @@ bool ContextImpl::verifySubjectAltName(X509* cert, return false; } -bool ContextImpl::dnsNameMatch(const std::string& dns_name, const std::string_view pattern) { +bool ContextImpl::dnsNameMatch(const std::string_view dns_name, const std::string_view pattern) { if (dns_name == pattern) { return true; } diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index adef7ecc197e..69c700b89631 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -84,7 +84,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { * @param pattern the pattern to match against (*.example.com) * @return true if the san matches pattern */ - static bool dnsNameMatch(const std::string& dns_name, const std::string_view pattern); + static bool dnsNameMatch(const std::string_view dns_name, const std::string_view pattern); SslStats& stats() { return stats_; } From 333b896c66c225d473a82ea3d25d54345f2f0767 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 31 Jul 2020 18:52:11 -0400 Subject: [PATCH 24/28] corner cases Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_source.cc | 2 +- .../quiche/envoy_quic_proof_source_base.cc | 5 ++ .../quiche/envoy_quic_proof_source_base.h | 5 +- .../quiche/envoy_quic_proof_verifier.cc | 6 +- .../quiche/envoy_quic_proof_verifier_base.cc | 37 +++++++----- .../quiche/envoy_quic_proof_verifier_base.h | 3 +- .../transport_sockets/tls/context_impl.cc | 4 +- .../transport_sockets/tls/context_impl.h | 2 +- .../quiche/envoy_quic_proof_source_test.cc | 56 +++++++++++++++++-- .../quiche/envoy_quic_proof_verifier_test.cc | 54 ++++++++++++++++++ .../integration/quic_http_integration_test.cc | 27 +++++++-- .../quiche/test_proof_verifier.h | 3 +- 12 files changed, 169 insertions(+), 35 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index 7b3ce20e7368..fa14c578ef00 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -50,7 +50,7 @@ void EnvoyQuicProofSource::signPayload( res.cert_config_; if (!cert_config_ref.has_value()) { ENVOY_LOG(warn, "No matching filter chain found for handshake."); - callback->Run(false, "No matching filter chain found for handshake.", nullptr); + callback->Run(false, "", nullptr); return; } auto& cert_config = cert_config_ref.value().get(); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc index 74d9d0a62195..220dc4cb1ccf 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -23,6 +23,11 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad quic::QuicReferenceCountedPointer chain = GetCertChain(server_address, client_address, hostname); + if (chain == nullptr || chain->certs.empty()) { + quic::QuicCryptoProof proof; + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + server_config.size(); auto payload = std::make_unique(payload_size); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index de75e0aeaeab..59602f6915f4 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -40,7 +40,8 @@ class EnvoyQuicProofSourceDetails : public quic::ProofSource::Details { const Network::FilterChain& filter_chain_; }; -// A partial implementation of quic::ProofSource which uses RSA cipher suite to sign in GetProof(). +// A partial implementation of quic::ProofSource which chooses a cipher suite accoridng to the leaf +// cert to sign in GetProof(). class EnvoyQuicProofSourceBase : public quic::ProofSource, protected Logger::Loggable { public: @@ -68,7 +69,7 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, - std::unique_ptr callback) = 0; + std::unique_ptr callback) PURE; private: // Used by GetProof() to get signature. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index e66ce0b84743..fe37c71d4832 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -1,5 +1,7 @@ #include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" +#include + #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" #include "quiche/quic/core/crypto/certificate_view.h" @@ -13,9 +15,7 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( const quic::ProofVerifyContext* /*context*/, std::string* error_details, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) { - if (certs.empty()) { - return quic::QUIC_FAILURE; - } + ASSERT(!certs.empty()); bssl::UniquePtr intermediates(sk_X509_new_null()); bssl::UniquePtr leaf; for (size_t i = 0; i < certs.size(); i++) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc index 21dc30c2e72a..229b3ab36628 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -17,7 +17,11 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifierBase::VerifyProof( const std::string& signature, const quic::ProofVerifyContext* context, std::string* error_details, std::unique_ptr* details, std::unique_ptr callback) { - if (!verifySignature(server_config, chlo_hash, certs[0], signature)) { + if (certs.empty()) { + *error_details = "Received empty cert chain."; + return quic::QUIC_FAILURE; + } + if (!verifySignature(server_config, chlo_hash, certs[0], signature, error_details)) { return quic::QUIC_FAILURE; } @@ -28,7 +32,19 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifierBase::VerifyProof( bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_config, absl::string_view chlo_hash, const std::string& cert, - const std::string& signature) { + const std::string& signature, + std::string* error_details) { + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(cert); + if (cert_view == nullptr) { + *error_details = "Invalid leaf cert."; + return false; + } + int sign_alg = deduceSignatureAlgorithmFromPublicKey(cert_view->public_key(), error_details); + if (sign_alg == 0) { + return false; + } + size_t payload_size = sizeof(quic::kProofSignatureLabel) + sizeof(uint32_t) + chlo_hash.size() + server_config.size(); auto payload = std::make_unique(payload_size); @@ -39,20 +55,15 @@ bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_confi payload_writer.WriteUInt32(chlo_hash.size()) && payload_writer.WriteStringPiece(chlo_hash) && payload_writer.WriteStringPiece(server_config); if (!success) { + *error_details = "QuicPacketWriter error."; return false; } - - std::unique_ptr cert_view = - quic::CertificateView::ParseSingleCertificate(cert); - ASSERT(cert_view != nullptr); - std::string error; - int sign_alg = deduceSignatureAlgorithmFromPublicKey(cert_view->public_key(), &error); - if (sign_alg == 0) { - ENVOY_LOG(warn, absl::StrCat("Invalid leaf cert, ", error)); - return false; + bool valid = cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), + signature, sign_alg); + if (!valid) { + *error_details = "Signature is not valid."; } - return cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), - signature, sign_alg); + return valid; } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h index 1716936667c1..02dac5facd42 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.h @@ -39,7 +39,8 @@ class EnvoyQuicProofVerifierBase : public quic::ProofVerifier, protected: virtual bool verifySignature(const std::string& server_config, absl::string_view chlo_hash, - const std::string& cert, const std::string& signature); + const std::string& cert, const std::string& signature, + std::string* error_details); }; } // namespace Quic diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index ba354b5098cc..502739958e50 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -676,7 +676,7 @@ bool ContextImpl::matchSubjectAltName( if (general_name->type == GEN_DNS && config_san_matcher.matcher().match_pattern_case() == envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact - ? dnsNameMatch(config_san_matcher.matcher().exact(), std::string_view(san)) + ? dnsNameMatch(config_san_matcher.matcher().exact(), absl::string_view(san)) : config_san_matcher.match(san)) { return true; } @@ -704,7 +704,7 @@ bool ContextImpl::verifySubjectAltName(X509* cert, return false; } -bool ContextImpl::dnsNameMatch(const std::string_view dns_name, const std::string_view pattern) { +bool ContextImpl::dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern) { if (dns_name == pattern) { return true; } diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 69c700b89631..5ea35a48228e 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -84,7 +84,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { * @param pattern the pattern to match against (*.example.com) * @return true if the san matches pattern */ - static bool dnsNameMatch(const std::string_view dns_name, const std::string_view pattern); + static bool dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern); SslStats& stats() { return stats_; } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 25f4e9e72c15..2fd4c83fdfcb 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -24,11 +24,11 @@ namespace Quic { class TestGetProofCallback : public quic::ProofSource::Callback { public: - TestGetProofCallback(bool& called, const std::string& server_config, + TestGetProofCallback(bool& called, bool should_succeed, const std::string& server_config, quic::QuicTransportVersion& version, quiche::QuicheStringPiece chlo_hash, Network::FilterChain& filter_chain) - : called_(called), server_config_(server_config), version_(version), chlo_hash_(chlo_hash), - expected_filter_chain_(filter_chain) { + : called_(called), should_succeed_(should_succeed), server_config_(server_config), + version_(version), chlo_hash_(chlo_hash), expected_filter_chain_(filter_chain) { ON_CALL(client_context_config_, cipherSuites) .WillByDefault(ReturnRef( Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CIPHER_SUITES)); @@ -37,7 +37,6 @@ class TestGetProofCallback : public quic::ProofSource::Callback { ReturnRef(Extensions::TransportSockets::Tls::ClientContextConfigImpl::DEFAULT_CURVES)); const std::string alpn("h2,http/1.1"); ON_CALL(client_context_config_, alpnProtocols()).WillByDefault(ReturnRef(alpn)); - ; const std::string empty_string; ON_CALL(client_context_config_, serverNameIndication()).WillByDefault(ReturnRef(empty_string)); ON_CALL(client_context_config_, signingAlgorithmsForTest()) @@ -79,6 +78,11 @@ class TestGetProofCallback : public quic::ProofSource::Callback { void Run(bool ok, const quic::QuicReferenceCountedPointer& chain, const quic::QuicCryptoProof& proof, std::unique_ptr details) override { + called_ = true; + if (!should_succeed_) { + EXPECT_FALSE(ok); + return; + }; EXPECT_TRUE(ok); EXPECT_EQ(2, chain->certs.size()); std::string error; @@ -89,11 +93,11 @@ class TestGetProofCallback : public quic::ProofSource::Callback { << error; EXPECT_EQ(&expected_filter_chain_, &static_cast(details.get())->filterChain()); - called_ = true; } private: bool& called_; + bool should_succeed_; const std::string& server_config_; const quic::QuicTransportVersion& version_; quiche::QuicheStringPiece chlo_hash_; @@ -108,14 +112,17 @@ class TestGetProofCallback : public quic::ProofSource::Callback { class TestSignatureCallback : public quic::ProofSource::SignatureCallback { public: TestSignatureCallback(bool expect_success) : expect_success_(expect_success) {} + ~TestSignatureCallback() override { EXPECT_TRUE(run_called_); } // quic::ProofSource::SignatureCallback void Run(bool ok, std::string, std::unique_ptr) override { EXPECT_EQ(expect_success_, ok); + run_called_ = true; } private: bool expect_success_; + bool run_called_{false}; }; class EnvoyQuicProofSourceTest : public ::testing::Test { @@ -147,7 +154,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { bool called = false; - auto callback = std::make_unique(called, server_config_, version_, + auto callback = std::make_unique(called, true, server_config_, version_, chlo_hash_, filter_chain_); EXPECT_CALL(listen_socket_, ioHandle()).Times(2); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) @@ -179,6 +186,43 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { EXPECT_TRUE(called); } +TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoFilterChain) { + bool called = false; + auto callback = std::make_unique(called, false, server_config_, version_, + chlo_hash_, filter_chain_); + EXPECT_CALL(listen_socket_, ioHandle()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) + .WillRepeatedly(Invoke([&](const Network::ConnectionSocket&) { return nullptr; })); + proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, + chlo_hash_, std::move(callback)); + EXPECT_TRUE(called); +} + +TEST_F(EnvoyQuicProofSourceTest, GetProofFailInvalidCert) { + bool called = false; + auto callback = std::make_unique(called, false, server_config_, version_, + chlo_hash_, filter_chain_); + EXPECT_CALL(listen_socket_, ioHandle()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) + .WillRepeatedly(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); + auto server_context_config = std::make_unique(); + auto server_context_config_ptr = server_context_config.get(); + QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); + 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(*server_context_config_ptr, tlsCertificates()) + .WillRepeatedly(Return(tls_cert_configs)); + std::string invalid_cert{"invalid certificate"}; + EXPECT_CALL(tls_cert_config, certificateChain()).WillOnce(ReturnRef(invalid_cert)); + proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, + chlo_hash_, std::move(callback)); + EXPECT_TRUE(called); +} + TEST_F(EnvoyQuicProofSourceTest, InvalidPrivateKey) { EXPECT_CALL(listen_socket_, ioHandle()); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 697a444b3783..499f1966fe11 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -129,5 +129,59 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureInvalidHost) { << error_details; EXPECT_EQ("Leaf certificate doesn't match hostname: unknown.org", error_details); } + +TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureEmptyCertChain) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; + quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + std::string server_config{"Server Config"}; + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + const std::vector certs; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyProof(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + server_config, version, chlo_hash, certs, cert_sct, "signature", + nullptr, &error_details, nullptr, nullptr)); + EXPECT_EQ("Received empty cert chain.", error_details); +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; + quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + std::string server_config{"Server Config"}; + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + const std::vector certs{"invalid leaf cert"}; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyProof(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + server_config, version, chlo_hash, certs, cert_sct, "signature", + nullptr, &error_details, nullptr, nullptr)); + EXPECT_EQ("Invalid leaf cert.", error_details); +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidSignature) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; + quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + std::string server_config{"Server Config"}; + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyProof(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + server_config, version, chlo_hash, {leaf_cert_}, cert_sct, + "signature", nullptr, &error_details, nullptr, nullptr)); + EXPECT_EQ("Signature is not valid.", error_details); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 158785c8e76c..353bf1c8aadd 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -1,3 +1,5 @@ +#include + #include #include "envoy/config/bootstrap/v3/bootstrap.pb.h" @@ -48,8 +50,8 @@ class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { }; std::unique_ptr -createQuicClientTransportSocketFactory(const Ssl::ClientSslTransportOptions& options, - Api::Api& api) { +createQuicClientTransportSocketFactory(const Ssl::ClientSslTransportOptions& options, Api::Api& api, + const std::string& san_to_match) { std::string yaml_plain = R"EOF( common_tls_context: validation_context: @@ -64,8 +66,8 @@ createQuicClientTransportSocketFactory(const Ssl::ClientSslTransportOptions& opt common_context->add_alpn_protocols("h3"); } if (options.san_) { - common_context->mutable_validation_context() - ->add_hidden_envoy_deprecated_verify_subject_alt_name("spiffe://lyft.com/backend-team"); + common_context->mutable_validation_context()->add_match_subject_alt_names()->set_exact( + san_to_match); } for (const std::string& cipher_suite : options.cipher_suites_) { common_context->mutable_tls_params()->add_cipher_suites(cipher_suite); @@ -213,7 +215,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers std::make_unique(std::make_unique( stats_store_, createQuicClientTransportSocketFactory( - Ssl::ClientSslTransportOptions().setAlpn(true).setSan(true), *api_) + Ssl::ClientSslTransportOptions().setAlpn(true).setSan(true), *api_, san_to_match_) ->clientContextConfig(), timeSystem())); } @@ -223,6 +225,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers protected: quic::QuicConfig quic_config_; quic::QuicServerId server_id_{"lyft.com", 443, false}; + std::string san_to_match_{"spiffe://lyft.com/backend-team"}; quic::QuicClientPushPromiseIndex push_promise_index_; quic::ParsedQuicVersionVector supported_versions_; std::unique_ptr crypto_config_; @@ -507,5 +510,19 @@ TEST_P(QuicHttpIntegrationTest, AdminDrainDrainsListeners) { testAdminDrain(Http::CodecClient::Type::HTTP1); } +TEST_P(QuicHttpIntegrationTest, CertVerificationFailure) { + san_to_match_ = "www.random_domain.com"; + initialize(); + codec_client_ = makeRawHttpConnection(makeClientConnection((lookupPort("http"))), absl::nullopt); + EXPECT_FALSE(codec_client_->connected()); + std::string failure_reason = + GetParam().second == QuicVersionType::GquicQuicCrypto + ? "QUIC_PROOF_INVALID with details: Proof invalid: X509_verify_cert: certificate " + "verification error at depth 0: ok" + : "QUIC_HANDSHAKE_FAILED with details: TLS handshake failure (ENCRYPTION_HANDSHAKE) 46: " + "certificate unknown"; + EXPECT_EQ(failure_reason, codec_client_->connection()->transportFailureReason()); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/test_proof_verifier.h b/test/extensions/quic_listeners/quiche/test_proof_verifier.h index 0ef2a7a31c6f..77dada22d1cd 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_verifier.h +++ b/test/extensions/quic_listeners/quiche/test_proof_verifier.h @@ -20,7 +20,8 @@ class TestProofVerifier : public EnvoyQuicProofVerifierBase { protected: // EnvoyQuicProofVerifierBase bool verifySignature(const std::string& /*server_config*/, absl::string_view /*chlo_hash*/, - const std::string& /*cert*/, const std::string& /*signature*/) override { + const std::string& /*cert*/, const std::string& /*signature*/, + std::string* /*error_details*/) override { return true; } }; From 99aa26894b1424818d455fb1a33688f27a8d3e0d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 3 Aug 2020 12:51:58 -0400 Subject: [PATCH 25/28] format Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_source_base.h | 2 +- .../quic_listeners/quiche/envoy_quic_proof_verifier.cc | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index 59602f6915f4..149cc50c7d63 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -40,7 +40,7 @@ class EnvoyQuicProofSourceDetails : public quic::ProofSource::Details { const Network::FilterChain& filter_chain_; }; -// A partial implementation of quic::ProofSource which chooses a cipher suite accoridng to the leaf +// A partial implementation of quic::ProofSource which chooses a cipher suite according to the leaf // cert to sign in GetProof(). class EnvoyQuicProofSourceBase : public quic::ProofSource, protected Logger::Loggable { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index fe37c71d4832..b7040d1279d7 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -1,7 +1,5 @@ #include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" -#include - #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" #include "quiche/quic/core/crypto/certificate_view.h" From fc121e767c276d0f50eff829fd2a5cd5f56b79c7 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 3 Aug 2020 16:54:02 -0400 Subject: [PATCH 26/28] fix test failure Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_source.cc | 4 ++-- .../quic_listeners/quiche/envoy_quic_proof_source_test.cc | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index fa14c578ef00..da6f70611dd2 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -28,11 +28,11 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address } auto& cert_config = cert_config_ref.value().get(); const std::string& chain_str = cert_config.certificateChain(); - std::string pem_str = std::string(const_cast(chain_str.data()), chain_str.size()); std::stringstream pem_stream(chain_str); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); if (chain.empty()) { - ENVOY_LOG(warn, "Failed to load certificate chain from %s", cert_config.certificateChainPath()); + const std::string& path = cert_config.certificateChainPath(); + ENVOY_LOG(warn, "Failed to load certificate chain from %s", path); return quic::QuicReferenceCountedPointer( new quic::ProofSource::Chain({})); } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 2fd4c83fdfcb..aaf6cca1fe63 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -218,6 +218,8 @@ TEST_F(EnvoyQuicProofSourceTest, GetProofFailInvalidCert) { .WillRepeatedly(Return(tls_cert_configs)); std::string invalid_cert{"invalid certificate"}; EXPECT_CALL(tls_cert_config, certificateChain()).WillOnce(ReturnRef(invalid_cert)); + std::string path("some path"); + EXPECT_CALL(tls_cert_config, certificateChainPath()).WillOnce(ReturnRef(path)); proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, chlo_hash_, std::move(callback)); EXPECT_TRUE(called); From c3229ed367e140bdc41e4547dc80e9de67c73b9f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 5 Aug 2020 16:52:04 -0400 Subject: [PATCH 27/28] add more test Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_proof_source.cc | 7 - .../quic_listeners/quiche/envoy_quic_utils.cc | 3 +- .../quiche/envoy_quic_proof_source_test.cc | 125 ++++++++++-------- .../quiche/envoy_quic_proof_verifier_test.cc | 65 +++++++++ 4 files changed, 137 insertions(+), 63 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index da6f70611dd2..96fe056e818e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -30,12 +30,6 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address const std::string& chain_str = cert_config.certificateChain(); std::stringstream pem_stream(chain_str); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); - if (chain.empty()) { - const std::string& path = cert_config.certificateChainPath(); - ENVOY_LOG(warn, "Failed to load certificate chain from %s", path); - return quic::QuicReferenceCountedPointer( - new quic::ProofSource::Chain({})); - } return quic::QuicReferenceCountedPointer( new quic::ProofSource::Chain(chain)); } @@ -89,7 +83,6 @@ EnvoyQuicProofSource::getTlsCertConfigAndFilterChain(const quic::QuicSocketAddre const Network::FilterChain* filter_chain = filter_chain_manager_.findFilterChain(connection_socket); if (filter_chain == nullptr) { - ENVOY_LOG(warn, "No matching filter chain found for handshake."); listener_stats_.no_filter_chain_match_.inc(); return {absl::nullopt, absl::nullopt}; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 909b3226425d..b5c710a81269 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -180,8 +180,7 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin sign_alg = SSL_SIGN_RSA_PSS_RSAE_SHA256; } break; default: - *error_details = - "Invalid leaf cert, only RSA and ECDSA certificates are supported in FIPS mode"; + *error_details = "Invalid leaf cert, only RSA and ECDSA certificates are supported"; } return sign_alg; } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index aaf6cca1fe63..d896dbb86b7c 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -130,11 +130,49 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { EnvoyQuicProofSourceTest() : server_address_(quic::QuicIpAddress::Loopback4(), 12345), client_address_(quic::QuicIpAddress::Loopback4(), 54321), + transport_socket_factory_(std::make_unique()), 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_) {} + 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) { + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(server_address_), + *connection_socket.localAddress()); + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), + *connection_socket.remoteAddress()); + EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, + connection_socket.detectedTransportProtocol()); + EXPECT_EQ("h2", connection_socket.requestedApplicationProtocols()[0]); + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillRepeatedly(ReturnRef(transport_socket_factory_)); + + std::vector> tls_cert_configs{ + std::reference_wrapper(tls_cert_config_)}; + EXPECT_CALL(dynamic_cast( + transport_socket_factory_.serverContextConfig()), + 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_)); + } + } + + void testGetProof(bool expect_success) { + bool called = false; + auto callback = std::make_unique(called, expect_success, server_config_, + version_, chlo_hash_, filter_chain_); + proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, + chlo_hash_, std::move(callback)); + EXPECT_TRUE(called); + } + protected: std::string hostname_{"www.fake.com"}; quic::QuicSocketAddress server_address_; @@ -148,42 +186,15 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { Network::MockFilterChainManager filter_chain_manager_; Network::MockListenSocket listen_socket_; testing::NiceMock listener_config_; + QuicServerTransportSocketFactory transport_socket_factory_; + Ssl::MockTlsCertificateConfig tls_cert_config_; Server::ListenerStats listener_stats_; EnvoyQuicProofSource proof_source_; }; TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { - bool called = false; - auto callback = std::make_unique(called, true, server_config_, version_, - chlo_hash_, filter_chain_); - EXPECT_CALL(listen_socket_, ioHandle()).Times(2); - EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) - .WillRepeatedly(Invoke([&](const Network::ConnectionSocket& connection_socket) { - EXPECT_EQ(*quicAddressToEnvoyAddressInstance(server_address_), - *connection_socket.localAddress()); - EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), - *connection_socket.remoteAddress()); - EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, - connection_socket.detectedTransportProtocol()); - EXPECT_EQ("h2", connection_socket.requestedApplicationProtocols()[0]); - return &filter_chain_; - })); - auto server_context_config = std::make_unique(); - auto server_context_config_ptr = server_context_config.get(); - QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); - 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(*server_context_config_ptr, tlsCertificates()) - .WillRepeatedly(Return(tls_cert_configs)); - EXPECT_CALL(tls_cert_config, certificateChain()).WillOnce(ReturnRef(expected_certs_)); - EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(pkey_)); - proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, - chlo_hash_, std::move(callback)); - EXPECT_TRUE(called); + expectCertChainAndPrivateKey(expected_certs_, true); + testGetProof(true); } TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoFilterChain) { @@ -199,30 +210,36 @@ TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoFilterChain) { } TEST_F(EnvoyQuicProofSourceTest, GetProofFailInvalidCert) { - bool called = false; - auto callback = std::make_unique(called, false, server_config_, version_, - chlo_hash_, filter_chain_); - EXPECT_CALL(listen_socket_, ioHandle()); - EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) - .WillRepeatedly(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); - auto server_context_config = std::make_unique(); - auto server_context_config_ptr = server_context_config.get(); - QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); - EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(transport_socket_factory)); + std::string invalid_cert{R"(-----BEGIN CERTIFICATE----- + invalid certificate + -----END CERTIFICATE-----)"}; + expectCertChainAndPrivateKey(invalid_cert, false); + testGetProof(false); +} - Ssl::MockTlsCertificateConfig tls_cert_config; - std::vector> tls_cert_configs{ - std::reference_wrapper(tls_cert_config)}; - EXPECT_CALL(*server_context_config_ptr, tlsCertificates()) - .WillRepeatedly(Return(tls_cert_configs)); - std::string invalid_cert{"invalid certificate"}; - EXPECT_CALL(tls_cert_config, certificateChain()).WillOnce(ReturnRef(invalid_cert)); - std::string path("some path"); - EXPECT_CALL(tls_cert_config, certificateChainPath()).WillOnce(ReturnRef(path)); - proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, - chlo_hash_, std::move(callback)); - EXPECT_TRUE(called); +TEST_F(EnvoyQuicProofSourceTest, GetProofFailInvalidPublicKeyInCert) { + // 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); + testGetProof(false); } TEST_F(EnvoyQuicProofSourceTest, InvalidPrivateKey) { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 499f1966fe11..96d6e116e3ce 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -116,6 +116,34 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureFromSsl) { error_details); } +TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureInvalidLeafCert) { + configCertVerificationDetails(true); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + const std::vector certs{"invalid leaf cert"}; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain("www.google.com", 54321, certs, ocsp_response, cert_sct, + nullptr, &error_details, nullptr, nullptr)); + EXPECT_EQ("d2i_X509: fail to parse DER", error_details); +} + +TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureLeafCertWithGarbage) { + configCertVerificationDetails(true); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(leaf_cert_); + const std::string ocsp_response; + const std::string cert_sct; + std::string cert_with_trailing_garbage = absl::StrCat(leaf_cert_, "AAAAAA"); + std::string error_details; + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain(std::string(cert_view->subject_alt_name_domains()[0]), 54321, + {cert_with_trailing_garbage}, ocsp_response, cert_sct, + nullptr, &error_details, nullptr, nullptr)) + << error_details; + EXPECT_EQ("There is trailing garbage in DER.", error_details); +} + TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainFailureInvalidHost) { configCertVerificationDetails(true); std::unique_ptr cert_view = @@ -166,6 +194,43 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { EXPECT_EQ("Invalid leaf cert.", error_details); } +TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureUnsupportedRsaKey) { + configCertVerificationDetails(true); + quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; + quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + std::string server_config{"Server Config"}; + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + // This is a EC cert with secp384r1 curve which is not supported by Envoy. + const std::string certs{R"(-----BEGIN CERTIFICATE----- +MIICkDCCAhagAwIBAgIUTZbykU9eQL3GdrNlodxrOJDecIQwCgYIKoZIzj0EAwIw +fzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMRIwEAYDVQQHDAlDYW1icmlkZ2Ux +DzANBgNVBAoMBkdvb2dsZTEOMAwGA1UECwwFZW52b3kxDTALBgNVBAMMBHRlc3Qx +HzAdBgkqhkiG9w0BCQEWEGRhbnpoQGdvb2dsZS5jb20wHhcNMjAwODA1MjAyMDI0 +WhcNMjIwODA1MjAyMDI0WjB/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTUExEjAQ +BgNVBAcMCUNhbWJyaWRnZTEPMA0GA1UECgwGR29vZ2xlMQ4wDAYDVQQLDAVlbnZv +eTENMAsGA1UEAwwEdGVzdDEfMB0GCSqGSIb3DQEJARYQZGFuemhAZ29vZ2xlLmNv +bTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGRaEAtVq+xHXfsF4R/j+mqVN2E29ZYL +oFlvnelKeeT2B51bSfUv+X+Ci1BSa2OxPCVS6o0vpcF6YOlz4CS7QcXZIoRfhsv7 +O2Hz/IdxAPhX/gdK/70T1x+V/6nvIHiiw6NTMFEwHQYDVR0OBBYEFF75rDce6xNJ +GfpKbUg4emG2KWRMMB8GA1UdIwQYMBaAFF75rDce6xNJGfpKbUg4emG2KWRMMA8G +A1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAIyZghTK3cmyrRWkxfQ7 +xEc11gujcT8nbytYbM6jodKwcbtR6SOmLx2ychXrCMm2ZAIwXqmrTYBtrbqb3mBx +VdGXMAjeXhnOnPvmDi5hUz/uvI+Pg6cNmUoCRwSCnK/DazhA +-----END CERTIFICATE-----)"}; + std::stringstream pem_stream(certs); + std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); + std::unique_ptr cert_view = + quic::CertificateView::ParseSingleCertificate(chain[0]); + ASSERT(cert_view); + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyProof("www.google.com", 54321, server_config, version, chlo_hash, + chain, cert_sct, "signature", nullptr, &error_details, nullptr, + nullptr)); + EXPECT_EQ("Invalid leaf cert, only P-256 ECDSA certificates are supported", error_details); +} + TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidSignature) { configCertVerificationDetails(true); std::unique_ptr cert_view = From 0f7f7b8d86370adb0f3442b2565af4341d0efb2a Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 6 Aug 2020 15:14:26 -0400 Subject: [PATCH 28/28] fix test name Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_proof_verifier_test.cc | 2 +- tools/spelling/spelling_dictionary.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 96d6e116e3ce..4a1dfe144dd3 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -194,7 +194,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { EXPECT_EQ("Invalid leaf cert.", error_details); } -TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureUnsupportedRsaKey) { +TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureUnsupportedECKey) { configCertVerificationDetails(true); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; quiche::QuicheStringPiece chlo_hash{"aaaaa"}; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 98fc8fce45b0..6c0797ffe6e4 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -990,6 +990,7 @@ sched schedulable schemas scopekey +secp sendmsg sendmmsg sendto