diff --git a/include/envoy/ssl/context_config.h b/include/envoy/ssl/context_config.h index 6647e7f4dd187..5f9063e85d0c8 100644 --- a/include/envoy/ssl/context_config.h +++ b/include/envoy/ssl/context_config.h @@ -88,6 +88,14 @@ class ClientContextConfig : public virtual ContextConfig { * @return The maximum number of session keys to store. */ virtual size_t maxSessionKeys() const PURE; + + /** + * @return const std::string& with the signature algorithms for the context. + * This is a :-delimited list of algorithms, see + * https://tools.ietf.org/id/draft-ietf-tls-tls13-21.html#rfc.section.4.2.3 + * for names. + */ + virtual const std::string& signingAlgorithmsForTest() const PURE; }; typedef std::unique_ptr ClientContextConfigPtr; diff --git a/source/common/ssl/context_config_impl.cc b/source/common/ssl/context_config_impl.cc index baea5ab00409d..8c72b30850beb 100644 --- a/source/common/ssl/context_config_impl.cc +++ b/source/common/ssl/context_config_impl.cc @@ -252,11 +252,12 @@ unsigned ContextConfigImpl::tlsVersionFromProto( } ClientContextConfigImpl::ClientContextConfigImpl( - const envoy::api::v2::auth::UpstreamTlsContext& config, + const envoy::api::v2::auth::UpstreamTlsContext& config, absl::string_view sigalgs, Server::Configuration::TransportSocketFactoryContext& factory_context) : ContextConfigImpl(config.common_tls_context(), factory_context), server_name_indication_(config.sni()), allow_renegotiation_(config.allow_renegotiation()), - max_session_keys_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_session_keys, 1)) { + max_session_keys_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_session_keys, 1)), + sigalgs_(sigalgs) { // BoringSSL treats this as a C string, so embedded NULL characters will not // be handled correctly. if (server_name_indication_.find('\0') != std::string::npos) { diff --git a/source/common/ssl/context_config_impl.h b/source/common/ssl/context_config_impl.h index 07fd2f8182baf..f9705988dfad2 100644 --- a/source/common/ssl/context_config_impl.h +++ b/source/common/ssl/context_config_impl.h @@ -91,10 +91,14 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { class ClientContextConfigImpl : public ContextConfigImpl, public ClientContextConfig { public: - explicit ClientContextConfigImpl( - const envoy::api::v2::auth::UpstreamTlsContext& config, + ClientContextConfigImpl( + const envoy::api::v2::auth::UpstreamTlsContext& config, absl::string_view sigalgs, Server::Configuration::TransportSocketFactoryContext& secret_provider_context); - explicit ClientContextConfigImpl( + ClientContextConfigImpl( + const envoy::api::v2::auth::UpstreamTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) + : ClientContextConfigImpl(config, "", secret_provider_context) {} + ClientContextConfigImpl( const Json::Object& config, Server::Configuration::TransportSocketFactoryContext& secret_provider_context); @@ -102,19 +106,21 @@ class ClientContextConfigImpl : public ContextConfigImpl, public ClientContextCo const std::string& serverNameIndication() const override { return server_name_indication_; } bool allowRenegotiation() const override { return allow_renegotiation_; } size_t maxSessionKeys() const override { return max_session_keys_; } + const std::string& signingAlgorithmsForTest() const override { return sigalgs_; } private: const std::string server_name_indication_; const bool allow_renegotiation_; const size_t max_session_keys_; + const std::string sigalgs_; }; class ServerContextConfigImpl : public ContextConfigImpl, public ServerContextConfig { public: - explicit ServerContextConfigImpl( + ServerContextConfigImpl( const envoy::api::v2::auth::DownstreamTlsContext& config, Server::Configuration::TransportSocketFactoryContext& secret_provider_context); - explicit ServerContextConfigImpl( + ServerContextConfigImpl( const Json::Object& config, Server::Configuration::TransportSocketFactoryContext& secret_provider_context); diff --git a/source/common/ssl/context_impl.cc b/source/common/ssl/context_impl.cc index e7500ee9075c7..03af615d3ffb5 100644 --- a/source/common/ssl/context_impl.cc +++ b/source/common/ssl/context_impl.cc @@ -24,8 +24,27 @@ namespace Envoy { namespace Ssl { +namespace { + +bool cbsContainsU16(CBS& cbs, uint16_t n) { + while (CBS_len(&cbs) > 0) { + uint16_t v; + if (!CBS_get_u16(&cbs, &v)) { + return false; + } + if (v == n) { + return true; + } + } + + return false; +} + +} // namespace + ContextImpl::ContextImpl(Stats::Scope& scope, const ContextConfig& config, TimeSource& time_source) - : scope_(scope), stats_(generateStats(scope)), time_source_(time_source) { + : scope_(scope), stats_(generateStats(scope)), time_source_(time_source), + tls_max_version_(config.maxProtocolVersion()) { const auto tls_certificates = config.tlsCertificates(); tls_contexts_.resize(std::max(1UL, tls_certificates.size())); @@ -539,6 +558,14 @@ ClientContextImpl::ClientContextImpl(Stats::Scope& scope, const ClientContextCon } } + if (!config.signingAlgorithmsForTest().empty()) { + for (auto& ctx : tls_contexts_) { + int rc = + SSL_CTX_set1_sigalgs_list(ctx.ssl_ctx_.get(), config.signingAlgorithmsForTest().c_str()); + RELEASE_ASSERT(rc == 1, ""); + } + } + if (max_session_keys_ > 0) { SSL_CTX_set_session_cache_mode(tls_contexts_[0].ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT); SSL_CTX_sess_set_new_cb( @@ -824,11 +851,88 @@ int ServerContextImpl::sessionTicketProcess(SSL*, uint8_t* key_name, uint8_t* iv } } +bool ServerContextImpl::isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello) { + CBS client_hello; + CBS_init(&client_hello, ssl_client_hello->client_hello, ssl_client_hello->client_hello_len); + + // This is the TLSv1.3 case (TLSv1.2 on the wire and the supported_versions extensions present). + // We just need to loook at signature algorithms. + const uint16_t client_version = ssl_client_hello->version; + if (client_version == TLS1_2_VERSION && tls_max_version_ == TLS1_3_VERSION) { + // If the supported_versions extension is found then we assume that the client is competent + // enough that just checking the signature_algorithms is sufficient. + const uint8_t* supported_versions_data; + size_t supported_versions_len; + if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_supported_versions, + &supported_versions_data, &supported_versions_len)) { + const uint8_t* signature_algorithms_data; + size_t signature_algorithms_len; + if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_signature_algorithms, + &signature_algorithms_data, + &signature_algorithms_len)) { + CBS signature_algorithms_ext, signature_algorithms; + CBS_init(&signature_algorithms_ext, signature_algorithms_data, signature_algorithms_len); + if (!CBS_get_u16_length_prefixed(&signature_algorithms_ext, &signature_algorithms) || + CBS_len(&signature_algorithms_ext) != 0) { + return false; + } + if (cbsContainsU16(signature_algorithms, SSL_SIGN_ECDSA_SECP256R1_SHA256)) { + return true; + } + } + + return false; + } + } + + // Otherwise we are < TLSv1.3 and need to look at both the curves in the supported_groups for + // ECDSA and also for a compatible cipher suite. https://tools.ietf.org/html/rfc4492#section-5.1.1 + const uint8_t* curvelist_data; + size_t curvelist_len; + if (!SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_supported_groups, + &curvelist_data, &curvelist_len)) { + return false; + } + + CBS curvelist; + CBS_init(&curvelist, curvelist_data, curvelist_len); + + // We only support P256 ECDSA curves today. + if (!cbsContainsU16(curvelist, SSL_CURVE_SECP256R1)) { + return false; + } + + // The client must have offered an ECDSA ciphersuite that we like. + CBS cipher_suites; + CBS_init(&cipher_suites, ssl_client_hello->cipher_suites, ssl_client_hello->cipher_suites_len); + + while (CBS_len(&cipher_suites) > 0) { + uint16_t cipher_id; + if (!CBS_get_u16(&cipher_suites, &cipher_id)) { + return false; + } + // All tls_context_ share the same set of enabled ciphers, so we can just look at the base + // context. + if (tls_contexts_[0].isCipherEnabled(cipher_id, client_version)) { + return true; + } + } + + return false; +} + enum ssl_select_cert_result_t ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { - // This is currently a nop, since we only have a single cert, but this is where we will implement - // the certificate selection logic in #1319. - RELEASE_ASSERT(SSL_set_SSL_CTX(ssl_client_hello->ssl, tls_contexts_[0].ssl_ctx_.get()) != nullptr, + const bool client_ecdsa_capable = isClientEcdsaCapable(ssl_client_hello); + // Fallback on first certificate. + const TlsContext* selected_ctx = &tls_contexts_[0]; + for (const auto& ctx : tls_contexts_) { + if (client_ecdsa_capable == ctx.is_ecdsa_) { + selected_ctx = &ctx; + break; + } + } + RELEASE_ASSERT(SSL_set_SSL_CTX(ssl_client_hello->ssl, selected_ctx->ssl_ctx_.get()) != nullptr, ""); return ssl_select_cert_success; } @@ -878,5 +982,25 @@ void ServerContextImpl::TlsContext::addClientValidationContext( } } +bool ServerContextImpl::TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t client_version) { + const SSL_CIPHER* c = SSL_get_cipher_by_value(cipher_id); + if (c == nullptr) { + return false; + } + // Skip TLS 1.2 only ciphersuites unless the client supports it. + if (SSL_CIPHER_get_min_version(c) > client_version) { + return false; + } + if (SSL_CIPHER_get_auth_nid(c) != NID_auth_ecdsa) { + return false; + } + for (const SSL_CIPHER* our_c : SSL_CTX_get_ciphers(ssl_ctx_.get())) { + if (SSL_CIPHER_get_id(our_c) == SSL_CIPHER_get_id(c)) { + return true; + } + } + return false; +} + } // namespace Ssl } // namespace Envoy diff --git a/source/common/ssl/context_impl.h b/source/common/ssl/context_impl.h index b425ad3218045..41713ae773131 100644 --- a/source/common/ssl/context_impl.h +++ b/source/common/ssl/context_impl.h @@ -135,6 +135,7 @@ class ContextImpl : public virtual Context { std::string getCertChainFileName() const { return cert_chain_file_path_; }; void addClientValidationContext(const CertificateValidationContextConfig& config, bool require_client_cert); + bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); }; // This is always non-empty, with the first context used for all new SSL @@ -154,6 +155,7 @@ class ContextImpl : public virtual Context { std::string ca_file_path_; std::string cert_chain_file_path_; TimeSource& time_source_; + const unsigned tls_max_version_; }; typedef std::shared_ptr ContextImplSharedPtr; @@ -186,6 +188,7 @@ class ServerContextImpl : public ContextImpl, public ServerContext { unsigned int inlen); int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, int encrypt); + bool isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello); // Select the TLS certificate context in SSL_CTX_set_select_certificate_cb() callback with // ClientHello details. enum ssl_select_cert_result_t selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello); diff --git a/test/config/utility.cc b/test/config/utility.cc index e5903bdf9311c..ac4fb8a337e30 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -382,15 +382,16 @@ void ConfigHelper::setClientCodec( } } -void ConfigHelper::addSslConfig(bool ecdsa_cert) { +void ConfigHelper::addSslConfig(bool ecdsa_cert, bool tlsv1_3) { RELEASE_ASSERT(!finalized_, ""); auto* filter_chain = bootstrap_.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); - initializeTls(ecdsa_cert, *filter_chain->mutable_tls_context()->mutable_common_tls_context()); + initializeTls(ecdsa_cert, tlsv1_3, + *filter_chain->mutable_tls_context()->mutable_common_tls_context()); } -void ConfigHelper::initializeTls(bool ecdsa_cert, +void ConfigHelper::initializeTls(bool ecdsa_cert, bool tlsv1_3, envoy::api::v2::auth::CommonTlsContext& common_tls_context) { common_tls_context.add_alpn_protocols("h2"); common_tls_context.add_alpn_protocols("http/1.1"); @@ -400,6 +401,13 @@ void ConfigHelper::initializeTls(bool ecdsa_cert, TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); validation_context->add_verify_certificate_hash(TEST_CLIENT_CERT_HASH); + // We'll negotiate up to TLSv1.3 for the tests that care, but it really + // depends on what the client sets. + if (tlsv1_3) { + common_tls_context.mutable_tls_params()->set_tls_maximum_protocol_version( + envoy::api::v2::auth::TlsParameters::TLSv1_3); + } + auto* tls_certificate = common_tls_context.add_tls_certificates(); if (ecdsa_cert) { tls_certificate->mutable_certificate_chain()->set_filename( diff --git a/test/config/utility.h b/test/config/utility.h index 438a32c57022c..1739a0727bb2d 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -33,7 +33,7 @@ class ConfigHelper { ConfigHelper(const Network::Address::IpVersion version, const std::string& config = HTTP_PROXY_CONFIG); - static void initializeTls(bool ecdsa_cert, + static void initializeTls(bool ecdsa_cert, bool tlsv1_3, envoy::api::v2::auth::CommonTlsContext& common_context); typedef std::function ConfigModifierFunction; @@ -94,7 +94,7 @@ class ConfigHelper { type); // Add the default SSL configuration. - void addSslConfig(bool ecdsa_cert = false); + void addSslConfig(bool ecdsa_cert = false, bool tlsv1_3 = false); // Renames the first listener to the name specified. void renameListener(const std::string& name); diff --git a/test/integration/ssl_integration_test.cc b/test/integration/ssl_integration_test.cc index 38eb260bd9a74..0c116e7da9d03 100644 --- a/test/integration/ssl_integration_test.cc +++ b/test/integration/ssl_integration_test.cc @@ -26,8 +26,8 @@ using testing::Return; namespace Envoy { namespace Ssl { -void SslIntegrationTest::initialize() { - config_helper_.addSslConfig(server_ecdsa_cert_); +void SslIntegrationTestBase::initialize() { + config_helper_.addSslConfig(server_ecdsa_cert_, server_tlsv1_3_); HttpIntegrationTest::initialize(); context_manager_ = std::make_unique(timeSystem()); @@ -35,14 +35,14 @@ void SslIntegrationTest::initialize() { registerTestServerPorts({"http"}); } -void SslIntegrationTest::TearDown() { +void SslIntegrationTestBase::TearDown() { HttpIntegrationTest::cleanupUpstreamAndDownstream(); codec_client_.reset(); context_manager_.reset(); } Network::ClientConnectionPtr -SslIntegrationTest::makeSslClientConnection(const ClientSslTransportOptions& options) { +SslIntegrationTestBase::makeSslClientConnection(const ClientSslTransportOptions& options) { Network::Address::InstanceConstSharedPtr address = getSslAddress(version_, lookupPort("http")); if (debug_with_s_client_) { const std::string s_client_cmd = TestEnvironment::substitute( @@ -64,7 +64,7 @@ SslIntegrationTest::makeSslClientConnection(const ClientSslTransportOptions& opt client_transport_socket_factory_ptr->createTransportSocket({}), nullptr); } -void SslIntegrationTest::checkStats() { +void SslIntegrationTestBase::checkStats() { const uint32_t expected_handshakes = debug_with_s_client_ ? 2 : 1; Stats::CounterSharedPtr counter = test_server_->counter(listenerStatPrefix("ssl.handshake")); EXPECT_EQ(expected_handshakes, counter->value()); @@ -119,69 +119,6 @@ TEST_P(SslIntegrationTest, RouterRequestAndResponseWithBodyNoBufferHttp2VerifySA checkStats(); } -// Server with an RSA certificate and a client with RSA/ECDSA cipher suites -// works. -TEST_P(SslIntegrationTest, RouterRequestAndResponseWithBodyNoBufferServerRsa) { - server_ecdsa_cert_ = false; - ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { - return makeSslClientConnection({}); - }; - testRouterRequestAndResponseWithBody(1024, 512, false, &creator); - checkStats(); -} - -// Server with an ECDSA certificate and a client with RSA/ECDSA cipher suites -// works. -TEST_P(SslIntegrationTest, RouterRequestAndResponseWithBodyNoBufferServerEcdsa) { - server_ecdsa_cert_ = true; - ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { - return makeSslClientConnection({}); - }; - testRouterRequestAndResponseWithBody(1024, 512, false, &creator); - checkStats(); -} - -// Server with an RSA certificate and a client with only RSA cipher suites -// works. -TEST_P(SslIntegrationTest, RouterRequestAndResponseWithBodyNoBufferClientRsaOnly) { - server_ecdsa_cert_ = false; - ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { - return makeSslClientConnection( - ClientSslTransportOptions().setCipherSuites({"ECDHE-RSA-AES128-GCM-SHA256"})); - }; - testRouterRequestAndResponseWithBody(1024, 512, false, &creator); - checkStats(); -} - -// Server has only an ECDSA certificate, client is only RSA capable, leads -// to a connection fail. -TEST_P(SslIntegrationTest, RouterRequestAndResponseWithBodyNoBufferServerEcdsaClientRsaOnly) { - server_ecdsa_cert_ = true; - initialize(); - EXPECT_FALSE( - makeRawHttpConnection(makeSslClientConnection(ClientSslTransportOptions().setCipherSuites( - {"ECDHE-RSA-AES128-GCM-SHA256"}))) - ->connected()); - Stats::CounterSharedPtr counter = - test_server_->counter(listenerStatPrefix("ssl.connection_error")); - EXPECT_EQ(1U, counter->value()); - counter->reset(); -} - -// Server has only an RSA certificate, client is only ECDSA capable, leads to connection fail. -TEST_P(SslIntegrationTest, RouterRequestAndResponseWithBodyNoBufferServerEcdsaClientEcdsaOnly) { - server_ecdsa_cert_ = false; - initialize(); - EXPECT_FALSE( - makeRawHttpConnection(makeSslClientConnection(ClientSslTransportOptions().setCipherSuites( - {"ECDHE-ECDSA-AES128-GCM-SHA256"}))) - ->connected()); - Stats::CounterSharedPtr counter = - test_server_->counter(listenerStatPrefix("ssl.connection_error")); - EXPECT_EQ(1U, counter->value()); - counter->reset(); -} - TEST_P(SslIntegrationTest, RouterHeaderOnlyRequestAndResponse) { ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection({}); @@ -230,6 +167,115 @@ TEST_P(SslIntegrationTest, AdminCertEndpoint) { EXPECT_STREQ("200", response->headers().Status()->value().c_str()); } +// Validate certificate selection across different certificate types and client TLS versions. +class SslCertficateIntegrationTest + : public SslIntegrationTestBase, + public testing::TestWithParam> { +public: + SslCertficateIntegrationTest() : SslIntegrationTestBase(std::get<0>(GetParam())) { + server_tlsv1_3_ = true; + } + + Network::ClientConnectionPtr + makeSslClientConnection(const ClientSslTransportOptions& options) override { + ClientSslTransportOptions modified_options{options}; + modified_options.setTlsVersion(tls_version_); + return SslIntegrationTestBase::makeSslClientConnection(modified_options); + } + + void TearDown() override { SslIntegrationTestBase::TearDown(); }; + + ClientSslTransportOptions rsaOnlyClientOptions() { + if (tls_version_ == envoy::api::v2::auth::TlsParameters::TLSv1_3) { + return ClientSslTransportOptions().setSigningAlgorithmsForTest("rsa_pss_rsae_sha256"); + } else { + return ClientSslTransportOptions().setCipherSuites({"ECDHE-RSA-AES128-GCM-SHA256"}); + } + } + + ClientSslTransportOptions ecdsaOnlyClientOptions() { + if (tls_version_ == envoy::api::v2::auth::TlsParameters::TLSv1_3) { + return ClientSslTransportOptions().setSigningAlgorithmsForTest("ecdsa_secp256r1_sha256"); + } else { + return ClientSslTransportOptions().setCipherSuites({"ECDHE-ECDSA-AES128-GCM-SHA256"}); + } + } + + static std::string ipClientVersionTestParamsToString( + const testing::TestParamInfo< + std::tuple>& + params) { + return fmt::format("{}_TLSv1_{}", + std::get<0>(params.param) == Network::Address::IpVersion::v4 ? "IPv4" + : "IPv6", + std::get<1>(params.param) - 1); + } + + const envoy::api::v2::auth::TlsParameters_TlsProtocol tls_version_{std::get<1>(GetParam())}; +}; + +INSTANTIATE_TEST_CASE_P( + IpVersionsClientVersions, SslCertficateIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + testing::Values(envoy::api::v2::auth::TlsParameters::TLSv1_2, + envoy::api::v2::auth::TlsParameters::TLSv1_3)), + SslCertficateIntegrationTest::ipClientVersionTestParamsToString); + +// Server with an RSA certificate and a client with RSA/ECDSA cipher suites works. +TEST_P(SslCertficateIntegrationTest, ServerRsa) { + server_ecdsa_cert_ = false; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection({}); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, &creator); + checkStats(); +} + +// Server with an ECDSA certificate and a client with RSA/ECDSA cipher suites works. +TEST_P(SslCertficateIntegrationTest, ServerEcdsa) { + server_ecdsa_cert_ = true; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection({}); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, &creator); + checkStats(); +} + +// Server with an RSA certificate and a client with only RSA cipher suites works. +TEST_P(SslCertficateIntegrationTest, ClientRsaOnly) { + server_ecdsa_cert_ = false; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(rsaOnlyClientOptions()); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, &creator); + checkStats(); +} + +// Server has only an ECDSA certificate, client is only RSA capable, leads to a connection fail. +TEST_P(SslCertficateIntegrationTest, ServerEcdsaClientRsaOnly) { + server_ecdsa_cert_ = true; + initialize(); + auto codec_client = makeRawHttpConnection(makeSslClientConnection(rsaOnlyClientOptions())); + EXPECT_FALSE(codec_client->connected()); + Stats::CounterSharedPtr counter = + test_server_->counter(listenerStatPrefix("ssl.connection_error")); + EXPECT_EQ(1U, counter->value()); + counter->reset(); +} + +// Server has only an RSA certificate, client is only ECDSA capable, leads to connection fail. +TEST_P(SslCertficateIntegrationTest, ServerEcdsaClientEcdsaOnly) { + server_ecdsa_cert_ = false; + initialize(); + EXPECT_FALSE( + makeRawHttpConnection(makeSslClientConnection(ecdsaOnlyClientOptions()))->connected()); + Stats::CounterSharedPtr counter = + test_server_->counter(listenerStatPrefix("ssl.connection_error")); + EXPECT_EQ(1U, counter->value()); + counter->reset(); +} + class SslCaptureIntegrationTest : public SslIntegrationTest { public: void initialize() override { diff --git a/test/integration/ssl_integration_test.h b/test/integration/ssl_integration_test.h index 190d086ebe142..1af732e4f631f 100644 --- a/test/integration/ssl_integration_test.h +++ b/test/integration/ssl_integration_test.h @@ -16,21 +16,22 @@ using testing::NiceMock; namespace Envoy { namespace Ssl { -class SslIntegrationTest : public HttpIntegrationTest, - public testing::TestWithParam { +class SslIntegrationTestBase : public HttpIntegrationTest { public: - SslIntegrationTest() - : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam(), realTime()) {} + SslIntegrationTestBase(Network::Address::IpVersion ip_version) + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, ip_version, realTime()) {} void initialize() override; - void TearDown() override; + void TearDown(); Network::ClientConnectionPtr makeSslConn() { return makeSslClientConnection({}); } - Network::ClientConnectionPtr makeSslClientConnection(const ClientSslTransportOptions& options); + virtual Network::ClientConnectionPtr + makeSslClientConnection(const ClientSslTransportOptions& options); void checkStats(); protected: + bool server_tlsv1_3_{false}; bool server_ecdsa_cert_{false}; // Set this true to debug SSL handshake issues with openssl s_client. The // verbose trace will be in the logs, openssl must be installed separately. @@ -40,5 +41,12 @@ class SslIntegrationTest : public HttpIntegrationTest, std::unique_ptr context_manager_; }; +class SslIntegrationTest : public SslIntegrationTestBase, + public testing::TestWithParam { +public: + SslIntegrationTest() : SslIntegrationTestBase(GetParam()) {} + void TearDown() override { SslIntegrationTestBase::TearDown(); }; +}; + } // namespace Ssl } // namespace Envoy diff --git a/test/integration/ssl_utility.cc b/test/integration/ssl_utility.cc index 1d97411b0c783..9a8963ccf0652 100644 --- a/test/integration/ssl_utility.cc +++ b/test/integration/ssl_utility.cc @@ -46,8 +46,12 @@ createClientSslTransportSocketFactory(const ClientSslTransportOptions& options, common_context->mutable_tls_params()->add_cipher_suites(cipher_suite); } + 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; - auto cfg = std::make_unique(tls_context, mock_factory_ctx); + auto cfg = + std::make_unique(tls_context, options.sigalgs_, mock_factory_ctx); static auto* client_stats_store = new Stats::TestIsolatedStoreImpl(); return Network::TransportSocketFactoryPtr{ new Ssl::ClientSslSocketFactory(std::move(cfg), context_manager, *client_stats_store)}; @@ -55,7 +59,7 @@ createClientSslTransportSocketFactory(const ClientSslTransportOptions& options, Network::TransportSocketFactoryPtr createUpstreamSslContext(ContextManager& context_manager) { envoy::api::v2::auth::DownstreamTlsContext tls_context; - ConfigHelper::initializeTls(false, *tls_context.mutable_common_tls_context()); + ConfigHelper::initializeTls(false, false, *tls_context.mutable_common_tls_context()); NiceMock mock_factory_ctx; auto cfg = std::make_unique(tls_context, mock_factory_ctx); diff --git a/test/integration/ssl_utility.h b/test/integration/ssl_utility.h index b8f9e125db8c3..2b188e675496a 100644 --- a/test/integration/ssl_utility.h +++ b/test/integration/ssl_utility.h @@ -24,9 +24,23 @@ struct ClientSslTransportOptions { return *this; } + ClientSslTransportOptions& + setTlsVersion(envoy::api::v2::auth::TlsParameters_TlsProtocol tls_version) { + tls_version_ = tls_version; + return *this; + } + + ClientSslTransportOptions& setSigningAlgorithmsForTest(const std::string& sigalgs) { + sigalgs_ = sigalgs; + return *this; + } + bool alpn_{}; bool san_{}; std::vector cipher_suites_{}; + envoy::api::v2::auth::TlsParameters_TlsProtocol tls_version_{ + envoy::api::v2::auth::TlsParameters::TLS_AUTO}; + std::string sigalgs_; }; Network::TransportSocketFactoryPtr