diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 502739958e50..6f136859fa18 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -27,8 +27,10 @@ #include "absl/container/node_hash_set.h" #include "absl/strings/match.h" #include "absl/strings/str_join.h" +#include "openssl/bytestring.h" #include "openssl/evp.h" #include "openssl/hmac.h" +#include "openssl/pool.h" #include "openssl/rand.h" namespace Envoy { @@ -52,6 +54,92 @@ bool cbsContainsU16(CBS& cbs, uint16_t n) { return false; } +int sslTlsContextIndex() { + CONSTRUCT_ON_FIRST_USE(int, []() -> int { + int ssl_context_index = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + RELEASE_ASSERT(ssl_context_index >= 0, ""); + return ssl_context_index; + }()); +} + +bool seekToSubject(CRYPTO_BUFFER* cert, CBS* tbs_certificate) { + CBS der; + CRYPTO_BUFFER_init_CBS(cert, &der); + CBS certificate, opt_version, serial, signature, issuer, validity; + // From RFC 5280, section 4.1 + // + // Certificate ::= SEQUENCE { + // tbsCertificate TBSCertificate, + // signatureAlgorithm AlgorithmIdentifier, + // signatureValue BIT STRING } + // + // TBSCertificate ::= SEQUENCE { + // version [0] EXPLICIT Version DEFAULT v1, + // serialNumber CertificateSerialNumber, + // signature AlgorithmIdentifier, + // issuer Name, + // validity Validity, + // subject Name, + // subjectPublicKeyInfo SubjectPublicKeyInfo, + // ... + if (!CBS_get_asn1(&der, &certificate, CBS_ASN1_SEQUENCE) || + CBS_len(&der) != 0 || // We don't allow junk after the certificate. + !CBS_get_asn1(&certificate, tbs_certificate, CBS_ASN1_SEQUENCE) || + !CBS_get_optional_asn1(tbs_certificate, &opt_version, nullptr, + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) || + !CBS_get_asn1(tbs_certificate, &serial, CBS_ASN1_INTEGER) || + !CBS_get_asn1(tbs_certificate, &signature, CBS_ASN1_SEQUENCE) || + !CBS_get_asn1(tbs_certificate, &issuer, CBS_ASN1_SEQUENCE) || + !CBS_get_asn1(tbs_certificate, &validity, CBS_ASN1_SEQUENCE)) { + return false; + } + + return true; +} + +bool extractSPKIFromDERCert(CRYPTO_BUFFER* cert, CBS* spki) { + CBS tbs_certificate; + if (!seekToSubject(cert, &tbs_certificate)) { + return false; + } + + CBS subject; + if (!CBS_get_asn1(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE) || + !CBS_get_asn1_element(&tbs_certificate, spki, CBS_ASN1_SEQUENCE)) { + return false; + } + + return true; +} + +absl::optional extractSubjectNameFromDERCert(CRYPTO_BUFFER* cert) { + CBS tbs_certificate; + if (!seekToSubject(cert, &tbs_certificate)) { + return absl::nullopt; + } + + CBS subject; + if (!CBS_get_asn1_element(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE)) { + return absl::nullopt; + } + + return absl::string_view(reinterpret_cast(CBS_data(&subject)), CBS_len(&subject)); +} + +void pushBufferIfUnique(STACK_OF(CRYPTO_BUFFER) * stack, absl::string_view value) { + for (auto buf : stack) { + if (CRYPTO_BUFFER_len(buf) == value.size() && + memcmp(CRYPTO_BUFFER_data(buf), value.data(), value.size()) == 0) { + return; + } + } + + bssl::UniquePtr buf( + CRYPTO_BUFFER_new(reinterpret_cast(value.data()), value.size(), nullptr)); + bool rc = bssl::PushToStack(stack, std::move(buf)); + RELEASE_ASSERT(rc == true, Utility::getLastCryptoError().value_or("")); +} + } // namespace int ContextImpl::sslExtendedSocketInfoIndex() { @@ -79,11 +167,16 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c tls_contexts_.resize(std::max(static_cast(1), tls_certificates.size())); for (auto& ctx : tls_contexts_) { - ctx.ssl_ctx_.reset(SSL_CTX_new(TLS_method())); + ctx.ssl_ctx_.reset(SSL_CTX_new(TLS_with_buffers_method())); + ctx.x509_store_.reset(X509_STORE_new()); + RELEASE_ASSERT(ctx.x509_store_ != nullptr, Utility::getLastCryptoError().value_or("")); int rc = SSL_CTX_set_app_data(ctx.ssl_ctx_.get(), this); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + rc = SSL_CTX_set_ex_data(ctx.ssl_ctx_.get(), sslTlsContextIndex(), &ctx); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + rc = SSL_CTX_set_min_proto_version(ctx.ssl_ctx_.get(), config.minProtocolVersion()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); @@ -154,7 +247,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c } for (auto& ctx : tls_contexts_) { - X509_STORE* store = SSL_CTX_get_cert_store(ctx.ssl_ctx_.get()); + X509_STORE* store = ctx.x509_store_.get(); bool has_crl = false; for (const X509_INFO* item : list.get()) { if (item->x509) { @@ -179,10 +272,10 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c 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. + // NOTE: We're using SSL_CTX_set_custom_verify() 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); } @@ -207,7 +300,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c } for (auto& ctx : tls_contexts_) { - X509_STORE* store = SSL_CTX_get_cert_store(ctx.ssl_ctx_.get()); + X509_STORE* store = ctx.x509_store_.get(); for (const X509_INFO* item : list.get()) { if (item->crl) { X509_STORE_add_crl(store, item->crl); @@ -263,9 +356,12 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c } for (auto& ctx : tls_contexts_) { - if (verify_mode != SSL_VERIFY_NONE) { - SSL_CTX_set_verify(ctx.ssl_ctx_.get(), verify_mode, nullptr); - SSL_CTX_set_cert_verify_callback(ctx.ssl_ctx_.get(), ContextImpl::verifyCallback, this); + if (verify_mode == SSL_VERIFY_NONE) { + SSL_CTX_set_custom_verify( + ctx.ssl_ctx_.get(), SSL_VERIFY_NONE, + [](SSL*, uint8_t*) -> ssl_verify_result_t { return ssl_verify_ok; } /* always succeed */); + } else { + SSL_CTX_set_custom_verify(ctx.ssl_ctx_.get(), verify_mode, ContextImpl::verifyCallback); } } @@ -279,29 +375,24 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c BIO_new_mem_buf(const_cast(tls_certificate.certificateChain().data()), tls_certificate.certificateChain().size())); RELEASE_ASSERT(bio != nullptr, ""); - ctx.cert_chain_.reset(PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, nullptr)); - if (ctx.cert_chain_ == nullptr || - !SSL_CTX_use_certificate(ctx.ssl_ctx_.get(), ctx.cert_chain_.get())) { - while (uint64_t err = ERR_get_error()) { - ENVOY_LOG_MISC(debug, "SSL error: {}:{}:{}:{}", err, ERR_lib_error_string(err), - ERR_func_error_string(err), ERR_GET_REASON(err), - ERR_reason_error_string(err)); - } + uint8_t* data = nullptr; + long len; + if (!PEM_bytes_read_bio(&data, &len, nullptr, PEM_STRING_X509, bio.get(), nullptr, nullptr)) { throw EnvoyException( absl::StrCat("Failed to load certificate chain from ", ctx.cert_chain_file_path_)); } + { + bssl::UniquePtr der(data); + ctx.cert_.reset(CRYPTO_BUFFER_new(data, len, nullptr)); + } // Read rest of the certificate chain. + std::vector> chain; while (true) { - bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); - if (cert == nullptr) { + if (!PEM_bytes_read_bio(&data, &len, nullptr, PEM_STRING_X509, bio.get(), nullptr, nullptr)) { break; } - if (!SSL_CTX_add_extra_chain_cert(ctx.ssl_ctx_.get(), cert.get())) { - throw EnvoyException( - absl::StrCat("Failed to load certificate chain from ", ctx.cert_chain_file_path_)); - } - // SSL_CTX_add_extra_chain_cert() takes ownership. - cert.release(); + bssl::UniquePtr der(data); + chain.push_back(bssl::UniquePtr(CRYPTO_BUFFER_new(data, len, nullptr))); } // Check for EOF. const uint32_t err = ERR_peek_last_error(); @@ -312,7 +403,17 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c absl::StrCat("Failed to load certificate chain from ", ctx.cert_chain_file_path_)); } - bssl::UniquePtr public_key(X509_get_pubkey(ctx.cert_chain_.get())); + std::vector certs(chain.size() + 1); + certs[0] = ctx.cert_.get(); + for (size_t i = 0; i < chain.size(); ++i) { + certs[i + 1] = chain[i].get(); + } + + CBS spki; + if (!extractSPKIFromDERCert(ctx.cert_.get(), &spki)) { + throw EnvoyException(absl::StrCat("Failed to extract SPKI from ", ctx.cert_chain_file_path_)); + } + bssl::UniquePtr public_key(EVP_parse_public_key(&spki)); 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 " @@ -381,7 +482,11 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c fmt::format("Private key method doesn't support FIPS mode with current parameters")); } #endif - SSL_CTX_set_private_key_method(ctx.ssl_ctx_.get(), private_key_method.get()); + if (!SSL_CTX_set_chain_and_key(ctx.ssl_ctx_.get(), certs.data(), certs.size(), nullptr, + private_key_method.get())) { + throw EnvoyException( + absl::StrCat("Failed to set certificate chain from ", ctx.cert_chain_file_path_)); + } } else { // Load private key. bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), @@ -392,10 +497,16 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c !tls_certificate.password().empty() ? const_cast(tls_certificate.password().c_str()) : nullptr)); - if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ctx.ssl_ctx_.get(), pkey.get())) { + if (pkey == nullptr) { throw EnvoyException( absl::StrCat("Failed to load private key from ", tls_certificate.privateKeyPath())); } + if (!SSL_CTX_set_chain_and_key(ctx.ssl_ctx_.get(), certs.data(), certs.size(), pkey.get(), + nullptr)) { + throw EnvoyException(absl::StrCat( + "Failed to set certificate chain from ", tls_certificate.certificateChainPath(), + " and/or private key from ", tls_certificate.privateKeyPath())); + } #ifdef BORINGSSL_FIPS // Verify that private keys are passing FIPS pairwise consistency tests. @@ -523,22 +634,51 @@ int ContextImpl::ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* ctx return ok; } -int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { - ContextImpl* impl = reinterpret_cast(arg); - SSL* ssl = reinterpret_cast( - X509_STORE_CTX_get_ex_data(store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - auto cert = bssl::UniquePtr(SSL_get_peer_certificate(ssl)); - return impl->doVerifyCertChain( - store_ctx, +ssl_verify_result_t ContextImpl::verifyCallback(SSL* ssl, uint8_t* out_alert) { + const STACK_OF(CRYPTO_BUFFER)* buffers = SSL_get0_peer_certificates(ssl); + bssl::UniquePtr cert = nullptr; + bssl::UniquePtr chain(sk_X509_new(nullptr)); + for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(buffers); ++i) { + bssl::UniquePtr x509(X509_parse_from_buffer(sk_CRYPTO_BUFFER_value(buffers, i))); + if (!x509) { + return ssl_verify_invalid; + } + if (!cert) { + cert = std::move(x509); + } else { + bool rc = bssl::PushToStack(chain.get(), std::move(x509)); + RELEASE_ASSERT(rc == true, Utility::getLastCryptoError().value_or("")); + } + } + + ContextImpl* impl = reinterpret_cast(SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); + const Network::TransportSocketOptions* transport_socket_options = + static_cast(SSL_get_app_data(ssl)); + Ssl::SslExtendedSocketInfo* ssl_extended_info = reinterpret_cast( - SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), - *cert, static_cast(SSL_get_app_data(ssl))); + SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())); + + if (impl->verify_trusted_ca_) { + bssl::ScopedX509_STORE_CTX store_ctx; + if (!X509_STORE_CTX_init(store_ctx.get(), impl->tls_contexts_[0].x509_store_.get(), cert.get(), + chain.get()) || + !X509_STORE_CTX_set_default(store_ctx.get(), + SSL_is_server(ssl) ? "ssl_client" : "ssl_server")) { + return ssl_verify_invalid; + } + return impl->doVerifyCertChain(store_ctx.get(), ssl_extended_info, *cert.get(), + transport_socket_options, out_alert); + } else { + return impl->doVerifyCertChain(nullptr, ssl_extended_info, *cert.get(), + transport_socket_options, out_alert); + } } -int ContextImpl::doVerifyCertChain( +ssl_verify_result_t ContextImpl::doVerifyCertChain( X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, X509& leaf_cert, - const Network::TransportSocketOptions* transport_socket_options) { + const Network::TransportSocketOptions* transport_socket_options, uint8_t* out_alert) { if (verify_trusted_ca_) { + ASSERT(store_ctx != nullptr); int ret = X509_verify_cert(store_ctx); if (ssl_extended_info) { ssl_extended_info->setCertificateValidationStatus( @@ -548,7 +688,15 @@ int ContextImpl::doVerifyCertChain( if (ret <= 0) { stats_.fail_verify_error_.inc(); - return allow_untrusted_certificate_ ? 1 : ret; + if (allow_untrusted_certificate_) { + ERR_clear_error(); + return ssl_verify_ok; + } else { + if (out_alert) { + *out_alert = SSL_alert_from_verify_result(store_ctx->error); + } + return ssl_verify_invalid; + } } } @@ -569,8 +717,11 @@ int ContextImpl::doVerifyCertChain( } } - return allow_untrusted_certificate_ ? 1 - : (validated != Envoy::Ssl::ClientValidationStatus::Failed); + if (allow_untrusted_certificate_ || validated != Envoy::Ssl::ClientValidationStatus::Failed) { + return ssl_verify_ok; + } else { + return ssl_verify_invalid; + } } Envoy::Ssl::ClientValidationStatus ContextImpl::verifyCertificate( @@ -643,8 +794,8 @@ void ContextImpl::logHandshake(SSL* ssl) const { incCounter(ssl_sigalgs_, sigalg, unknown_ssl_algorithm_); } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl)); - if (!cert.get()) { + const STACK_OF(CRYPTO_BUFFER) * chain(SSL_get0_peer_certificates(ssl)); + if (!chain || sk_CRYPTO_BUFFER_num(chain) == 0) { stats_.no_certificate_.inc(); } } @@ -725,6 +876,12 @@ bool ContextImpl::dnsNameMatch(const absl::string_view dns_name, const absl::str return false; } +CRYPTO_BUFFER* ContextImpl::localLeafCertificate(SSL* ssl) { + auto tls_context = reinterpret_cast( + SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), sslTlsContextIndex())); + return tls_context->cert_.get(); +} + bool ContextImpl::verifyCertificateHashList( X509* cert, const std::vector>& expected_hashes) { std::vector computed_hash(SHA256_DIGEST_LENGTH); @@ -773,8 +930,13 @@ SslStats ContextImpl::generateStats(Stats::Scope& store) { size_t ContextImpl::daysUntilFirstCertExpires() const { int daysUntilExpiration = Utility::getDaysUntilExpiration(ca_cert_.get(), time_source_); for (auto& ctx : tls_contexts_) { - daysUntilExpiration = std::min( - Utility::getDaysUntilExpiration(ctx.cert_chain_.get(), time_source_), daysUntilExpiration); + if (ctx.cert_ == nullptr) { + continue; + } + bssl::UniquePtr x509(X509_parse_from_buffer(ctx.cert_.get())); + RELEASE_ASSERT(x509 != nullptr, "TLS context must have a valid certificate"); + daysUntilExpiration = std::min(Utility::getDaysUntilExpiration(x509.get(), time_source_), + daysUntilExpiration); } if (daysUntilExpiration < 0) { // Ensure that the return value is unsigned return 0; @@ -792,11 +954,12 @@ Envoy::Ssl::CertificateDetailsPtr ContextImpl::getCaCertInformation() const { std::vector ContextImpl::getCertChainInformation() const { std::vector cert_details; for (const auto& ctx : tls_contexts_) { - if (ctx.cert_chain_ == nullptr) { + if (ctx.cert_ == nullptr) { continue; } - cert_details.emplace_back( - certificateDetails(ctx.cert_chain_.get(), ctx.getCertChainFileName())); + bssl::UniquePtr x509(X509_parse_from_buffer(ctx.cert_.get())); + RELEASE_ASSERT(x509 != nullptr, "TLS context must have a valid certificate"); + cert_details.emplace_back(certificateDetails(x509.get(), ctx.getCertChainFileName())); } return cert_details; } @@ -899,7 +1062,8 @@ bssl::UniquePtr ClientContextImpl::newSsl(const Network::TransportSocketOpt if (options && !options->verifySubjectAltNameListOverride().empty()) { SSL_set_app_data(ssl_con.get(), options); - SSL_set_verify(ssl_con.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + SSL_set_custom_verify(ssl_con.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + ContextImpl::verifyCallback); } // We determine what ALPN using the following precedence: @@ -1066,9 +1230,10 @@ ServerContextImpl::generateHashForSessionContextId(const std::vector cert(X509_parse_from_buffer(ctx.cert_.get())); RELEASE_ASSERT(cert != nullptr, "TLS context should have an active certificate"); - X509_NAME* cert_subject = X509_get_subject_name(cert); + X509_NAME* cert_subject = X509_get_subject_name(cert.get()); RELEASE_ASSERT(cert_subject != nullptr, "TLS certificate should have a subject"); const int cn_index = X509_NAME_get_index_by_NID(cert_subject, NID_commonName, -1); @@ -1087,7 +1252,7 @@ ServerContextImpl::generateHashForSessionContextId(const std::vector san_names(static_cast( - X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + X509_get_ext_d2i(cert.get(), NID_subject_alt_name, nullptr, nullptr))); if (san_names != nullptr) { for (const GENERAL_NAME* san : san_names.get()) { @@ -1119,7 +1284,8 @@ ServerContextImpl::generateHashForSessionContextId(const std::vector 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** a, const X509_NAME** b) -> int { return X509_NAME_cmp(*a, *b); })); + bssl::UniquePtr list(sk_CRYPTO_BUFFER_new_null()); RELEASE_ASSERT(list != nullptr, ""); for (;;) { - bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); - if (cert == nullptr) { + uint8_t* data = nullptr; + long len; + if (!PEM_bytes_read_bio(&data, &len, nullptr, PEM_STRING_X509, bio.get(), nullptr, 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())) { + bssl::UniquePtr der(data); + bssl::UniquePtr cert(CRYPTO_BUFFER_new(data, len, nullptr)); + auto name = extractSubjectNameFromDERCert(cert.get()); + if (!name) { throw EnvoyException( absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); } + pushBufferIfUnique(list.get(), name.value()); } // Check for EOF. const uint32_t err = ERR_peek_last_error(); @@ -1367,11 +1526,12 @@ void ServerContextImpl::TlsContext::addClientValidationContext( throw EnvoyException( absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); } - SSL_CTX_set_client_CA_list(ssl_ctx_.get(), list.release()); + SSL_CTX_set0_client_CAs(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); + SSL_CTX_set_custom_verify(ssl_ctx_.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + ContextImpl::verifyCallback); } } @@ -1397,19 +1557,18 @@ bool ServerContextImpl::TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t bool ContextImpl::verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediates, std::string& error_details) { - bssl::UniquePtr ctx(X509_STORE_CTX_new()); // It doesn't matter which SSL context is used, because they share the same // cert validation config. - X509_STORE* store = SSL_CTX_get_cert_store(tls_contexts_[0].ssl_ctx_.get()); - if (!X509_STORE_CTX_init(ctx.get(), store, &leaf_cert, &intermediates)) { + bssl::ScopedX509_STORE_CTX store_ctx; + if (!X509_STORE_CTX_init(store_ctx.get(), tls_contexts_[0].x509_store_.get(), &leaf_cert, + &intermediates)) { error_details = "Failed to verify certificate chain: X509_STORE_CTX_init"; return false; } - - int res = doVerifyCertChain(ctx.get(), nullptr, leaf_cert, nullptr); - if (res <= 0) { - const int n = X509_STORE_CTX_get_error(ctx.get()); - const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); + auto ret = doVerifyCertChain(store_ctx.get(), nullptr, leaf_cert, nullptr, nullptr); + if (ret != ssl_verify_ok) { + const int n = X509_STORE_CTX_get_error(store_ctx.get()); + const int depth = X509_STORE_CTX_get_error_depth(store_ctx.get()); error_details = absl::StrCat("X509_verify_cert: certificate verification error at depth ", depth, ": ", X509_verify_cert_error_string(n)); return false; diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 5ea35a48228e..60d5dfb5809a 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -86,6 +86,13 @@ class ContextImpl : public virtual Envoy::Ssl::Context { */ static bool dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern); + /** + * Returns the leaf certificate used by the supplied connection. + * @param ssl a connected SSL connection + * @return the DER-encoded leaf certificate that was presented during the SSL handshake. + */ + static CRYPTO_BUFFER* localLeafCertificate(SSL* ssl); + SslStats& stats() { return stats_; } /** @@ -116,13 +123,13 @@ class ContextImpl : public virtual Envoy::Ssl::Context { // 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); + // A SSL_CTX_set_custom_verify_cb for custom cert validation. + static ssl_verify_result_t verifyCallback(SSL* ssl, uint8_t* out_alert); // 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); + ssl_verify_result_t doVerifyCertChain( + X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options, uint8_t* out_alert); Envoy::Ssl::ClientValidationStatus verifyCertificate(X509* cert, const std::vector& verify_san_list, @@ -166,7 +173,10 @@ class ContextImpl : public virtual Envoy::Ssl::Context { // safely substituted via SSL_set_SSL_CTX() during the // SSL_CTX_set_select_certificate_cb() callback following ClientHello. bssl::UniquePtr ssl_ctx_; - bssl::UniquePtr cert_chain_; + // x509_store_ configures X509_verify_cert(). + bssl::UniquePtr x509_store_; + // cert_ is the leaf certificate. (We don't retain the rest of the chain.) + bssl::UniquePtr cert_; std::string cert_chain_file_path_; bool is_ecdsa_{}; Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index ab2644ccc808..03b476fbef8e 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -315,8 +315,8 @@ SslSocketInfo::SslSocketInfo(bssl::UniquePtr ssl, ContextImplSharedPtr ctx) } bool SslSocketInfo::peerCertificatePresented() const { - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - return cert != nullptr; + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl()); + return chain != nullptr && sk_CRYPTO_BUFFER_num(chain) > 0; } bool SslSocketInfo::peerCertificateValidated() const { @@ -329,13 +329,11 @@ absl::Span SslSocketInfo::uriSanLocalCertificate() const { return cached_uri_san_local_certificate_; } - // The cert object is not owned. - X509* cert = SSL_get_certificate(ssl()); - if (!cert) { + if (!getX509Certificate()) { ASSERT(cached_uri_san_local_certificate_.empty()); return cached_uri_san_local_certificate_; } - cached_uri_san_local_certificate_ = Utility::getSubjectAltNames(*cert, GEN_URI); + cached_uri_san_local_certificate_ = Utility::getSubjectAltNames(*getX509Certificate(), GEN_URI); return cached_uri_san_local_certificate_; } @@ -344,12 +342,11 @@ absl::Span SslSocketInfo::dnsSansLocalCertificate() const { return cached_dns_san_local_certificate_; } - X509* cert = SSL_get_certificate(ssl()); - if (!cert) { + if (!getX509Certificate()) { ASSERT(cached_dns_san_local_certificate_.empty()); return cached_dns_san_local_certificate_; } - cached_dns_san_local_certificate_ = Utility::getSubjectAltNames(*cert, GEN_DNS); + cached_dns_san_local_certificate_ = Utility::getSubjectAltNames(*getX509Certificate(), GEN_DNS); return cached_dns_san_local_certificate_; } @@ -357,16 +354,15 @@ const std::string& SslSocketInfo::sha256PeerCertificateDigest() const { if (!cached_sha_256_peer_certificate_digest_.empty()) { return cached_sha_256_peer_certificate_digest_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl()); + if (!chain || sk_CRYPTO_BUFFER_num(chain) == 0) { ASSERT(cached_sha_256_peer_certificate_digest_.empty()); return cached_sha_256_peer_certificate_digest_; } + CRYPTO_BUFFER* leaf = sk_CRYPTO_BUFFER_value(chain, 0); std::vector computed_hash(SHA256_DIGEST_LENGTH); - unsigned int n; - X509_digest(cert.get(), EVP_sha256(), computed_hash.data(), &n); - RELEASE_ASSERT(n == computed_hash.size(), ""); + SHA256(CRYPTO_BUFFER_data(leaf), CRYPTO_BUFFER_len(leaf), computed_hash.data()); cached_sha_256_peer_certificate_digest_ = Hex::encode(computed_hash); return cached_sha_256_peer_certificate_digest_; } @@ -375,16 +371,15 @@ const std::string& SslSocketInfo::sha1PeerCertificateDigest() const { if (!cached_sha_1_peer_certificate_digest_.empty()) { return cached_sha_1_peer_certificate_digest_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { - ASSERT(cached_sha_1_peer_certificate_digest_.empty()); - return cached_sha_1_peer_certificate_digest_; + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl()); + if (!chain || sk_CRYPTO_BUFFER_num(chain) == 0) { + ASSERT(cached_sha_256_peer_certificate_digest_.empty()); + return cached_sha_256_peer_certificate_digest_; } + CRYPTO_BUFFER* leaf = sk_CRYPTO_BUFFER_value(chain, 0); std::vector computed_hash(SHA_DIGEST_LENGTH); - unsigned int n; - X509_digest(cert.get(), EVP_sha1(), computed_hash.data(), &n); - RELEASE_ASSERT(n == computed_hash.size(), ""); + SHA1(CRYPTO_BUFFER_data(leaf), CRYPTO_BUFFER_len(leaf), computed_hash.data()); cached_sha_1_peer_certificate_digest_ = Hex::encode(computed_hash); return cached_sha_1_peer_certificate_digest_; } @@ -393,15 +388,18 @@ const std::string& SslSocketInfo::urlEncodedPemEncodedPeerCertificate() const { if (!cached_url_encoded_pem_encoded_peer_certificate_.empty()) { return cached_url_encoded_pem_encoded_peer_certificate_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl()); + if (!chain || sk_CRYPTO_BUFFER_num(chain) == 0) { ASSERT(cached_url_encoded_pem_encoded_peer_certificate_.empty()); return cached_url_encoded_pem_encoded_peer_certificate_; } + CRYPTO_BUFFER* leaf = sk_CRYPTO_BUFFER_value(chain, 0); bssl::UniquePtr buf(BIO_new(BIO_s_mem())); RELEASE_ASSERT(buf != nullptr, ""); - RELEASE_ASSERT(PEM_write_bio_X509(buf.get(), cert.get()) == 1, ""); + RELEASE_ASSERT(PEM_write_bio(buf.get(), PEM_STRING_X509, /* header */ "", + CRYPTO_BUFFER_data(leaf), CRYPTO_BUFFER_len(leaf)) != 0, + ""); const uint8_t* output; size_t length; RELEASE_ASSERT(BIO_mem_contents(buf.get(), &output, &length) == 1, ""); @@ -416,18 +414,19 @@ const std::string& SslSocketInfo::urlEncodedPemEncodedPeerCertificateChain() con return cached_url_encoded_pem_encoded_peer_cert_chain_; } - STACK_OF(X509)* cert_chain = SSL_get_peer_full_cert_chain(ssl()); - if (cert_chain == nullptr) { + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl()); + if (!chain) { ASSERT(cached_url_encoded_pem_encoded_peer_cert_chain_.empty()); return cached_url_encoded_pem_encoded_peer_cert_chain_; } - for (uint64_t i = 0; i < sk_X509_num(cert_chain); i++) { - X509* cert = sk_X509_value(cert_chain, i); - + for (uint64_t i = 0; i < sk_CRYPTO_BUFFER_num(chain); i++) { + CRYPTO_BUFFER* cert = sk_CRYPTO_BUFFER_value(chain, i); bssl::UniquePtr buf(BIO_new(BIO_s_mem())); RELEASE_ASSERT(buf != nullptr, ""); - RELEASE_ASSERT(PEM_write_bio_X509(buf.get(), cert) == 1, ""); + RELEASE_ASSERT(PEM_write_bio(buf.get(), PEM_STRING_X509, /* header */ "", + CRYPTO_BUFFER_data(cert), CRYPTO_BUFFER_len(cert)) != 0, + ""); const uint8_t* output; size_t length; RELEASE_ASSERT(BIO_mem_contents(buf.get(), &output, &length) == 1, ""); @@ -446,12 +445,12 @@ absl::Span SslSocketInfo::uriSanPeerCertificate() const { return cached_uri_san_peer_certificate_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { ASSERT(cached_uri_san_peer_certificate_.empty()); return cached_uri_san_peer_certificate_; } - cached_uri_san_peer_certificate_ = Utility::getSubjectAltNames(*cert, GEN_URI); + cached_uri_san_peer_certificate_ = + Utility::getSubjectAltNames(*getX509PeerCertificate(), GEN_URI); return cached_uri_san_peer_certificate_; } @@ -460,12 +459,12 @@ absl::Span SslSocketInfo::dnsSansPeerCertificate() const { return cached_dns_san_peer_certificate_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { ASSERT(cached_dns_san_peer_certificate_.empty()); return cached_dns_san_peer_certificate_; } - cached_dns_san_peer_certificate_ = Utility::getSubjectAltNames(*cert, GEN_DNS); + cached_dns_san_peer_certificate_ = + Utility::getSubjectAltNames(*getX509PeerCertificate(), GEN_DNS); return cached_dns_san_peer_certificate_; } @@ -520,11 +519,33 @@ const std::string& SslSocketInfo::tlsVersion() const { } absl::optional SslSocketInfo::x509Extension(absl::string_view extension_name) const { - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { return absl::nullopt; } - return Utility::getX509ExtensionValue(*cert, extension_name); + return Utility::getX509ExtensionValue(*getX509PeerCertificate(), extension_name); +} + +X509* SslSocketInfo::getX509Certificate() const { + if (cached_certificate_) { + return cached_certificate_.get(); + } + CRYPTO_BUFFER* leaf = ContextImpl::localLeafCertificate(ssl_.get()); + if (leaf) { + cached_certificate_.reset(X509_parse_from_buffer(leaf)); + } + return cached_certificate_.get(); +} + +X509* SslSocketInfo::getX509PeerCertificate() const { + if (cached_peer_certificate_) { + return cached_peer_certificate_.get(); + } + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl_.get()); + if (chain && sk_CRYPTO_BUFFER_num(chain) > 0) { + CRYPTO_BUFFER* leaf = sk_CRYPTO_BUFFER_value(chain, 0); + cached_peer_certificate_.reset(X509_parse_from_buffer(leaf)); + } + return cached_peer_certificate_.get(); } absl::string_view SslSocket::failureReason() const { return failure_reason_; } @@ -533,12 +554,12 @@ const std::string& SslSocketInfo::serialNumberPeerCertificate() const { if (!cached_serial_number_peer_certificate_.empty()) { return cached_serial_number_peer_certificate_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { ASSERT(cached_serial_number_peer_certificate_.empty()); return cached_serial_number_peer_certificate_; } - cached_serial_number_peer_certificate_ = Utility::getSerialNumberFromCertificate(*cert.get()); + cached_serial_number_peer_certificate_ = + Utility::getSerialNumberFromCertificate(*getX509PeerCertificate()); return cached_serial_number_peer_certificate_; } @@ -546,12 +567,11 @@ const std::string& SslSocketInfo::issuerPeerCertificate() const { if (!cached_issuer_peer_certificate_.empty()) { return cached_issuer_peer_certificate_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { ASSERT(cached_issuer_peer_certificate_.empty()); return cached_issuer_peer_certificate_; } - cached_issuer_peer_certificate_ = Utility::getIssuerFromCertificate(*cert); + cached_issuer_peer_certificate_ = Utility::getIssuerFromCertificate(*getX509PeerCertificate()); return cached_issuer_peer_certificate_; } @@ -559,12 +579,11 @@ const std::string& SslSocketInfo::subjectPeerCertificate() const { if (!cached_subject_peer_certificate_.empty()) { return cached_subject_peer_certificate_; } - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { ASSERT(cached_subject_peer_certificate_.empty()); return cached_subject_peer_certificate_; } - cached_subject_peer_certificate_ = Utility::getSubjectFromCertificate(*cert); + cached_subject_peer_certificate_ = Utility::getSubjectFromCertificate(*getX509PeerCertificate()); return cached_subject_peer_certificate_; } @@ -572,29 +591,26 @@ const std::string& SslSocketInfo::subjectLocalCertificate() const { if (!cached_subject_local_certificate_.empty()) { return cached_subject_local_certificate_; } - X509* cert = SSL_get_certificate(ssl()); - if (!cert) { + if (!getX509Certificate()) { ASSERT(cached_subject_local_certificate_.empty()); return cached_subject_local_certificate_; } - cached_subject_local_certificate_ = Utility::getSubjectFromCertificate(*cert); + cached_subject_local_certificate_ = Utility::getSubjectFromCertificate(*getX509Certificate()); return cached_subject_local_certificate_; } absl::optional SslSocketInfo::validFromPeerCertificate() const { - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { return absl::nullopt; } - return Utility::getValidFrom(*cert); + return Utility::getValidFrom(*getX509PeerCertificate()); } absl::optional SslSocketInfo::expirationPeerCertificate() const { - bssl::UniquePtr cert(SSL_get_peer_certificate(ssl())); - if (!cert) { + if (!getX509PeerCertificate()) { return absl::nullopt; } - return Utility::getExpirationTime(*cert); + return Utility::getExpirationTime(*getX509PeerCertificate()); } const std::string& SslSocketInfo::sessionId() const { diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 27416ce7f635..29c72dd7b313 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -82,6 +82,14 @@ class SslSocketInfo : public Envoy::Ssl::ConnectionInfo { bssl::UniquePtr ssl_; private: + // getX509Certificate parses our leaf certificate, and caches and returns the result. + X509* getX509Certificate() const; + // getX509PeerCertificate parses the peer's leaf certificate (if any), and caches and returns the + // result. + X509* getX509PeerCertificate() const; + + mutable bssl::UniquePtr cached_certificate_; + mutable bssl::UniquePtr cached_peer_certificate_; mutable std::vector cached_uri_san_local_certificate_; mutable std::string cached_sha_256_peer_certificate_digest_; mutable std::string cached_sha_1_peer_certificate_digest_; diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 60cec6e1fe17..4bb3d4a970a2 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -75,7 +75,7 @@ INSTANTIATE_TEST_SUITE_P(CipherSuites, SslLibraryCipherSuiteSupport, // Tests for whether new cipher suites are added. When they are, they must be added to // knownCipherSuites() so that this test can detect if they are removed in the future. TEST_F(SslLibraryCipherSuiteSupport, CipherSuitesNotAdded) { - bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); + bssl::UniquePtr ctx(SSL_CTX_new(TLS_with_buffers_method())); EXPECT_NE(0, SSL_CTX_set_strict_cipher_list(ctx.get(), "ALL")); std::vector present_cipher_suites; @@ -89,7 +89,7 @@ TEST_F(SslLibraryCipherSuiteSupport, CipherSuitesNotAdded) { // suite is removed, it must be added to the release notes as an incompatible change, because it can // cause previously loadable configurations to no longer load if they reference the cipher suite. TEST_P(SslLibraryCipherSuiteSupport, CipherSuitesNotRemoved) { - bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); + bssl::UniquePtr ctx(SSL_CTX_new(TLS_with_buffers_method())); EXPECT_NE(0, SSL_CTX_set_strict_cipher_list(ctx.get(), GetParam().c_str())); } diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 76f3a16b56b1..0033ef66f240 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -1561,7 +1561,7 @@ TEST_P(SslSocketTest, FailedClientCertAllowExpiredBadHashVerification) { TestUtilOptionsV2 test_options(listener, client, false, GetParam()); testUtilV2(test_options.setExpectedServerStats("ssl.fail_verify_cert_hash") .setExpectedClientCertUri("spiffe://lyft.com/test-team") - .setExpectedTransportFailureReasonContains("SSLV3_ALERT_CERTIFICATE_EXPIRED")); + .setExpectedTransportFailureReasonContains("SSLV3_ALERT_CERTIFICATE_UNKNOWN")); } // Allow expired certificates, but use the wrong CA so it should fail still. @@ -2565,9 +2565,9 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { SSL_set_cert_cb( ssl_socket->ssl(), [](SSL* ssl, void*) -> int { - STACK_OF(X509_NAME)* list = SSL_get_client_CA_list(ssl); + const STACK_OF(CRYPTO_BUFFER)* list = SSL_get0_server_requested_CAs(ssl); EXPECT_NE(nullptr, list); - EXPECT_EQ(2U, sk_X509_NAME_num(list)); + EXPECT_EQ(2U, sk_CRYPTO_BUFFER_num(list)); return 1; }, nullptr);