From 057f294c1aa6083b1f1cfd254bfbe932e6056a54 Mon Sep 17 00:00:00 2001 From: Dmitri Dolguikh Date: Mon, 3 May 2021 19:10:26 -0700 Subject: [PATCH] Fixes MAISTRA-2324: port of tls: separate out cert validation logic from ContextImpl (#14757) --- source/extensions/transport_sockets/tls/BUILD | 19 +- .../tls/cert_validator/BUILD | 39 ++ .../tls/cert_validator/cert_validator.h | 87 +++ .../tls/cert_validator/default_validator.cc | 483 ++++++++++++++++ .../tls/cert_validator/default_validator.h | 128 ++++ .../transport_sockets/tls/context_impl.cc | 546 ++---------------- .../transport_sockets/tls/context_impl.h | 151 +---- .../transport_sockets/tls/openssl_impl.cc | 5 - .../transport_sockets/tls/openssl_impl.h | 2 - .../extensions/transport_sockets/tls/stats.cc | 20 + .../extensions/transport_sockets/tls/stats.h | 37 ++ .../transport_sockets/tls/utility.cc | 33 ++ .../transport_sockets/tls/utility.h | 5 + .../tls/cert_validator/BUILD | 25 + .../cert_validator/default_validator_test.cc | 163 ++++++ .../tls/context_impl_test.cc | 139 ----- .../transport_sockets/tls/ssl_socket_test.cc | 1 - test/server/listener_manager_impl_test.cc | 8 +- 18 files changed, 1126 insertions(+), 765 deletions(-) create mode 100644 source/extensions/transport_sockets/tls/cert_validator/BUILD create mode 100644 source/extensions/transport_sockets/tls/cert_validator/cert_validator.h create mode 100644 source/extensions/transport_sockets/tls/cert_validator/default_validator.cc create mode 100644 source/extensions/transport_sockets/tls/cert_validator/default_validator.h create mode 100644 source/extensions/transport_sockets/tls/stats.cc create mode 100644 source/extensions/transport_sockets/tls/stats.h create mode 100644 test/extensions/transport_sockets/tls/cert_validator/BUILD create mode 100644 test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index e25c45f7bd..6caa0d0f41 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -141,12 +141,13 @@ envoy_cc_library( "abseil_node_hash_set", "abseil_synchronization", "ssl", - "bssl_wrapper_lib", + "bssl_wrapper_lib", ], # TLS is core functionality. visibility = ["//visibility:public"], deps = [ ":openssl_impl_lib", + ":stats_lib", ":utility_lib", "//include/envoy/ssl:context_config_interface", "//include/envoy/ssl:context_interface", @@ -164,6 +165,7 @@ envoy_cc_library( "//source/common/runtime:runtime_features_lib", "//source/common/stats:symbol_table_lib", "//source/common/stats:utility_lib", + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", "//source/extensions/transport_sockets/tls/ocsp:ocsp_lib", "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", @@ -171,6 +173,21 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "stats_lib", + srcs = ["stats.cc"], + hdrs = ["stats.h"], + external_deps = [ + "ssl", + ], + deps = [ + "//include/envoy/stats:stats_interface", + "//include/envoy/stats:stats_macros", + "//source/common/stats:symbol_table_lib", + "//source/common/stats:utility_lib", + ], +) + envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], diff --git a/source/extensions/transport_sockets/tls/cert_validator/BUILD b/source/extensions/transport_sockets/tls/cert_validator/BUILD new file mode 100644 index 0000000000..ca7d0ab11a --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/BUILD @@ -0,0 +1,39 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "cert_validator_lib", + srcs = [ + "default_validator.cc", + ], + hdrs = [ + "cert_validator.h", + "default_validator.h", + ], + external_deps = [ + "ssl", + "bssl_wrapper_lib", + ], + # TLS is core functionality. + visibility = ["//visibility:public"], + deps = [ + "//include/envoy/ssl:context_config_interface", + "//include/envoy/ssl:ssl_socket_extended_info_interface", + "//source/common/common:assert_lib", + "//source/common/common:base64_lib", + "//source/common/common:hex_lib", + "//source/common/common:utility_lib", + "//source/common/runtime:runtime_features_lib", + "//source/common/stats:symbol_table_lib", + "//source/common/stats:utility_lib", + "//source/extensions/transport_sockets/tls:stats_lib", + "//source/extensions/transport_sockets/tls:utility_lib", + ], +) diff --git a/source/extensions/transport_sockets/tls/cert_validator/cert_validator.h b/source/extensions/transport_sockets/tls/cert_validator/cert_validator.h new file mode 100644 index 0000000000..e5ae5b2c16 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/cert_validator.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/matchers.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/transport_sockets/tls/stats.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class CertValidator { +public: + virtual ~CertValidator() = default; + + /** + * Called to add the client validation context information to a given ssl context + * + * @param context the store context + * @param require_client_cert whether or not client cert is required + */ + virtual void addClientValidationContext(SSL_CTX* context, bool require_client_cert) PURE; + + /** + * Called by verifyCallback to do the actual cert chain verification. + * + * @param store_ctx the store context + * @param ssl_extended_info the info for storing the validation status + * @param leaf_cert the peer certificate to verify + * @return 1 to indicate verification success and 0 to indicate verification failure. + */ + virtual int + doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) PURE; + + /** + * Called to initialize all ssl contexts + * + * @param contexts the store context + * @param handshaker_provides_certificates whether or not a handshaker implementation provides + * certificates itself. + * @return the ssl verification mode flag + */ + virtual int initializeSslContexts(std::vector contexts, + bool handshaker_provides_certificates) PURE; + + /** + * Called when calculation hash for session context ids + * + * @param md the store context + * @param hash_buffer the buffer used for digest calculation + * @param hash_length the expected length of hash + */ + virtual void updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, + uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) PURE; + + virtual size_t daysUntilFirstCertExpires() const PURE; + virtual std::string getCaFileName() const PURE; + virtual Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const PURE; +}; + +using CertValidatorPtr = std::unique_ptr; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc new file mode 100644 index 0000000000..7b0fce7e83 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc @@ -0,0 +1,483 @@ +#include "extensions/transport_sockets/tls/cert_validator/default_validator.h" + +#include +#include +#include +#include +#include + +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/assert.h" +#include "common/common/base64.h" +#include "common/common/fmt.h" +#include "common/common/hex.h" +#include "common/common/matchers.h" +#include "common/common/utility.h" +#include "common/network/address_impl.h" +#include "common/protobuf/utility.h" +#include "common/runtime/runtime_features.h" +#include "common/stats/symbol_table_impl.h" +#include "common/stats/utility.h" + +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" +#include "extensions/transport_sockets/tls/stats.h" +#include "extensions/transport_sockets/tls/utility.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/err.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +DefaultCertValidator::DefaultCertValidator( + const Envoy::Ssl::CertificateValidationContextConfig* config, SslStats& stats, + TimeSource& time_source) + : config_(config), stats_(stats), time_source_(time_source) { + if (config_ != nullptr) { + allow_untrusted_certificate_ = config_->trustChainVerification() == + envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED; + } +}; + +int DefaultCertValidator::initializeSslContexts(std::vector contexts, + bool provides_certificates) { + + int verify_mode = SSL_VERIFY_NONE; + int verify_mode_validation_context = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + if (config_ != nullptr) { + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification verification = config_->trustChainVerification(); + if (verification == envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED) { + verify_mode = SSL_VERIFY_PEER; // Ensure client-certs will be requested even if we have + // nothing to verify against + verify_mode_validation_context = SSL_VERIFY_PEER; + } + } + + if (config_ != nullptr && !config_->caCert().empty() && !provides_certificates) { + ca_file_path_ = config_->caCertPath(); + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->caCert().data()), config_->caCert().size())); + RELEASE_ASSERT(bio != nullptr, ""); + // Based on BoringSSL's X509_load_cert_crl_file(). + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificates from ", config_->caCertPath())); + } + + for (auto& ctx : contexts) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + bool has_crl = false; + for (const X509_INFO* item : list.get()) { + if (item->x509) { + X509_STORE_add_cert(store, item->x509); + if (ca_cert_ == nullptr) { + X509_up_ref(item->x509); + ca_cert_.reset(item->x509); + } + } + if (item->crl) { + X509_STORE_add_crl(store, item->crl); + has_crl = true; + } + } + if (ca_cert_ == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificates from ", config_->caCertPath())); + } + if (has_crl) { + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + verify_mode = SSL_VERIFY_PEER; + verify_trusted_ca_ = true; + + // NOTE: We're using SSL_CTX_set_cert_verify_callback() instead of X509_verify_cert() + // directly. However, our new callback is still calling X509_verify_cert() under + // the hood. Therefore, to ignore cert expiration, we need to set the callback + // for X509_verify_cert to ignore that error. + if (config_->allowExpiredCertificate()) { + X509_STORE_set_verify_cb(store, DefaultCertValidator::ignoreCertificateExpirationCallback); + } + } + } + + if (config_ != nullptr && !config_->certificateRevocationList().empty()) { + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->certificateRevocationList().data()), + config_->certificateRevocationList().size())); + RELEASE_ASSERT(bio != nullptr, ""); + + // Based on BoringSSL's X509_load_cert_crl_file(). + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load CRL from ", config_->certificateRevocationListPath())); + } + + for (auto& ctx : contexts) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + for (const X509_INFO* item : list.get()) { + if (item->crl) { + X509_STORE_add_crl(store, item->crl); + } + } + + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + } + + const Envoy::Ssl::CertificateValidationContextConfig* cert_validation_config = config_; + if (cert_validation_config != nullptr) { + if (!cert_validation_config->verifySubjectAltNameList().empty()) { + verify_subject_alt_name_list_ = cert_validation_config->verifySubjectAltNameList(); + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->subjectAltNameMatchers().empty()) { + for (const envoy::type::matcher::v3::StringMatcher& matcher : + cert_validation_config->subjectAltNameMatchers()) { + subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher)); + } + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->verifyCertificateHashList().empty()) { + for (auto hash : cert_validation_config->verifyCertificateHashList()) { + // Remove colons from the 95 chars long colon-separated "fingerprint" + // in order to get the hex-encoded string. + if (hash.size() == 95) { + hash.erase(std::remove(hash.begin(), hash.end(), ':'), hash.end()); + } + const auto& decoded = Hex::decode(hash); + if (decoded.size() != SHA256_DIGEST_LENGTH) { + throw EnvoyException(absl::StrCat("Invalid hex-encoded SHA-256 ", hash)); + } + verify_certificate_hash_list_.push_back(decoded); + } + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->verifyCertificateSpkiList().empty()) { + for (const auto& hash : cert_validation_config->verifyCertificateSpkiList()) { + const auto decoded = Base64::decode(hash); + if (decoded.size() != SHA256_DIGEST_LENGTH) { + throw EnvoyException(absl::StrCat("Invalid base64-encoded SHA-256 ", hash)); + } + verify_certificate_spki_list_.emplace_back(decoded.begin(), decoded.end()); + } + verify_mode = verify_mode_validation_context; + } + } + + return verify_mode; +} + +int DefaultCertValidator::doVerifyCertChain( + 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); + if (ssl_extended_info) { + ssl_extended_info->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; + } + } + + Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( + &leaf_cert, + transport_socket_options && + !transport_socket_options->verifySubjectAltNameListOverride().empty() + ? transport_socket_options->verifySubjectAltNameListOverride() + : verify_subject_alt_name_list_, + subject_alt_name_matchers_); + + if (ssl_extended_info) { + if (ssl_extended_info->certificateValidationStatus() == + Envoy::Ssl::ClientValidationStatus::NotValidated) { + ssl_extended_info->setCertificateValidationStatus(validated); + } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { + ssl_extended_info->setCertificateValidationStatus(validated); + } + } + + return allow_untrusted_certificate_ ? 1 + : (validated != Envoy::Ssl::ClientValidationStatus::Failed); +} + +int DefaultCertValidator::ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx) { + if (!ok) { + int err = X509_STORE_CTX_get_error(store_ctx); + if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_CERT_NOT_YET_VALID) { + return 1; + } + } + + return ok; +} + +Envoy::Ssl::ClientValidationStatus DefaultCertValidator::verifyCertificate( + X509* cert, const std::vector& verify_san_list, + const std::vector& subject_alt_name_matchers) { + Envoy::Ssl::ClientValidationStatus validated = Envoy::Ssl::ClientValidationStatus::NotValidated; + + if (!verify_san_list.empty()) { + if (!verifySubjectAltName(cert, verify_san_list)) { + stats_.fail_verify_san_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + validated = Envoy::Ssl::ClientValidationStatus::Validated; + } + + if (!subject_alt_name_matchers.empty() && !matchSubjectAltName(cert, subject_alt_name_matchers)) { + stats_.fail_verify_san_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + + if (!verify_certificate_hash_list_.empty() || !verify_certificate_spki_list_.empty()) { + const bool valid_certificate_hash = + !verify_certificate_hash_list_.empty() && + verifyCertificateHashList(cert, verify_certificate_hash_list_); + const bool valid_certificate_spki = + !verify_certificate_spki_list_.empty() && + verifyCertificateSpkiList(cert, verify_certificate_spki_list_); + + if (!valid_certificate_hash && !valid_certificate_spki) { + stats_.fail_verify_cert_hash_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + + validated = Envoy::Ssl::ClientValidationStatus::Validated; + } + + return validated; +} + +bool DefaultCertValidator::verifySubjectAltName(X509* cert, + const std::vector& subject_alt_names) { + bssl::UniquePtr san_names( + static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + if (san_names == nullptr) { + return false; + } + for (const GENERAL_NAME* general_name : san_names.get()) { + const std::string san = Utility::generalNameAsString(general_name); + for (auto& config_san : subject_alt_names) { + if (general_name->type == GEN_DNS ? dnsNameMatch(config_san, san.c_str()) + : config_san == san) { + return true; + } + } + } + return false; +} + +bool DefaultCertValidator::dnsNameMatch(const absl::string_view dns_name, + const absl::string_view pattern) { + if (dns_name == pattern) { + return true; + } + + 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.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); + } else { + return dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); + } + } + } + + return false; +} + +bool DefaultCertValidator::matchSubjectAltName( + X509* cert, const std::vector& subject_alt_name_matchers) { + bssl::UniquePtr san_names( + static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + if (san_names == nullptr) { + return false; + } + for (const GENERAL_NAME* general_name : san_names.get()) { + const std::string san = Utility::generalNameAsString(general_name); + for (auto& config_san_matcher : subject_alt_name_matchers) { + // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics. + 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(), absl::string_view(san)) + : config_san_matcher.match(san)) { + return true; + } + } + } + return false; +} + +bool DefaultCertValidator::verifyCertificateSpkiList( + X509* cert, const std::vector>& expected_hashes) { + X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(cert); + if (pubkey == nullptr) { + return false; + } + uint8_t* spki = nullptr; + const int len = i2d_X509_PUBKEY(pubkey, &spki); + if (len < 0) { + return false; + } + bssl::UniquePtr free_spki(spki); + + std::vector computed_hash(SHA256_DIGEST_LENGTH); + SHA256(spki, len, computed_hash.data()); + + for (const auto& expected_hash : expected_hashes) { + if (computed_hash == expected_hash) { + return true; + } + } + return false; +} + +bool DefaultCertValidator::verifyCertificateHashList( + X509* cert, const std::vector>& expected_hashes) { + std::vector computed_hash(SHA256_DIGEST_LENGTH); + unsigned int n; + X509_digest(cert, EVP_sha256(), computed_hash.data(), &n); + RELEASE_ASSERT(n == computed_hash.size(), ""); + + for (const auto& expected_hash : expected_hashes) { + if (computed_hash == expected_hash) { + return true; + } + } + return false; +} + +void DefaultCertValidator::updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, + uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) { + int rc; + + // Hash all the settings that affect whether the server will allow/accept + // the client connection. This ensures that the client is always validated against + // the correct settings, even if session resumption across different listeners + // is enabled. + if (ca_cert_ != nullptr) { + rc = X509_digest(ca_cert_.get(), EVP_sha256(), hash_buffer, &hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + RELEASE_ASSERT(hash_length == SHA256_DIGEST_LENGTH, + fmt::format("invalid SHA256 hash length {}", hash_length)); + + rc = EVP_DigestUpdate(md.get(), hash_buffer, hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + + // verify_subject_alt_name_list_ can only be set with a ca_cert + for (const std::string& name : verify_subject_alt_name_list_) { + rc = EVP_DigestUpdate(md.get(), name.data(), name.size()); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + } + + for (const auto& hash : verify_certificate_hash_list_) { + rc = EVP_DigestUpdate(md.get(), hash.data(), + hash.size() * + sizeof(std::remove_reference::type::value_type)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + + for (const auto& hash : verify_certificate_spki_list_) { + rc = EVP_DigestUpdate(md.get(), hash.data(), + hash.size() * + sizeof(std::remove_reference::type::value_type)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } +} + +void DefaultCertValidator::addClientValidationContext(SSL_CTX* ctx, bool require_client_cert) { + if (config_ == nullptr || config_->caCert().empty()) { + return; + } + + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->caCert().data()), config_->caCert().size())); + RELEASE_ASSERT(bio != nullptr, ""); + // Based on BoringSSL's SSL_add_file_cert_subjects_to_stack(). + bssl::UniquePtr list(sk_X509_NAME_new( + [](const X509_NAME* const* a, const X509_NAME* const* b) -> int { return X509_NAME_cmp(*a, *b); })); + RELEASE_ASSERT(list != nullptr, ""); + for (;;) { + bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + if (cert == nullptr) { + break; + } + X509_NAME* name = X509_get_subject_name(cert.get()); + if (name == nullptr) { + throw EnvoyException(absl::StrCat("Failed to load trusted client CA certificates from ", + config_->caCertPath())); + } + // Check for duplicates. + // Note that BoringSSL call only returns 0 or 1. + // OpenSSL can also return -1, for example on sk_find calls in an empty list + if (sk_X509_NAME_find(list.get(), nullptr, name) >= 0) { + continue; + } + bssl::UniquePtr name_dup(X509_NAME_dup(name)); + if (name_dup == nullptr || !sk_X509_NAME_push(list.get(), name_dup.release())) { + throw EnvoyException(absl::StrCat("Failed to load trusted client CA certificates from ", + config_->caCertPath())); + } + } + + // Check for EOF. + const uint32_t err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { + ERR_clear_error(); + } else { + throw EnvoyException( + absl::StrCat("Failed to load trusted client CA certificates from ", config_->caCertPath())); + } + SSL_CTX_set_client_CA_list(ctx, list.release()); + + if (require_client_cert) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } +} + +Envoy::Ssl::CertificateDetailsPtr DefaultCertValidator::getCaCertInformation() const { + if (ca_cert_ == nullptr) { + return nullptr; + } + return Utility::certificateDetails(ca_cert_.get(), getCaFileName(), time_source_); +} + +size_t DefaultCertValidator::daysUntilFirstCertExpires() const { + return Utility::getDaysUntilExpiration(ca_cert_.get(), time_source_); +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/default_validator.h b/source/extensions/transport_sockets/tls/cert_validator/default_validator.h new file mode 100644 index 0000000000..104f72155d --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.h @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/matchers.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" +#include "extensions/transport_sockets/tls/stats.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class DefaultCertValidator : public CertValidator { +public: + DefaultCertValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, + SslStats& stats, TimeSource& time_source); + + ~DefaultCertValidator() override = default; + + // Tls::CertValidator + void addClientValidationContext(SSL_CTX* context, bool require_client_cert) override; + + int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) override; + + int initializeSslContexts(std::vector contexts, bool provides_certificates) override; + + void updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) override; + + size_t daysUntilFirstCertExpires() const override; + std::string getCaFileName() const override { return ca_file_path_; }; + Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const override; + + // utility functions + static int ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx); + + Envoy::Ssl::ClientValidationStatus + verifyCertificate(X509* cert, const std::vector& verify_san_list, + const std::vector& subject_alt_name_matchers); + + /** + * Verifies certificate hash for pinning. The hash is a hex-encoded SHA-256 of the DER-encoded + * certificate. + * + * @param ssl the certificate to verify + * @param expected_hashes the configured list of certificate hashes to match + * @return true if the verification succeeds + */ + static bool verifyCertificateHashList(X509* cert, + const std::vector>& expected_hashes); + + /** + * Verifies certificate hash for pinning. The hash is a base64-encoded SHA-256 of the DER-encoded + * Subject Public Key Information (SPKI) of the certificate. + * + * @param ssl the certificate to verify + * @param expected_hashes the configured list of certificate hashes to match + * @return true if the verification succeeds + */ + static bool verifyCertificateSpkiList(X509* cert, + const std::vector>& expected_hashes); + + /** + * Performs subjectAltName verification + * @param ssl the certificate to verify + * @param subject_alt_names the configured subject_alt_names to match + * @return true if the verification succeeds + */ + static bool verifySubjectAltName(X509* cert, const std::vector& subject_alt_names); + + /** + * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard. + * NOTE: public for testing + * @param dns_name the DNS name to match + * @param pattern the pattern to match against (*.example.com) + * @return true if the san matches pattern + */ + static bool dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern); + + /** + * Performs subjectAltName matching with the provided matchers. + * @param ssl the certificate to verify + * @param subject_alt_name_matchers the configured matchers to match + * @return true if the verification succeeds + */ + static bool + matchSubjectAltName(X509* cert, + const std::vector& subject_alt_name_matchers); + +private: + const Envoy::Ssl::CertificateValidationContextConfig* config_; + SslStats& stats_; + TimeSource& time_source_; + + bool allow_untrusted_certificate_{false}; + bssl::UniquePtr ca_cert_; + std::string ca_file_path_; + std::vector subject_alt_name_matchers_; + std::vector> verify_certificate_hash_list_; + std::vector> verify_certificate_spki_list_; + std::vector verify_subject_alt_name_list_; + bool verify_trusted_ca_{false}; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 27ff5cd48a..68e5b428e5 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -22,6 +22,8 @@ #include "common/runtime/runtime_features.h" #include "common/stats/utility.h" +#include "extensions/transport_sockets/tls/cert_validator/default_validator.h" +#include "extensions/transport_sockets/tls/stats.h" #include "extensions/transport_sockets/tls/openssl_impl.h" #include "extensions/transport_sockets/tls/utility.h" @@ -57,7 +59,7 @@ int ContextImpl::sslExtendedSocketInfoIndex() { ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source) - : scope_(scope), stats_(generateStats(scope)), time_source_(time_source), + : scope_(scope), stats_(generateSslStats(scope)), time_source_(time_source), tls_max_version_(config.maxProtocolVersion()), stat_name_set_(scope.symbolTable().makeSet("TransportSockets::Tls")), unknown_ssl_cipher_(stat_name_set_->add("unknown_ssl_cipher")), @@ -68,6 +70,9 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ssl_versions_(stat_name_set_->add("ssl.versions")), ssl_curves_(stat_name_set_->add("ssl.curves")), ssl_sigalgs_(stat_name_set_->add("ssl.sigalgs")), capabilities_(config.capabilities()) { + + cert_validator_ = std::make_unique(config.certificateValidationContext(), + stats_, time_source_); const auto tls_certificates = config.tlsCertificates(); tls_context_.cert_contexts_.resize(std::max(static_cast(1), tls_certificates.size())); @@ -82,7 +87,8 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c rc = SSL_CTX_set_max_proto_version(tls_context_.ssl_ctx_.get(), config.maxProtocolVersion()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - if (!Envoy::Extensions::TransportSockets::Tls::set_strict_cipher_list(tls_context_.ssl_ctx_.get(), config.cipherSuites().c_str())) { + if (!capabilities_.provides_ciphers_and_curves && + !Envoy::Extensions::TransportSockets::Tls::set_strict_cipher_list(tls_context_.ssl_ctx_.get(), config.cipherSuites().c_str())) { // Break up a set of ciphers into each individual cipher and try them each individually in // order to attempt to log which specific one failed. Example of config.cipherSuites(): // "-ALL:[ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]:ECDHE-ECDSA-AES128-SHA". @@ -110,154 +116,20 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c config.cipherSuites(), absl::StrJoin(bad_ciphers, ", "))); } - if (!SSL_CTX_set1_curves_list(tls_context_.ssl_ctx_.get(), config.ecdhCurves().c_str())) { + if (!capabilities_.provides_ciphers_and_curves && + !SSL_CTX_set1_curves_list(tls_context_.ssl_ctx_.get(), config.ecdhCurves().c_str())) { throw EnvoyException(absl::StrCat("Failed to initialize ECDH curves ", config.ecdhCurves())); } - int verify_mode = SSL_VERIFY_NONE; - int verify_mode_validation_context = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - - if (config.certificateValidationContext() != nullptr) { - envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: - TrustChainVerification verification = - config.certificateValidationContext()->trustChainVerification(); - if (verification == envoy::extensions::transport_sockets::tls::v3:: - CertificateValidationContext::ACCEPT_UNTRUSTED) { - verify_mode = SSL_VERIFY_PEER; // Ensure client-certs will be requested even if we have - // nothing to verify against - verify_mode_validation_context = SSL_VERIFY_PEER; - } - } - - if (config.certificateValidationContext() != nullptr && - !config.certificateValidationContext()->caCert().empty() && - !config.capabilities().provides_certificates) { - ca_file_path_ = config.certificateValidationContext()->caCertPath(); - bssl::UniquePtr bio( - BIO_new_mem_buf(const_cast(config.certificateValidationContext()->caCert().data()), - config.certificateValidationContext()->caCert().size())); - RELEASE_ASSERT(bio != nullptr, ""); - // Based on BoringSSL's X509_load_cert_crl_file(). - bssl::UniquePtr list( - PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); - if (list == nullptr) { - throw EnvoyException(absl::StrCat("Failed to load trusted CA certificates from ", - config.certificateValidationContext()->caCertPath())); - } - - X509_STORE* store = SSL_CTX_get_cert_store(tls_context_.ssl_ctx_.get()); - bool has_crl = false; - for (const X509_INFO* item : list.get()) { - if (item->x509) { - X509_STORE_add_cert(store, item->x509); - if (ca_cert_ == nullptr) { - X509_up_ref(item->x509); - ca_cert_.reset(item->x509); - } - } - if (item->crl) { - X509_STORE_add_crl(store, item->crl); - has_crl = true; - } - - // TODO (dmitri-d) why do we do this here? Upstream doesn't - Envoy::Extensions::TransportSockets::Tls::ssl_ctx_add_client_CA(tls_context_.ssl_ctx_.get(), - item->x509); - } - if (ca_cert_ == nullptr) { - throw EnvoyException(fmt::format("Failed to load trusted CA certificates from {}", - config.certificateValidationContext()->caCertPath())); - } - if (has_crl) { - X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); - } - verify_mode = SSL_VERIFY_PEER; - verify_trusted_ca_ = true; - - // NOTE: We're using SSL_CTX_set_cert_verify_callback() instead of X509_verify_cert() - // directly. However, our new callback is still calling X509_verify_cert() under - // the hood. Therefore, to ignore cert expiration, we need to set the callback - // for X509_verify_cert to ignore that error. - if (config.certificateValidationContext()->allowExpiredCertificate()) { - X509_STORE_set_verify_cb(store, ContextImpl::ignoreCertificateExpirationCallback); - } - } - - if (config.certificateValidationContext() != nullptr && - !config.certificateValidationContext()->certificateRevocationList().empty()) { - bssl::UniquePtr bio(BIO_new_mem_buf( - const_cast( - config.certificateValidationContext()->certificateRevocationList().data()), - config.certificateValidationContext()->certificateRevocationList().size())); - RELEASE_ASSERT(bio != nullptr, ""); - - // Based on BoringSSL's X509_load_cert_crl_file(). - bssl::UniquePtr list( - PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); - if (list == nullptr) { - throw EnvoyException( - absl::StrCat("Failed to load CRL from ", - config.certificateValidationContext()->certificateRevocationListPath())); - } - - X509_STORE* store = SSL_CTX_get_cert_store(tls_context_.ssl_ctx_.get()); - for (const X509_INFO* item : list.get()) { - if (item->crl) { - X509_STORE_add_crl(store, item->crl); - } - } - - X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); - } - - const Envoy::Ssl::CertificateValidationContextConfig* cert_validation_config = - config.certificateValidationContext(); - if (cert_validation_config != nullptr) { - if (!cert_validation_config->verifySubjectAltNameList().empty()) { - verify_subject_alt_name_list_ = cert_validation_config->verifySubjectAltNameList(); - verify_mode = verify_mode_validation_context; - } - - if (!cert_validation_config->subjectAltNameMatchers().empty()) { - for (const envoy::type::matcher::v3::StringMatcher& matcher : - cert_validation_config->subjectAltNameMatchers()) { - subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher)); - } - verify_mode = verify_mode_validation_context; - } - - if (!cert_validation_config->verifyCertificateHashList().empty()) { - for (auto hash : cert_validation_config->verifyCertificateHashList()) { - // Remove colons from the 95 chars long colon-separated "fingerprint" - // in order to get the hex-encoded string. - if (hash.size() == 95) { - hash.erase(std::remove(hash.begin(), hash.end(), ':'), hash.end()); - } - const auto& decoded = Hex::decode(hash); - if (decoded.size() != SHA256_DIGEST_LENGTH) { - throw EnvoyException(absl::StrCat("Invalid hex-encoded SHA-256 ", hash)); - } - verify_certificate_hash_list_.push_back(decoded); - } - verify_mode = verify_mode_validation_context; - } - - if (!cert_validation_config->verifyCertificateSpkiList().empty()) { - for (const auto& hash : cert_validation_config->verifyCertificateSpkiList()) { - const auto decoded = Base64::decode(hash); - if (decoded.size() != SHA256_DIGEST_LENGTH) { - throw EnvoyException(absl::StrCat("Invalid base64-encoded SHA-256 ", hash)); - } - verify_certificate_spki_list_.emplace_back(decoded.begin(), decoded.end()); - } - verify_mode = verify_mode_validation_context; - } - } - + // We only maintain one SSL_CTX under OpenSSL, but to keep maintenance simple[r], + // initializeSslContexts() parameter list was kept unchanged from upstream, + // hence construction of a vector of ssl contexts... + auto verify_mode = cert_validator_->initializeSslContexts( + { tls_context_.ssl_ctx_.get() }, config.capabilities().provides_certificates); if (!capabilities_.verifies_peer_certificates) { if (verify_mode != SSL_VERIFY_NONE) { SSL_CTX_set_verify(tls_context_.ssl_ctx_.get(), verify_mode, nullptr); - SSL_CTX_set_cert_verify_callback(tls_context_.ssl_ctx_.get(), ContextImpl::verifyCallback, this); + SSL_CTX_set_cert_verify_callback(tls_context_.ssl_ctx_.get(), verifyCallback, this); } } @@ -316,12 +188,13 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c if (must_staple == must_staple_ext_value) { cert_context.is_must_staple_ = true; } + bssl::UniquePtr public_key(X509_get_pubkey(cert_context.cert_chain_.get())); const int pkey_id = EVP_PKEY_id(public_key.get()); if (!cert_pkey_ids.insert(pkey_id).second) { throw EnvoyException(fmt::format("Failed to load certificate chain from {}, at most one " "certificate of a given type may be specified", - cert_context.cert_chain_file_path_)); + cert_context.cert_chain_file_path_)); } cert_context.is_ecdsa_ = pkey_id == EVP_PKEY_EC; switch (pkey_id) { @@ -351,7 +224,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c fmt::format("Failed to load certificate chain from {}, only RSA " "certificates with 2048-bit or larger keys are supported", cert_context.cert_chain_file_path_)); - } + } } break; } @@ -372,7 +245,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c */ } else { // Load private key. - bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), + bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), tls_certificate.privateKey().size())); RELEASE_ASSERT(bio != nullptr, ""); bssl::UniquePtr pkey( @@ -381,8 +254,9 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ? const_cast(tls_certificate.password().c_str()) : nullptr)); if (pkey == nullptr || !SSL_CTX_use_PrivateKey(tls_context_.ssl_ctx_.get(), pkey.get())) { - throw EnvoyException( - absl::StrCat("Failed to load private key from ", tls_certificate.privateKeyPath())); + throw EnvoyException(fmt::format("Failed to load private key from {}, Cause: {}", + tls_certificate.privateKeyPath(), + Utility::getLastCryptoError().value_or("unknown"))); } } } @@ -391,13 +265,6 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c // use the server's cipher list preferences SSL_CTX_set_options(tls_context_.ssl_ctx_.get(), SSL_OP_CIPHER_SERVER_PREFERENCE); - if (config.certificateValidationContext() != nullptr) { - allow_untrusted_certificate_ = - config.certificateValidationContext()->trustChainVerification() == - envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: - ACCEPT_UNTRUSTED; - } - parsed_alpn_protocols_ = parseAlpnProtocols(config.alpnProtocols()); // To enumerate the required builtin ciphers, curves, algorithms, and @@ -480,17 +347,6 @@ bssl::UniquePtr ContextImpl::newSsl(const Network::TransportSocketOptions*) return bssl::UniquePtr(SSL_new(tls_context_.ssl_ctx_.get())); } -int ContextImpl::ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* ctx) { - if (!ok) { - int err = X509_STORE_CTX_get_error(ctx); - if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_CERT_NOT_YET_VALID) { - return 1; - } - } - - return ok; -} - int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { ContextImpl* impl = reinterpret_cast(arg); SSL* ssl = reinterpret_cast( @@ -501,88 +357,13 @@ int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { cert = X509_STORE_CTX_get0_cert(store_ctx); } - return impl->doVerifyCertChain( + return impl->cert_validator_->doVerifyCertChain( store_ctx, reinterpret_cast( SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), *cert, static_cast(SSL_get_app_data(ssl))); } -int ContextImpl::doVerifyCertChain( - 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); - if (ssl_extended_info) { - ssl_extended_info->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; - } - } - - Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( - &leaf_cert, - transport_socket_options && - !transport_socket_options->verifySubjectAltNameListOverride().empty() - ? transport_socket_options->verifySubjectAltNameListOverride() - : verify_subject_alt_name_list_, - subject_alt_name_matchers_); - - if (ssl_extended_info) { - if (ssl_extended_info->certificateValidationStatus() == - Envoy::Ssl::ClientValidationStatus::NotValidated) { - ssl_extended_info->setCertificateValidationStatus(validated); - } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { - ssl_extended_info->setCertificateValidationStatus(validated); - } - } - - return allow_untrusted_certificate_ ? 1 - : (validated != Envoy::Ssl::ClientValidationStatus::Failed); -} - -Envoy::Ssl::ClientValidationStatus ContextImpl::verifyCertificate( - X509* cert, const std::vector& verify_san_list, - const std::vector& subject_alt_name_matchers) { - Envoy::Ssl::ClientValidationStatus validated = Envoy::Ssl::ClientValidationStatus::NotValidated; - - if (!verify_san_list.empty()) { - if (!verifySubjectAltName(cert, verify_san_list)) { - stats_.fail_verify_san_.inc(); - return Envoy::Ssl::ClientValidationStatus::Failed; - } - validated = Envoy::Ssl::ClientValidationStatus::Validated; - } - - if (!subject_alt_name_matchers.empty() && !matchSubjectAltName(cert, subject_alt_name_matchers)) { - stats_.fail_verify_san_.inc(); - return Envoy::Ssl::ClientValidationStatus::Failed; - } - - if (!verify_certificate_hash_list_.empty() || !verify_certificate_spki_list_.empty()) { - const bool valid_certificate_hash = - !verify_certificate_hash_list_.empty() && - verifyCertificateHashList(cert, verify_certificate_hash_list_); - const bool valid_certificate_spki = - !verify_certificate_spki_list_.empty() && - verifyCertificateSpkiList(cert, verify_certificate_spki_list_); - - if (!valid_certificate_hash && !valid_certificate_spki) { - stats_.fail_verify_cert_hash_.inc(); - return Envoy::Ssl::ClientValidationStatus::Failed; - } - - validated = Envoy::Ssl::ClientValidationStatus::Validated; - } - - return validated; -} - void ContextImpl::incCounter(const Stats::StatName name, absl::string_view value, const Stats::StatName fallback) const { Stats::Counter& counter = Stats::Utility::counterFromElements( @@ -653,108 +434,6 @@ std::vector ContextImpl::getPrivateKeyMe return providers; } -bool ContextImpl::matchSubjectAltName( - X509* cert, const std::vector& subject_alt_name_matchers) { - bssl::UniquePtr san_names( - static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); - if (san_names == nullptr) { - return false; - } - for (const GENERAL_NAME* general_name : san_names.get()) { - const std::string san = Utility::generalNameAsString(general_name); - for (auto& config_san_matcher : subject_alt_name_matchers) { - // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics. - 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(), absl::string_view(san)) - : config_san_matcher.match(san)) { - return true; - } - } - } - return false; -} - -bool ContextImpl::verifySubjectAltName(X509* cert, - const std::vector& subject_alt_names) { - bssl::UniquePtr san_names( - static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); - if (san_names == nullptr) { - return false; - } - for (const GENERAL_NAME* general_name : san_names.get()) { - const std::string san = Utility::generalNameAsString(general_name); - for (auto& config_san : subject_alt_names) { - if (general_name->type == GEN_DNS ? dnsNameMatch(config_san, san.c_str()) - : config_san == san) { - return true; - } - } - } - return false; -} - -bool ContextImpl::dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern) { - if (dns_name == pattern) { - return true; - } - - 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.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); - } else { - return dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); - } - } - } - - return false; -} - -bool ContextImpl::verifyCertificateHashList( - X509* cert, const std::vector>& expected_hashes) { - std::vector computed_hash(SHA256_DIGEST_LENGTH); - unsigned int n; - X509_digest(cert, EVP_sha256(), computed_hash.data(), &n); - RELEASE_ASSERT(n == computed_hash.size(), ""); - - for (const auto& expected_hash : expected_hashes) { - if (computed_hash == expected_hash) { - return true; - } - } - return false; -} - -bool ContextImpl::verifyCertificateSpkiList( - X509* cert, const std::vector>& expected_hashes) { - X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(cert); - if (pubkey == nullptr) { - return false; - } - uint8_t* spki = nullptr; - const int len = i2d_X509_PUBKEY(pubkey, &spki); - if (len < 0) { - return false; - } - bssl::UniquePtr free_spki(spki); - - std::vector computed_hash(SHA256_DIGEST_LENGTH); - SHA256(spki, len, computed_hash.data()); - - for (const auto& expected_hash : expected_hashes) { - if (computed_hash == expected_hash) { - return true; - } - } - return false; -} - SslStats ContextImpl::generateStats(Stats::Scope& store) { std::string prefix("ssl."); return {ALL_SSL_STATS(POOL_COUNTER_PREFIX(store, prefix), POOL_GAUGE_PREFIX(store, prefix), @@ -762,7 +441,7 @@ SslStats ContextImpl::generateStats(Stats::Scope& store) { } size_t ContextImpl::daysUntilFirstCertExpires() const { - int daysUntilExpiration = Utility::getDaysUntilExpiration(ca_cert_.get(), time_source_); + int daysUntilExpiration = cert_validator_->daysUntilFirstCertExpires(); for (auto& cert : tls_context_.cert_contexts_) { daysUntilExpiration = std::min( Utility::getDaysUntilExpiration(cert.cert_chain_.get(), time_source_), daysUntilExpiration); @@ -787,10 +466,7 @@ absl::optional ContextImpl::secondsUntilFirstOcspResponseExpires() con } Envoy::Ssl::CertificateDetailsPtr ContextImpl::getCaCertInformation() const { - if (ca_cert_ == nullptr) { - return nullptr; - } - return certificateDetails(ca_cert_.get(), getCaFileName(), nullptr); + return cert_validator_->getCaCertInformation(); } std::vector ContextImpl::getCertChainInformation() const { @@ -799,51 +475,21 @@ std::vector ContextImpl::getCertChainInformat if (cert.cert_chain_ == nullptr) { continue; } - cert_details.emplace_back(certificateDetails(cert.cert_chain_.get(), cert.getCertChainFileName(), - cert.ocsp_response_.get())); - } + auto detail = Utility::certificateDetails(cert.cert_chain_.get(), cert.getCertChainFileName(), + time_source_); + auto ocsp_resp = cert.ocsp_response_.get(); + if (ocsp_resp) { + auto* ocsp_details = detail->mutable_ocsp_details(); + ProtobufWkt::Timestamp* valid_from = ocsp_details->mutable_valid_from(); + TimestampUtil::systemClockToTimestamp(ocsp_resp->getThisUpdate(), *valid_from); + ProtobufWkt::Timestamp* expiration = ocsp_details->mutable_expiration(); + TimestampUtil::systemClockToTimestamp(ocsp_resp->getNextUpdate(), *expiration); + } + cert_details.push_back(std::move(detail)); + } return cert_details; } -Envoy::Ssl::CertificateDetailsPtr -ContextImpl::certificateDetails(X509* cert, const std::string& path, - const Ocsp::OcspResponseWrapper* ocsp_response) const { - Envoy::Ssl::CertificateDetailsPtr certificate_details = - std::make_unique(); - certificate_details->set_path(path); - certificate_details->set_serial_number(Utility::getSerialNumberFromCertificate(*cert)); - certificate_details->set_days_until_expiration( - Utility::getDaysUntilExpiration(cert, time_source_)); - if (ocsp_response) { - auto* ocsp_details = certificate_details->mutable_ocsp_details(); - ProtobufWkt::Timestamp* valid_from = ocsp_details->mutable_valid_from(); - TimestampUtil::systemClockToTimestamp(ocsp_response->getThisUpdate(), *valid_from); - ProtobufWkt::Timestamp* expiration = ocsp_details->mutable_expiration(); - TimestampUtil::systemClockToTimestamp(ocsp_response->getNextUpdate(), *expiration); - } - ProtobufWkt::Timestamp* valid_from = certificate_details->mutable_valid_from(); - TimestampUtil::systemClockToTimestamp(Utility::getValidFrom(*cert), *valid_from); - ProtobufWkt::Timestamp* expiration_time = certificate_details->mutable_expiration_time(); - TimestampUtil::systemClockToTimestamp(Utility::getExpirationTime(*cert), *expiration_time); - - for (auto& dns_san : Utility::getSubjectAltNames(*cert, GEN_DNS)) { - envoy::admin::v3::SubjectAlternateName& subject_alt_name = - *certificate_details->add_subject_alt_names(); - subject_alt_name.set_dns(dns_san); - } - for (auto& uri_san : Utility::getSubjectAltNames(*cert, GEN_URI)) { - envoy::admin::v3::SubjectAlternateName& subject_alt_name = - *certificate_details->add_subject_alt_names(); - subject_alt_name.set_uri(uri_san); - } - for (auto& ip_san : Utility::getSubjectAltNames(*cert, GEN_IPADD)) { - envoy::admin::v3::SubjectAlternateName& subject_alt_name = - *certificate_details->add_subject_alt_names(); - subject_alt_name.set_ip_address(ip_san); - } - return certificate_details; -} - ClientContextImpl::ClientContextImpl(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, TimeSource& time_source) @@ -1005,11 +651,9 @@ ServerContextImpl::ServerContextImpl(Stats::Scope& scope, const SessionContextID session_id = generateHashForSessionContextId(server_names); const auto tls_certificates = config.tlsCertificates(); - if (!config.capabilities().verifies_peer_certificates && - config.certificateValidationContext() != nullptr && - !config.certificateValidationContext()->caCert().empty()) { - tls_context_.addClientValidationContext(*config.certificateValidationContext(), - config.requireClientCertificate()); + if (!config.capabilities().verifies_peer_certificates) { + cert_validator_->addClientValidationContext(tls_context_.ssl_ctx_.get(), + config.requireClientCertificate()); } if (!parsed_alpn_protocols_.empty() && !config.capabilities().handles_alpn_selection) { @@ -1085,12 +729,11 @@ ServerContextImpl::ServerContextImpl(Stats::Scope& scope, ServerContextImpl::SessionContextID ServerContextImpl::generateHashForSessionContextId(const std::vector& server_names) { uint8_t hash_buffer[EVP_MAX_MD_SIZE]; - unsigned hash_length; + unsigned hash_length = 0; - // md is released before we return from the method - EVP_MD_CTX* md = Envoy::Extensions::TransportSockets::Tls::newEVP_MD_CTX(); + bssl::ScopedEVP_MD_CTX md; - int rc = EVP_DigestInit(md, EVP_sha256()); + int rc = EVP_DigestInit(md.get(), EVP_sha256()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); // Hash the CommonName/SANs of all the server certificates. This makes sure that sessions can only @@ -1114,7 +757,7 @@ ServerContextImpl::generateHashForSessionContextId(const std::vectortype) { case GEN_IPADD: - rc = EVP_DigestUpdate(md, san->d.iPAddress->data, san->d.iPAddress->length); + rc = EVP_DigestUpdate(md.get(), san->d.iPAddress->data, san->d.iPAddress->length); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); ++san_count; break; case GEN_DNS: - rc = EVP_DigestUpdate(md, ASN1_STRING_data(san->d.dNSName), + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.dNSName), ASN1_STRING_length(san->d.dNSName)); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); ++san_count; break; case GEN_URI: - rc = EVP_DigestUpdate(md, ASN1_STRING_data(san->d.uniformResourceIdentifier), + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.uniformResourceIdentifier), ASN1_STRING_length(san->d.uniformResourceIdentifier)); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); ++san_count; @@ -1157,48 +800,16 @@ ServerContextImpl::generateHashForSessionContextId(const std::vector::type::value_type)); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - } - - for (const auto& hash : verify_certificate_spki_list_) { - rc = EVP_DigestUpdate(md, hash.data(), - hash.size() * - sizeof(std::remove_reference::type::value_type)); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - } + cert_validator_->updateDigestForSessionId(md, hash_buffer, hash_length); // Hash configured SNIs for this context, so that sessions cannot be resumed across different // filter chains, even when using the same server certificate. for (const auto& name : server_names) { - rc = EVP_DigestUpdate(md, name.data(), name.size()); + rc = EVP_DigestUpdate(md.get(), name.data(), name.size()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); } @@ -1209,12 +820,11 @@ ServerContextImpl::generateHashForSessionContextId(const std::vectorsecond.get(); } -OcspStapleAction ServerContextImpl::ocspStapleAction(const ContextImpl::CertContext& cert_context, +OcspStapleAction ServerContextImpl::ocspStapleAction(const CertContext& cert_context, bool client_ocsp_capable) { if (!client_ocsp_capable) { return OcspStapleAction::ClientNotCapable; @@ -1373,52 +983,6 @@ int ServerContextImpl::handleOcspStapling(SSL* ssl, void*) { return SSL_TLSEXT_ERR_OK; } -void ServerContextImpl::TlsContext::addClientValidationContext( - const Envoy::Ssl::CertificateValidationContextConfig& config, bool require_client_cert) { - bssl::UniquePtr bio( - BIO_new_mem_buf(const_cast(config.caCert().data()), config.caCert().size())); - RELEASE_ASSERT(bio != nullptr, ""); - // Based on BoringSSL's SSL_add_file_cert_subjects_to_stack(). - bssl::UniquePtr list(sk_X509_NAME_new( - [](const X509_NAME* const* a, const X509_NAME* const* b) -> int { return X509_NAME_cmp(*a, *b); })); - RELEASE_ASSERT(list != nullptr, ""); - for (;;) { - bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); - if (cert == nullptr) { - break; - } - X509_NAME* name = X509_get_subject_name(cert.get()); - if (name == nullptr) { - throw EnvoyException( - absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); - } - // Check for duplicates. - if (sk_X509_NAME_find(list.get(), nullptr, name)) { - continue; - } - bssl::UniquePtr name_dup(X509_NAME_dup(name)); - if (name_dup == nullptr || !sk_X509_NAME_push(list.get(), name_dup.release())) { - throw EnvoyException( - absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); - } - } - // Check for EOF. - const uint32_t err = ERR_peek_last_error(); - if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { - ERR_clear_error(); - } else { - throw EnvoyException( - absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); - } - if (sk_X509_NAME_num(list.get()) > 0) - SSL_CTX_set_client_CA_list(ssl_ctx_.get(), list.release()); - - // SSL_VERIFY_PEER or stronger mode was already set in ContextImpl::ContextImpl(). - if (require_client_cert) { - SSL_CTX_set_verify(ssl_ctx_.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); - } -} - bool ContextImpl::verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediates, std::string& error_details) { bssl::UniquePtr ctx(X509_STORE_CTX_new()); @@ -1430,7 +994,7 @@ bool ContextImpl::verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediate return false; } - int res = doVerifyCertChain(ctx.get(), nullptr, leaf_cert, nullptr); + int res = cert_validator_->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 bc1961e991..efcc2cb842 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -17,8 +17,10 @@ #include "common/common/matchers.h" #include "common/stats/symbol_table_impl.h" +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" #include "extensions/transport_sockets/tls/context_manager_impl.h" #include "extensions/transport_sockets/tls/ocsp/ocsp.h" +#include "extensions/transport_sockets/tls/stats.h" #include "extensions/transport_sockets/tls/openssl_impl.h" #include "absl/synchronization/mutex.h" @@ -33,25 +35,30 @@ namespace Extensions { namespace TransportSockets { namespace Tls { -#define ALL_SSL_STATS(COUNTER, GAUGE, HISTOGRAM) \ - COUNTER(connection_error) \ - COUNTER(handshake) \ - COUNTER(session_reused) \ - COUNTER(no_certificate) \ - COUNTER(fail_verify_no_cert) \ - COUNTER(fail_verify_error) \ - COUNTER(fail_verify_san) \ - COUNTER(fail_verify_cert_hash) \ - COUNTER(ocsp_staple_failed) \ - COUNTER(ocsp_staple_omitted) \ - COUNTER(ocsp_staple_responses) \ - COUNTER(ocsp_staple_requests) - -/** - * Wrapper struct for SSL stats. @see stats_macros.h - */ -struct SslStats { - ALL_SSL_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) +struct CertContext { + bssl::UniquePtr cert_chain_; + std::string cert_chain_file_path_; + Ocsp::OcspResponseWrapperPtr ocsp_response_; + bool is_ecdsa_{}; + bool is_must_staple_{}; + std::string getCertChainFileName() const { return cert_chain_file_path_; }; + Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { + return private_key_method_provider_; + } +}; + +// Use a single context for certificates instead of one context per certificate as in the BoringSSL case. +// A single context is required to hold all certificates for OpenSSL, certificate selection is handled by OpenSSL. +struct TlsContext { + bssl::UniquePtr ssl_ctx_; + std::vector cert_contexts_; + // a map of cert hashes as calculated by X509_digest with EVP_sha1 to cert contexts + absl::flat_hash_map> cert_context_lookup_; + + void addClientValidationContext(const Envoy::Ssl::CertificateValidationContextConfig& config, + bool require_client_cert); + bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); }; class ContextImpl : public virtual Envoy::Ssl::Context { @@ -64,33 +71,6 @@ class ContextImpl : public virtual Envoy::Ssl::Context { */ void logHandshake(SSL* ssl) const; - /** - * Performs subjectAltName verification - * @param ssl the certificate to verify - * @param subject_alt_names the configured subject_alt_names to match - * @return true if the verification succeeds - */ - static bool verifySubjectAltName(X509* cert, const std::vector& subject_alt_names); - - /** - * Performs subjectAltName matching with the provided matchers. - * @param ssl the certificate to verify - * @param subject_alt_name_matchers the configured matchers to match - * @return true if the verification succeeds - */ - static bool - matchSubjectAltName(X509* cert, - const std::vector& subject_alt_name_matchers); - - /** - * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard. - * NOTE: public for testing - * @param dns_name the DNS name to match - * @param pattern the pattern to match against (*.example.com) - * @return true if the san matches pattern - */ - static bool dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern); - SslStats& stats() { return stats_; } /** @@ -118,94 +98,22 @@ class ContextImpl : public virtual Envoy::Ssl::Context { */ static int sslContextIndex(); - // A X509_STORE_CTX_verify_cb callback for ignoring cert expiration in X509_verify_cert(). - static int ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx); - // 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, - X509& 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); - - /** - * Verifies certificate hash for pinning. The hash is a hex-encoded SHA-256 of the DER-encoded - * certificate. - * - * @param ssl the certificate to verify - * @param expected_hashes the configured list of certificate hashes to match - * @return true if the verification succeeds - */ - static bool verifyCertificateHashList(X509* cert, - const std::vector>& expected_hashes); - - /** - * Verifies certificate hash for pinning. The hash is a base64-encoded SHA-256 of the DER-encoded - * Subject Public Key Information (SPKI) of the certificate. - * - * @param ssl the certificate to verify - * @param expected_hashes the configured list of certificate hashes to match - * @return true if the verification succeeds - */ - static bool verifyCertificateSpkiList(X509* cert, - const std::vector>& expected_hashes); - bool parseAndSetAlpn(const std::vector& alpn, SSL& ssl); std::vector parseAlpnProtocols(const std::string& alpn_protocols); static SslStats generateStats(Stats::Scope& scope); - std::string getCaFileName() const { return ca_file_path_; }; void incCounter(const Stats::StatName name, absl::string_view value, const Stats::StatName fallback) const; - Envoy::Ssl::CertificateDetailsPtr - certificateDetails(X509* cert, const std::string& path, - const Ocsp::OcspResponseWrapper* ocsp_response) const; - - struct CertContext { - bssl::UniquePtr cert_chain_; - std::string cert_chain_file_path_; - Ocsp::OcspResponseWrapperPtr ocsp_response_; - bool is_ecdsa_{}; - bool is_must_staple_{}; - std::string getCertChainFileName() const { return cert_chain_file_path_; }; - Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; - Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { - return private_key_method_provider_; - } - }; - - // Use a single context for certificates instead of one context per certificate as in the BoringSSL case. - // A single context is required to hold all certificates for OpenSSL, certificate selection is handled by OpenSSL. - struct TlsContext { - bssl::UniquePtr ssl_ctx_; - std::vector cert_contexts_; - // a map of cert hashes as calculated by X509_digest with EVP_sha1 to cert contexts - absl::flat_hash_map> cert_context_lookup_; - - void addClientValidationContext(const Envoy::Ssl::CertificateValidationContextConfig& config, - bool require_client_cert); - bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); - }; - TlsContext tls_context_; - bool verify_trusted_ca_{false}; - std::vector verify_subject_alt_name_list_; - std::vector subject_alt_name_matchers_; - std::vector> verify_certificate_hash_list_; - std::vector> verify_certificate_spki_list_; - bool allow_untrusted_certificate_{false}; + CertValidatorPtr cert_validator_; Stats::Scope& scope_; SslStats stats_; std::vector parsed_alpn_protocols_; - bssl::UniquePtr ca_cert_; bssl::UniquePtr cert_chain_; - std::string ca_file_path_; std::string cert_chain_file_path_; TimeSource& time_source_; const unsigned tls_max_version_; @@ -260,9 +168,8 @@ class ServerContextImpl : public ContextImpl, public Envoy::Ssl::ServerContext { bool isClientOcspCapable(SSL* ssl); // returns a reference to a CertContext created in ContextImpl ctor // matching cert SHA1 digest - const ContextImpl::CertContext& certificateContext(X509* cert); - OcspStapleAction ocspStapleAction(const ContextImpl::CertContext& cert_context, - bool client_ocsp_capable); + const CertContext& certificateContext(X509* cert); + OcspStapleAction ocspStapleAction(const CertContext& cert_context, bool client_ocsp_capable); int handleOcspStapling(SSL* ssl, void*); SessionContextID generateHashForSessionContextId(const std::vector& server_names); diff --git a/source/extensions/transport_sockets/tls/openssl_impl.cc b/source/extensions/transport_sockets/tls/openssl_impl.cc index 81cb5d3ac1..1fe1cbb58b 100644 --- a/source/extensions/transport_sockets/tls/openssl_impl.cc +++ b/source/extensions/transport_sockets/tls/openssl_impl.cc @@ -97,11 +97,6 @@ void allowRenegotiation(SSL* ssl) { // SSL_set_renegotiate_mode(ssl, mode); } -EVP_MD_CTX* newEVP_MD_CTX() { - EVP_MD_CTX* md(EVP_MD_CTX_new()); - return md; -} - SSL_SESSION* ssl_session_from_bytes(SSL* client_ssl_socket, const SSL_CTX* client_ssl_context, const std::string& client_session) { SSL_SESSION* client_ssl_session = SSL_get_session(client_ssl_socket); diff --git a/source/extensions/transport_sockets/tls/openssl_impl.h b/source/extensions/transport_sockets/tls/openssl_impl.h index 0ad97b3720..2c083d67c5 100644 --- a/source/extensions/transport_sockets/tls/openssl_impl.h +++ b/source/extensions/transport_sockets/tls/openssl_impl.h @@ -30,8 +30,6 @@ STACK_OF(X509)* SSL_get_peer_full_cert_chain(const SSL *ssl); void allowRenegotiation(SSL* ssl); -EVP_MD_CTX* newEVP_MD_CTX(); - SSL_SESSION* ssl_session_from_bytes(SSL* client_ssl_socket, const SSL_CTX* client_ssl_context, const std::string& client_session); diff --git a/source/extensions/transport_sockets/tls/stats.cc b/source/extensions/transport_sockets/tls/stats.cc new file mode 100644 index 0000000000..f162e9db75 --- /dev/null +++ b/source/extensions/transport_sockets/tls/stats.cc @@ -0,0 +1,20 @@ +#include "extensions/transport_sockets/tls/stats.h" + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +SslStats generateSslStats(Stats::Scope& store) { + std::string prefix("ssl."); + return {ALL_SSL_STATS(POOL_COUNTER_PREFIX(store, prefix), POOL_GAUGE_PREFIX(store, prefix), + POOL_HISTOGRAM_PREFIX(store, prefix))}; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/stats.h b/source/extensions/transport_sockets/tls/stats.h new file mode 100644 index 0000000000..05023af677 --- /dev/null +++ b/source/extensions/transport_sockets/tls/stats.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +#define ALL_SSL_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(connection_error) \ + COUNTER(handshake) \ + COUNTER(session_reused) \ + COUNTER(no_certificate) \ + COUNTER(fail_verify_no_cert) \ + COUNTER(fail_verify_error) \ + COUNTER(fail_verify_san) \ + COUNTER(fail_verify_cert_hash) \ + COUNTER(ocsp_staple_failed) \ + COUNTER(ocsp_staple_omitted) \ + COUNTER(ocsp_staple_responses) \ + COUNTER(ocsp_staple_requests) + +/** + * Wrapper struct for SSL stats. @see stats_macros.h + */ +struct SslStats { + ALL_SSL_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) +}; + +SslStats generateSslStats(Stats::Scope& store); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/utility.cc b/source/extensions/transport_sockets/tls/utility.cc index fd68e483ed..9d3d425108 100644 --- a/source/extensions/transport_sockets/tls/utility.cc +++ b/source/extensions/transport_sockets/tls/utility.cc @@ -3,6 +3,7 @@ #include "openssl/err.h" #include "common/common/assert.h" #include "common/network/address_impl.h" +#include "common/protobuf/utility.h" #include "absl/strings/str_join.h" #include "openssl/x509v3.h" @@ -12,6 +13,38 @@ namespace Extensions { namespace TransportSockets { namespace Tls { +Envoy::Ssl::CertificateDetailsPtr Utility::certificateDetails(X509* cert, const std::string& path, + TimeSource& time_source) { + Envoy::Ssl::CertificateDetailsPtr certificate_details = + std::make_unique(); + certificate_details->set_path(path); + certificate_details->set_serial_number(Utility::getSerialNumberFromCertificate(*cert)); + certificate_details->set_days_until_expiration( + Utility::getDaysUntilExpiration(cert, time_source)); + + ProtobufWkt::Timestamp* valid_from = certificate_details->mutable_valid_from(); + TimestampUtil::systemClockToTimestamp(Utility::getValidFrom(*cert), *valid_from); + ProtobufWkt::Timestamp* expiration_time = certificate_details->mutable_expiration_time(); + TimestampUtil::systemClockToTimestamp(Utility::getExpirationTime(*cert), *expiration_time); + + for (auto& dns_san : Utility::getSubjectAltNames(*cert, GEN_DNS)) { + envoy::admin::v3::SubjectAlternateName& subject_alt_name = + *certificate_details->add_subject_alt_names(); + subject_alt_name.set_dns(dns_san); + } + for (auto& uri_san : Utility::getSubjectAltNames(*cert, GEN_URI)) { + envoy::admin::v3::SubjectAlternateName& subject_alt_name = + *certificate_details->add_subject_alt_names(); + subject_alt_name.set_uri(uri_san); + } + for (auto& ip_san : Utility::getSubjectAltNames(*cert, GEN_IPADD)) { + envoy::admin::v3::SubjectAlternateName& subject_alt_name = + *certificate_details->add_subject_alt_names(); + subject_alt_name.set_ip_address(ip_san); + } + return certificate_details; +} + namespace { enum class CertName { Issuer, Subject }; diff --git a/source/extensions/transport_sockets/tls/utility.h b/source/extensions/transport_sockets/tls/utility.h index 531ae02a3a..31d5f92e47 100644 --- a/source/extensions/transport_sockets/tls/utility.h +++ b/source/extensions/transport_sockets/tls/utility.h @@ -3,6 +3,8 @@ #include #include +#include "envoy/ssl/context.h" + #include "common/common/utility.h" #include "absl/types/optional.h" @@ -18,6 +20,9 @@ namespace TransportSockets { namespace Tls { namespace Utility { +Envoy::Ssl::CertificateDetailsPtr certificateDetails(X509* cert, const std::string& path, + TimeSource& time_source); + /** * Retrieves the serial number of a certificate. * @param cert the certificate diff --git a/test/extensions/transport_sockets/tls/cert_validator/BUILD b/test/extensions/transport_sockets/tls/cert_validator/BUILD new file mode 100644 index 0000000000..e8675ccec1 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/BUILD @@ -0,0 +1,25 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "default_validator_test", + srcs = [ + "default_validator_test.cc", + ], + data = [ + "//test/extensions/transport_sockets/tls/test_data:certs", + ], + deps = [ + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", + "//test/extensions/transport_sockets/tls:ssl_test_utils", + "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", + ], +) diff --git a/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc new file mode 100644 index 0000000000..2edbe80b49 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc @@ -0,0 +1,163 @@ +#include +#include + +#include "extensions/transport_sockets/tls/cert_validator/default_validator.h" + +#include "test/extensions/transport_sockets/tls/ssl_test_utility.h" +#include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +TEST(DefaultCertValidatorTest, TestDnsNameMatching) { + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("lyft.com", "lyft.com")); + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("a.lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("foo.test.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "")); +} + +TEST(DefaultCertValidatorTest, TestDnsNameMatchingLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("lyft.com", "lyft.com")); + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("a.lyft.com", "*.lyft.com")); + // Legacy behavior + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("foo.test.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "")); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameDNSMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"server1.example.com", + "server2.example.com"}; + EXPECT_TRUE(DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameDNSMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_hidden_envoy_deprecated_regex(".*.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameWildcardDNSMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("api.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMultiLevelMatch) { + // san_multiple_dns_cert matches *.example.com + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("foo.api.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMultiLevelMatchLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("foo.api.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameURIMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + std::vector verify_subject_alt_name_list = {"spiffe://lyft.com/fake-team", + "spiffe://lyft.com/test-team"}; + EXPECT_TRUE(DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltMultiDomain) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; + EXPECT_FALSE( + DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltMultiDomainLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; + EXPECT_TRUE(DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameURIMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_hidden_envoy_deprecated_regex("spiffe://lyft.com/.*-team"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameNotMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"foo", "bar"}; + EXPECT_FALSE( + DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameNotMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_hidden_envoy_deprecated_regex(".*.foo.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 16dc6b221f..4b6da12e75 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -106,145 +106,6 @@ class SslContextImplTest : public SslCertsTest { ContextManagerImpl manager_{time_system_}; }; -TEST_F(SslContextImplTest, TestDnsNameMatching) { - EXPECT_TRUE(ContextImpl::dnsNameMatch("lyft.com", "lyft.com")); - EXPECT_TRUE(ContextImpl::dnsNameMatch("a.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("foo.test.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "")); -} - -TEST_F(SslContextImplTest, TestDnsNameMatchingLegacy) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); - EXPECT_TRUE(ContextImpl::dnsNameMatch("lyft.com", "lyft.com")); - EXPECT_TRUE(ContextImpl::dnsNameMatch("a.lyft.com", "*.lyft.com")); - // Legacy behavior - EXPECT_TRUE(ContextImpl::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("foo.test.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "")); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltNameDNSMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"server1.example.com", - "server2.example.com"}; - EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameDNSMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_hidden_envoy_deprecated_regex(".*.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameWildcardDNSMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_exact("api.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestMultiLevelMatch) { - // san_multiple_dns_cert matches *.example.com - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_exact("foo.api.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_FALSE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestMultiLevelMatchLegacy) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_exact("foo.api.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltNameURIMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); - std::vector verify_subject_alt_name_list = {"spiffe://lyft.com/fake-team", - "spiffe://lyft.com/test-team"}; - EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltMultiDomain) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; - EXPECT_FALSE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltMultiDomainLegacy) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; - EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameURIMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_hidden_envoy_deprecated_regex("spiffe://lyft.com/.*-team"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltNameNotMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"foo", "bar"}; - EXPECT_FALSE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameNotMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_hidden_envoy_deprecated_regex(".*.foo.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_FALSE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - TEST_F(SslContextImplTest, TestCipherSuites) { const std::string yaml = R"EOF( common_tls_context: diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index f3ca3a2476..207e723d08 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -2743,7 +2743,6 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { auto socket = std::make_shared( Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockTcpListenerCallbacks callbacks; - Network::MockConnectionHandler connection_handler; Network::ListenerPtr listener = dispatcher_->createListener(socket, callbacks, true, ENVOY_TCP_BACKLOG_SIZE); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index c59099857c..7eb7bdb9f8 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -3525,8 +3525,8 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidIntermediate )EOF"), Network::Address::IpVersion::v4); - EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), - EnvoyException, "Failed to load certificate chain from "); + EXPECT_THROW_WITH_REGEX(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), + EnvoyException, "^Failed to load certificate chain from "); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) { @@ -3545,8 +3545,8 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) )EOF", Network::Address::IpVersion::v4); - EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), - EnvoyException, "Failed to load private key from "); + EXPECT_THROW_WITH_REGEX(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), + EnvoyException, "^Failed to load private key from "); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) {