diff --git a/CODEOWNERS b/CODEOWNERS index a6ef0e4aa0dc..79783fc3a039 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,6 +26,8 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/http/header_to_metadata @rgs1 @zuercher # alts transport socket extension /*/extensions/transport_sockets/alts @htuch @yangminzhu +# tls transport socket extension +/*/extensions/transport_sockets/tls @PiotrSikora @lizan # sni_cluster extension /*/extensions/filters/network/sni_cluster @rshriram @lizan # tracers.datadog extension diff --git a/api/envoy/api/v2/auth/cert.proto b/api/envoy/api/v2/auth/cert.proto index 526caf292829..30db22c6d7a6 100644 --- a/api/envoy/api/v2/auth/cert.proto +++ b/api/envoy/api/v2/auth/cert.proto @@ -10,6 +10,8 @@ option go_package = "auth"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/core/config_source.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; @@ -102,6 +104,22 @@ message TlsParameters { repeated string ecdh_curves = 4; } +// BoringSSL private key method configuration. The private key methods are used for external +// (potentially asynchronous) signing and decryption operations. Some use cases for private key +// methods would be TPM support and TLS acceleration. +message PrivateKeyProvider { + // Private key method provider name. The name must match a + // supported private key method provider type. + string provider_name = 1 [(validate.rules).string.min_bytes = 1]; + + // Private key method provider specific configuration. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + message TlsCertificate { // The TLS certificate chain. core.DataSource certificate_chain = 1; @@ -109,6 +127,15 @@ message TlsCertificate { // The TLS private key. core.DataSource private_key = 2; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key + // ` field. This can't be + // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + // ` and + // :ref:`private_key_provider + // ` fields will result in an + // error. + PrivateKeyProvider private_key_provider = 6; + // The password to decrypt the TLS private key. If this field is not set, it is assumed that the // TLS private key is not password encrypted. core.DataSource password = 3; diff --git a/docs/root/extending/extending.rst b/docs/root/extending/extending.rst index 1551d0992428..51198fefacc4 100644 --- a/docs/root/extending/extending.rst +++ b/docs/root/extending/extending.rst @@ -19,6 +19,7 @@ types including: * :ref:`Stat sinks ` * :ref:`Tracers ` * Transport sockets +* BoringSSL private key methods As of this writing there is no high level extension developer documentation. The :repo:`existing extensions ` are a good way to learn what is possible. diff --git a/docs/root/intro/arch_overview/security/ssl.rst b/docs/root/intro/arch_overview/security/ssl.rst index e73d14dd3ef3..a44a8f531872 100644 --- a/docs/root/intro/arch_overview/security/ssl.rst +++ b/docs/root/intro/arch_overview/security/ssl.rst @@ -23,6 +23,10 @@ requirements (TLS1.2, SNI, etc.). Envoy supports the following TLS features: tickets (see `RFC 5077 `_). Resumption can be performed across hot restarts and between parallel Envoy instances (typically useful in a front proxy configuration). +* **BoringSSL private key methods**: TLS private key operations (signing and decrypting) can be + performed asynchronously from an extension. This allows extending Envoy to support various key + management schemes (such as TPM) and TLS acceleration. This mechanism uses + `BoringSSL private key method interface `_. Underlying implementation ------------------------- diff --git a/include/envoy/ssl/BUILD b/include/envoy/ssl/BUILD index 73373af9842c..8ea81a6e9090 100644 --- a/include/envoy/ssl/BUILD +++ b/include/envoy/ssl/BUILD @@ -48,6 +48,9 @@ envoy_cc_library( envoy_cc_library( name = "tls_certificate_config_interface", hdrs = ["tls_certificate_config.h"], + deps = [ + "//include/envoy/ssl/private_key:private_key_interface", + ], ) envoy_cc_library( diff --git a/include/envoy/ssl/context_manager.h b/include/envoy/ssl/context_manager.h index 7358b7745b45..bb0c104e5202 100644 --- a/include/envoy/ssl/context_manager.h +++ b/include/envoy/ssl/context_manager.h @@ -5,6 +5,7 @@ #include "envoy/common/time.h" #include "envoy/ssl/context.h" #include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/stats/scope.h" namespace Envoy { @@ -39,6 +40,12 @@ class ContextManager { * Iterate through all currently allocated contexts. */ virtual void iterateContexts(std::function callback) PURE; + + /** + * Access the private key operations manager, which is part of SSL + * context manager. + */ + virtual PrivateKeyMethodManager& privateKeyMethodManager() PURE; }; using ContextManagerPtr = std::unique_ptr; diff --git a/include/envoy/ssl/private_key/BUILD b/include/envoy/ssl/private_key/BUILD new file mode 100644 index 000000000000..4bb651d1f8d3 --- /dev/null +++ b/include/envoy/ssl/private_key/BUILD @@ -0,0 +1,35 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "private_key_interface", + hdrs = ["private_key.h"], + external_deps = ["ssl"], + deps = [ + ":private_key_callbacks_interface", + "//include/envoy/event:dispatcher_interface", + "@envoy_api//envoy/api/v2/auth:cert_cc", + ], +) + +envoy_cc_library( + name = "private_key_config_interface", + hdrs = ["private_key_config.h"], + deps = [ + ":private_key_interface", + "//include/envoy/registry", + ], +) + +envoy_cc_library( + name = "private_key_callbacks_interface", + hdrs = ["private_key_callbacks.h"], + external_deps = ["ssl"], +) diff --git a/include/envoy/ssl/private_key/private_key.h b/include/envoy/ssl/private_key/private_key.h new file mode 100644 index 000000000000..e972d608cd02 --- /dev/null +++ b/include/envoy/ssl/private_key/private_key.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/common/pure.h" +#include "envoy/event/dispatcher.h" +#include "envoy/ssl/private_key/private_key_callbacks.h" + +#include "openssl/ssl.h" + +namespace Envoy { +namespace Server { +namespace Configuration { +// Prevent a dependency loop with the forward declaration. +class TransportSocketFactoryContext; +} // namespace Configuration +} // namespace Server + +namespace Ssl { + +using BoringSslPrivateKeyMethodSharedPtr = std::shared_ptr; + +class PrivateKeyMethodProvider { +public: + virtual ~PrivateKeyMethodProvider() = default; + + /** + * Register an SSL connection to private key operations by the provider. + * @param ssl a SSL connection object. + * @param cb a callbacks object, whose "complete" method will be invoked + * when the asynchronous processing is complete. + * @param dispatcher supplies the owning thread's dispatcher. + */ + virtual void registerPrivateKeyMethod(SSL* ssl, PrivateKeyConnectionCallbacks& cb, + Event::Dispatcher& dispatcher) PURE; + + /** + * Unregister an SSL connection from private key operations by the provider. + * @param ssl a SSL connection object. + * @throw EnvoyException if registration fails. + */ + virtual void unregisterPrivateKeyMethod(SSL* ssl) PURE; + + /** + * Check whether the private key method satisfies FIPS requirements. + * @return true if FIPS key requirements are satisfied, false if not. + */ + virtual bool checkFips() PURE; + + /** + * Get the private key methods from the provider. + * @return the private key methods associated with this provider and + * configuration. + */ + virtual BoringSslPrivateKeyMethodSharedPtr getBoringSslPrivateKeyMethod() PURE; +}; + +using PrivateKeyMethodProviderSharedPtr = std::shared_ptr; + +/** + * A manager for finding correct user-provided functions for handling BoringSSL private key + * operations. + */ +class PrivateKeyMethodManager { +public: + virtual ~PrivateKeyMethodManager() = default; + + /** + * Finds and returns a private key operations provider for BoringSSL. + * + * @param config a protobuf message object containing a PrivateKeyProvider message. + * @param factory_context context that provides components for creating and + * initializing connections using asynchronous private key operations. + * @return PrivateKeyMethodProvider the private key operations provider, or nullptr if + * no provider can be used with the context configuration. + */ + virtual PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProvider( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Envoy::Server::Configuration::TransportSocketFactoryContext& factory_context) PURE; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/include/envoy/ssl/private_key/private_key_callbacks.h b/include/envoy/ssl/private_key/private_key_callbacks.h new file mode 100644 index 000000000000..1f370fda947b --- /dev/null +++ b/include/envoy/ssl/private_key/private_key_callbacks.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" + +namespace Envoy { +namespace Ssl { + +class PrivateKeyConnectionCallbacks { +public: + virtual ~PrivateKeyConnectionCallbacks() = default; + + /** + * Callback function which is called when the asynchronous private key + * operation has been completed (with either success or failure). The + * provider will communicate the success status when SSL_do_handshake() + * is called the next time. + */ + virtual void onPrivateKeyMethodComplete() PURE; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/include/envoy/ssl/private_key/private_key_config.h b/include/envoy/ssl/private_key/private_key_config.h new file mode 100644 index 000000000000..8a5da737cac4 --- /dev/null +++ b/include/envoy/ssl/private_key/private_key_config.h @@ -0,0 +1,22 @@ +#pragma once + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/registry/registry.h" +#include "envoy/ssl/private_key/private_key.h" + +namespace Envoy { +namespace Ssl { + +// Base class which the private key operation provider implementations can register. + +class PrivateKeyMethodProviderInstanceFactory { +public: + virtual ~PrivateKeyMethodProviderInstanceFactory() = default; + virtual PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProviderInstance( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) PURE; + virtual std::string name() const PURE; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/include/envoy/ssl/tls_certificate_config.h b/include/envoy/ssl/tls_certificate_config.h index f934e5654a7a..882d40fe133d 100644 --- a/include/envoy/ssl/tls_certificate_config.h +++ b/include/envoy/ssl/tls_certificate_config.h @@ -4,6 +4,7 @@ #include #include "envoy/common/pure.h" +#include "envoy/ssl/private_key/private_key.h" namespace Envoy { namespace Ssl { @@ -34,6 +35,11 @@ class TlsCertificateConfig { */ virtual const std::string& privateKeyPath() const PURE; + /** + * @return private key method provider. + */ + virtual Envoy::Ssl::PrivateKeyMethodProviderSharedPtr privateKeyMethod() const PURE; + /** * @return a string of password. */ diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD index 37dcef2ba8ea..ebbe62647302 100644 --- a/source/common/ssl/BUILD +++ b/source/common/ssl/BUILD @@ -13,7 +13,9 @@ envoy_cc_library( srcs = ["tls_certificate_config_impl.cc"], hdrs = ["tls_certificate_config_impl.h"], deps = [ + "//include/envoy/server:transport_socket_config_interface", "//include/envoy/ssl:tls_certificate_config_interface", + "//include/envoy/ssl/private_key:private_key_interface", "//source/common/common:empty_string", "//source/common/config:datasource_lib", "@envoy_api//envoy/api/v2/auth:cert_cc", diff --git a/source/common/ssl/tls_certificate_config_impl.cc b/source/common/ssl/tls_certificate_config_impl.cc index 25f993c38da6..5b28c3568e09 100644 --- a/source/common/ssl/tls_certificate_config_impl.cc +++ b/source/common/ssl/tls_certificate_config_impl.cc @@ -1,6 +1,7 @@ #include "common/ssl/tls_certificate_config_impl.h" #include "envoy/common/exception.h" +#include "envoy/server/transport_socket_config.h" #include "common/common/empty_string.h" #include "common/common/fmt.h" @@ -12,7 +13,8 @@ namespace Ssl { static const std::string INLINE_STRING = ""; TlsCertificateConfigImpl::TlsCertificateConfigImpl( - const envoy::api::v2::auth::TlsCertificate& config, Api::Api& api) + const envoy::api::v2::auth::TlsCertificate& config, + Server::Configuration::TransportSocketFactoryContext* factory_context, Api::Api& api) : certificate_chain_(Config::DataSource::read(config.certificate_chain(), true, api)), certificate_chain_path_( Config::DataSource::getPath(config.certificate_chain()) @@ -22,9 +24,18 @@ TlsCertificateConfigImpl::TlsCertificateConfigImpl( .value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)), password_(Config::DataSource::read(config.password(), true, api)), password_path_(Config::DataSource::getPath(config.password()) - .value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)) { - - if (certificate_chain_.empty() || private_key_.empty()) { + .value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)), + private_key_method_( + factory_context != nullptr && config.has_private_key_provider() + ? factory_context->sslContextManager() + .privateKeyMethodManager() + .createPrivateKeyMethodProvider(config.private_key_provider(), *factory_context) + : nullptr) { + if (config.has_private_key_provider() && config.has_private_key()) { + throw EnvoyException(fmt::format( + "Certificate configuration can't have both private_key and private_key_provider")); + } + if (certificate_chain_.empty() || (private_key_.empty() && private_key_method_ == nullptr)) { throw EnvoyException(fmt::format("Failed to load incomplete certificate from {}, {}", certificate_chain_path_, private_key_path_)); } diff --git a/source/common/ssl/tls_certificate_config_impl.h b/source/common/ssl/tls_certificate_config_impl.h index ed664521b187..1db9046e925a 100644 --- a/source/common/ssl/tls_certificate_config_impl.h +++ b/source/common/ssl/tls_certificate_config_impl.h @@ -4,6 +4,7 @@ #include "envoy/api/api.h" #include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/ssl/tls_certificate_config.h" namespace Envoy { @@ -11,7 +12,9 @@ namespace Ssl { class TlsCertificateConfigImpl : public TlsCertificateConfig { public: - TlsCertificateConfigImpl(const envoy::api::v2::auth::TlsCertificate& config, Api::Api& api); + TlsCertificateConfigImpl(const envoy::api::v2::auth::TlsCertificate& config, + Server::Configuration::TransportSocketFactoryContext* factory_context, + Api::Api& api); const std::string& certificateChain() const override { return certificate_chain_; } const std::string& certificateChainPath() const override { return certificate_chain_path_; } @@ -19,6 +22,9 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig { const std::string& privateKeyPath() const override { return private_key_path_; } const std::string& password() const override { return password_; } const std::string& passwordPath() const override { return password_path_; } + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr privateKeyMethod() const override { + return private_key_method_; + } private: const std::string certificate_chain_; @@ -27,6 +33,7 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig { const std::string private_key_path_; const std::string password_; const std::string password_path_; + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_{}; }; } // namespace Ssl diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index 2d9526cfcd7b..cb8bb9ec02ea 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -38,6 +38,8 @@ envoy_cc_library( ":utility_lib", "//include/envoy/network:connection_interface", "//include/envoy/network:transport_socket_interface", + "//include/envoy/ssl/private_key:private_key_callbacks_interface", + "//include/envoy/ssl/private_key:private_key_interface", "//include/envoy/stats:stats_macros", "//source/common/common:assert_lib", "//source/common/common:empty_string", @@ -90,6 +92,7 @@ envoy_cc_library( "//include/envoy/ssl:context_config_interface", "//include/envoy/ssl:context_interface", "//include/envoy/ssl:context_manager_interface", + "//include/envoy/ssl/private_key:private_key_interface", "//include/envoy/stats:stats_interface", "//include/envoy/stats:stats_macros", "//source/common/common:assert_lib", @@ -98,6 +101,7 @@ envoy_cc_library( "//source/common/common:utility_lib", "//source/common/network:address_lib", "//source/common/protobuf:utility_lib", + "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "@envoy_api//envoy/admin/v2alpha:certs_cc", ], ) diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 424e08ae2d59..5978e136c880 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -25,7 +25,8 @@ std::vector getTlsCertificateConf if (!config.tls_certificates().empty()) { std::vector providers; for (const auto& tls_certificate : config.tls_certificates()) { - if (!tls_certificate.has_certificate_chain() && !tls_certificate.has_private_key()) { + if (!tls_certificate.has_private_key_provider() && !tls_certificate.has_certificate_chain() && + !tls_certificate.has_private_key()) { continue; } providers.push_back( @@ -143,7 +144,7 @@ ContextConfigImpl::ContextConfigImpl( if (!tls_certificate_providers_.empty()) { for (auto& provider : tls_certificate_providers_) { if (provider->secret() != nullptr) { - tls_certificate_configs_.emplace_back(*provider->secret(), api_); + tls_certificate_configs_.emplace_back(*provider->secret(), &factory_context, api_); } } } @@ -174,7 +175,8 @@ void ContextConfigImpl::setSecretUpdateCallback(std::function callback) // This breaks multiple certificate support, but today SDS is only single cert. // TODO(htuch): Fix this when SDS goes multi-cert. tls_certificate_configs_.clear(); - tls_certificate_configs_.emplace_back(*tls_certificate_providers_[0]->secret(), api_); + tls_certificate_configs_.emplace_back(*tls_certificate_providers_[0]->secret(), nullptr, + api_); callback(); }); } diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 03d247c489cc..f4795c0db2b1 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -305,40 +305,62 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c #endif } - // Load private key. - bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), - tls_certificate.privateKey().size())); - RELEASE_ASSERT(bio != nullptr, ""); - bssl::UniquePtr pkey(PEM_read_bio_PrivateKey( - bio.get(), nullptr, nullptr, - !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())) { - throw EnvoyException( - fmt::format("Failed to load private key from {}", tls_certificate.privateKeyPath())); - } - + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider = + tls_certificate.privateKeyMethod(); + // We either have a private key or a BoringSSL private key method provider. + if (private_key_method_provider) { + ctx.private_key_method_provider_ = private_key_method_provider; + // The provider has a reference to the private key method for the context lifetime. + Ssl::BoringSslPrivateKeyMethodSharedPtr private_key_method = + private_key_method_provider->getBoringSslPrivateKeyMethod(); + if (private_key_method == nullptr) { + throw EnvoyException( + fmt::format("Failed to get BoringSSL private key method from provider")); + } #ifdef BORINGSSL_FIPS - // Verify that private keys are passing FIPS pairwise consistency tests. - switch (pkey_id) { - case EVP_PKEY_EC: { - const EC_KEY* ecdsa_private_key = EVP_PKEY_get0_EC_KEY(pkey.get()); - if (!EC_KEY_check_fips(ecdsa_private_key)) { - throw EnvoyException(fmt::format("Failed to load private key from {}, ECDSA key failed " - "pairwise consistency test required in FIPS mode", - tls_certificate.privateKeyPath())); + if (!ctx.private_key_method_provider_->checkFips()) { + throw EnvoyException( + fmt::format("Private key method doesn't support FIPS mode with current parameters")); } - } break; - case EVP_PKEY_RSA: { - RSA* rsa_private_key = EVP_PKEY_get0_RSA(pkey.get()); - if (!RSA_check_fips(rsa_private_key)) { - throw EnvoyException(fmt::format("Failed to load private key from {}, RSA key failed " - "pairwise consistency test required in FIPS mode", - tls_certificate.privateKeyPath())); +#endif + SSL_CTX_set_private_key_method(ctx.ssl_ctx_.get(), private_key_method.get()); + } else { + // Load private key. + bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), + tls_certificate.privateKey().size())); + RELEASE_ASSERT(bio != nullptr, ""); + bssl::UniquePtr pkey( + PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, + !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())) { + throw EnvoyException( + fmt::format("Failed to load private key from {}", tls_certificate.privateKeyPath())); + } + +#ifdef BORINGSSL_FIPS + // Verify that private keys are passing FIPS pairwise consistency tests. + switch (pkey_id) { + case EVP_PKEY_EC: { + const EC_KEY* ecdsa_private_key = EVP_PKEY_get0_EC_KEY(pkey.get()); + if (!EC_KEY_check_fips(ecdsa_private_key)) { + throw EnvoyException(fmt::format("Failed to load private key from {}, ECDSA key failed " + "pairwise consistency test required in FIPS mode", + tls_certificate.privateKeyPath())); + } + } break; + case EVP_PKEY_RSA: { + RSA* rsa_private_key = EVP_PKEY_get0_RSA(pkey.get()); + if (!RSA_check_fips(rsa_private_key)) { + throw EnvoyException(fmt::format("Failed to load private key from {}, RSA key failed " + "pairwise consistency test required in FIPS mode", + tls_certificate.privateKeyPath())); + } + } break; } - } break; - } #endif + } } // use the server's cipher list preferences @@ -486,6 +508,19 @@ void ContextImpl::logHandshake(SSL* ssl) const { } } +std::vector ContextImpl::getPrivateKeyMethodProviders() { + std::vector providers; + + for (auto& tls_context : tls_contexts_) { + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr provider = + tls_context.getPrivateKeyMethodProvider(); + if (provider) { + providers.push_back(provider); + } + } + return providers; +} + bool ContextImpl::verifySubjectAltName(X509* cert, const std::vector& subject_alt_names) { bssl::UniquePtr san_names( diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index c4cf67d6cfa8..ccb984a63efa 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -8,6 +8,7 @@ #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/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -79,6 +80,8 @@ class ContextImpl : public virtual Envoy::Ssl::Context { Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const override; std::vector getCertChainInformation() const override; + std::vector getPrivateKeyMethodProviders(); + protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source); @@ -135,11 +138,15 @@ class ContextImpl : public virtual Envoy::Ssl::Context { bssl::UniquePtr cert_chain_; std::string cert_chain_file_path_; bool is_ecdsa_{}; + Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; std::string getCertChainFileName() const { return cert_chain_file_path_; }; void addClientValidationContext(const Envoy::Ssl::CertificateValidationContextConfig& config, bool require_client_cert); bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { + return private_key_method_provider_; + } }; // This is always non-empty, with the first context used for all new SSL diff --git a/source/extensions/transport_sockets/tls/context_manager_impl.h b/source/extensions/transport_sockets/tls/context_manager_impl.h index 88ef9e67d546..d08e12e97410 100644 --- a/source/extensions/transport_sockets/tls/context_manager_impl.h +++ b/source/extensions/transport_sockets/tls/context_manager_impl.h @@ -5,8 +5,11 @@ #include "envoy/common/time.h" #include "envoy/ssl/context_manager.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/stats/scope.h" +#include "extensions/transport_sockets/tls/private_key/private_key_manager_impl.h" + namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -33,11 +36,15 @@ class ContextManagerImpl final : public Envoy::Ssl::ContextManager { const std::vector& server_names) override; size_t daysUntilFirstCertExpires() const override; void iterateContexts(std::function callback) override; + Ssl::PrivateKeyMethodManager& privateKeyMethodManager() override { + return private_key_method_manager_; + }; private: void removeEmptyContexts(); TimeSource& time_source_; std::list> contexts_; + PrivateKeyMethodManagerImpl private_key_method_manager_{}; }; } // namespace Tls diff --git a/source/extensions/transport_sockets/tls/private_key/BUILD b/source/extensions/transport_sockets/tls/private_key/BUILD new file mode 100644 index 000000000000..2c181249b5d8 --- /dev/null +++ b/source/extensions/transport_sockets/tls/private_key/BUILD @@ -0,0 +1,26 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "private_key_manager_lib", + srcs = [ + "private_key_manager_impl.cc", + ], + hdrs = [ + "private_key_manager_impl.h", + ], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/registry", + "//include/envoy/ssl/private_key:private_key_config_interface", + "//include/envoy/ssl/private_key:private_key_interface", + "@envoy_api//envoy/api/v2/auth:cert_cc", + ], +) diff --git a/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc new file mode 100644 index 000000000000..817b9d362616 --- /dev/null +++ b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc @@ -0,0 +1,30 @@ +#include "extensions/transport_sockets/tls/private_key/private_key_manager_impl.h" + +#include "envoy/registry/registry.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +Envoy::Ssl::PrivateKeyMethodProviderSharedPtr +PrivateKeyMethodManagerImpl::createPrivateKeyMethodProvider( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) { + + Ssl::PrivateKeyMethodProviderInstanceFactory* factory = + Registry::FactoryRegistry::getFactory( + config.provider_name()); + + // Create a new provider instance with the configuration. + if (factory) { + return factory->createPrivateKeyMethodProviderInstance(config, factory_context); + } + + return nullptr; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h new file mode 100644 index 000000000000..1ae42d1916ec --- /dev/null +++ b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h @@ -0,0 +1,23 @@ +#pragma once + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/private_key/private_key_config.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class PrivateKeyMethodManagerImpl : public virtual Ssl::PrivateKeyMethodManager { +public: + // Ssl::PrivateKeyMethodManager + Ssl::PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProvider( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) override; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index d6b63be7d091..c079bb989fdc 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -46,7 +46,7 @@ SslSocket::SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) : transport_socket_options_(transport_socket_options), ctx_(std::dynamic_pointer_cast(ctx)), - ssl_(ctx_->newSsl(transport_socket_options_.get())) { + ssl_(ctx_->newSsl(transport_socket_options_.get())), state_(SocketState::PreHandshake) { if (state == InitialState::Client) { SSL_set_connect_state(ssl_.get()); } else { @@ -59,6 +59,12 @@ void SslSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& c ASSERT(!callbacks_); callbacks_ = &callbacks; + // Associate this SSL connection with all the certificates (with their potentially different + // private key methods). + for (auto const& provider : ctx_->getPrivateKeyMethodProviders()) { + provider->registerPrivateKeyMethod(ssl_.get(), *this, callbacks_->connection().dispatcher()); + } + BIO* bio = BIO_new_socket(callbacks_->ioHandle().fd(), 0); SSL_set_bio(ssl_.get(), bio, bio); } @@ -88,9 +94,9 @@ SslSocket::ReadResult SslSocket::sslReadIntoSlice(Buffer::RawSlice& slice) { } Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { - if (!handshake_complete_) { + if (state_ != SocketState::HandshakeComplete && state_ != SocketState::ShutdownSent) { PostIoAction action = doHandshake(); - if (action == PostIoAction::Close || !handshake_complete_) { + if (action == PostIoAction::Close || state_ != SocketState::HandshakeComplete) { // end_stream is false because either a hard error occurred (action == Close) or // the handshake isn't complete, so a half-close cannot occur yet. return {action, 0, false}; @@ -149,12 +155,24 @@ Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { return {action, bytes_read, end_stream}; } +void SslSocket::onPrivateKeyMethodComplete() { + ASSERT(isThreadSafe()); + ASSERT(state_ == SocketState::HandshakeInProgress); + + // Resume handshake. + PostIoAction action = doHandshake(); + if (action == PostIoAction::Close) { + ENVOY_CONN_LOG(debug, "async handshake completion error", callbacks_->connection()); + callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + } +} + PostIoAction SslSocket::doHandshake() { - ASSERT(!handshake_complete_); + ASSERT(state_ != SocketState::HandshakeComplete && state_ != SocketState::ShutdownSent); int rc = SSL_do_handshake(ssl_.get()); if (rc == 1) { ENVOY_CONN_LOG(debug, "handshake complete", callbacks_->connection()); - handshake_complete_ = true; + state_ = SocketState::HandshakeComplete; ctx_->logHandshake(ssl_.get()); callbacks_->raiseEvent(Network::ConnectionEvent::Connected); @@ -164,12 +182,18 @@ PostIoAction SslSocket::doHandshake() { : PostIoAction::Close; } else { int err = SSL_get_error(ssl_.get(), rc); - ENVOY_CONN_LOG(debug, "handshake error: {}", callbacks_->connection(), err); switch (err) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: + ENVOY_CONN_LOG(debug, "handshake expecting {}", callbacks_->connection(), + err == SSL_ERROR_WANT_READ ? "read" : "write"); + return PostIoAction::KeepOpen; + case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION: + ENVOY_CONN_LOG(debug, "handshake continued asynchronously", callbacks_->connection()); + state_ = SocketState::HandshakeInProgress; return PostIoAction::KeepOpen; default: + ENVOY_CONN_LOG(debug, "handshake error: {}", callbacks_->connection(), err); drainErrorQueue(); return PostIoAction::Close; } @@ -204,10 +228,10 @@ void SslSocket::drainErrorQueue() { } Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_stream) { - ASSERT(!shutdown_sent_ || write_buffer.length() == 0); - if (!handshake_complete_) { + ASSERT(state_ != SocketState::ShutdownSent || write_buffer.length() == 0); + if (state_ != SocketState::HandshakeComplete && state_ != SocketState::ShutdownSent) { PostIoAction action = doHandshake(); - if (action == PostIoAction::Close || !handshake_complete_) { + if (action == PostIoAction::Close || state_ != SocketState::HandshakeComplete) { return {action, 0, false}; } } @@ -260,15 +284,16 @@ Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_st return {PostIoAction::KeepOpen, total_bytes_written, false}; } -void SslSocket::onConnected() { ASSERT(!handshake_complete_); } +void SslSocket::onConnected() { ASSERT(state_ == SocketState::PreHandshake); } void SslSocket::shutdownSsl() { - ASSERT(handshake_complete_); - if (!shutdown_sent_ && callbacks_->connection().state() != Network::Connection::State::Closed) { + ASSERT(state_ != SocketState::PreHandshake); + if (state_ != SocketState::ShutdownSent && + callbacks_->connection().state() != Network::Connection::State::Closed) { int rc = SSL_shutdown(ssl_.get()); ENVOY_CONN_LOG(debug, "SSL shutdown: rc={}", callbacks_->connection(), rc); drainErrorQueue(); - shutdown_sent_ = true; + state_ = SocketState::ShutdownSent; } } @@ -381,10 +406,15 @@ std::vector SslSocket::dnsSansPeerCertificate() const { } void SslSocket::closeSocket(Network::ConnectionEvent) { + // Unregister the SSL connection object from private key method providers. + for (auto const& provider : ctx_->getPrivateKeyMethodProviders()) { + provider->unregisterPrivateKeyMethod(ssl_.get()); + } + // Attempt to send a shutdown before closing the socket. It's possible this won't go out if // there is no room on the socket. We can extend the state machine to handle this at some point // if needed. - if (handshake_complete_) { + if (state_ == SocketState::HandshakeInProgress || state_ == SocketState::HandshakeComplete) { shutdownSsl(); } } diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 549ffbf83696..8e0eb6601058 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -6,6 +6,7 @@ #include "envoy/network/connection.h" #include "envoy/network/transport_socket.h" #include "envoy/secret/secret_callbacks.h" +#include "envoy/ssl/private_key/private_key_callbacks.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -38,9 +39,11 @@ struct SslSocketFactoryStats { }; enum class InitialState { Client, Server }; +enum class SocketState { PreHandshake, HandshakeInProgress, HandshakeComplete, ShutdownSent }; class SslSocket : public Network::TransportSocket, public Envoy::Ssl::ConnectionInfo, + public Envoy::Ssl::PrivateKeyConnectionCallbacks, protected Logger::Loggable { public: SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, @@ -70,13 +73,16 @@ class SslSocket : public Network::TransportSocket, void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override; std::string protocol() const override; absl::string_view failureReason() const override; - bool canFlushClose() override { return handshake_complete_; } + bool canFlushClose() override { return state_ == SocketState::HandshakeComplete; } void closeSocket(Network::ConnectionEvent close_type) override; Network::IoResult doRead(Buffer::Instance& read_buffer) override; Network::IoResult doWrite(Buffer::Instance& write_buffer, bool end_stream) override; void onConnected() override; const Ssl::ConnectionInfo* ssl() const override { return this; } + // Ssl::PrivateKeyConnectionCallbacks + void onPrivateKeyMethodComplete() override; + SSL* rawSslForTest() const { return ssl_.get(); } private: @@ -89,18 +95,20 @@ class SslSocket : public Network::TransportSocket, Network::PostIoAction doHandshake(); void drainErrorQueue(); void shutdownSsl(); + bool isThreadSafe() const { + return callbacks_ != nullptr && callbacks_->connection().dispatcher().isThreadSafe(); + } const Network::TransportSocketOptionsSharedPtr transport_socket_options_; Network::TransportSocketCallbacks* callbacks_{}; ContextImplSharedPtr ctx_; bssl::UniquePtr ssl_; - bool handshake_complete_{}; - bool shutdown_sent_{}; uint64_t bytes_to_retry_{}; std::string failure_reason_; mutable std::string cached_sha_256_peer_certificate_digest_; mutable std::string cached_url_encoded_pem_encoded_peer_certificate_; mutable std::string cached_url_encoded_pem_encoded_peer_cert_chain_; + SocketState state_; }; class ClientSslSocketFactory : public Network::TransportSocketFactory, diff --git a/source/server/ssl_context_manager.cc b/source/server/ssl_context_manager.cc index 3e422643ed43..4573cdf6de2f 100644 --- a/source/server/ssl_context_manager.cc +++ b/source/server/ssl_context_manager.cc @@ -29,6 +29,8 @@ class SslContextManagerNoTlsStub final : public Envoy::Ssl::ContextManager { void iterateContexts(std::function /* callback */) override{}; + Ssl::PrivateKeyMethodManager& privateKeyMethodManager() override { throwException(); } + private: [[noreturn]] void throwException() { throw EnvoyException("SSL is not supported in this configuration"); diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 780211864399..ea65ba4f5f10 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -87,7 +87,7 @@ TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); subscription_factory_.callbacks_->onConfigUpdate(secret_resources, ""); - Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -182,7 +182,7 @@ TEST_F(SdsApiTest, DeltaUpdateSuccess) { initialize(); subscription_factory_.callbacks_->onConfigUpdate(secret_resources, {}, ""); - Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc index b3a6105fb11f..69e3051ce880 100644 --- a/test/common/secret/secret_manager_impl_test.cc +++ b/test/common/secret/secret_manager_impl_test.cc @@ -66,7 +66,7 @@ name: "abc.com" ASSERT_NE(secret_manager->findStaticTlsCertificateProvider("abc.com"), nullptr); Ssl::TlsCertificateConfigImpl tls_config( - *secret_manager->findStaticTlsCertificateProvider("abc.com")->secret(), *api_); + *secret_manager->findStaticTlsCertificateProvider("abc.com")->secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -206,7 +206,7 @@ name: "abc.com" init_target_handle->initialize(init_watcher); secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(secret_resources, ""); - Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -261,7 +261,7 @@ name: "abc.com" init_target_handle->initialize(init_watcher); secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(secret_resources, "keycert-v1"); - Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), nullptr, *api_); EXPECT_EQ("DUMMY_INLINE_BYTES_FOR_CERT_CHAIN", tls_config.certificateChain()); EXPECT_EQ("DUMMY_INLINE_BYTES_FOR_PRIVATE_KEY", tls_config.privateKey()); EXPECT_EQ("DUMMY_PASSWORD", tls_config.password()); diff --git a/test/extensions/transport_sockets/tls/BUILD b/test/extensions/transport_sockets/tls/BUILD index 32ec88bb3d58..94f93e62a6dd 100644 --- a/test/extensions/transport_sockets/tls/BUILD +++ b/test/extensions/transport_sockets/tls/BUILD @@ -25,6 +25,7 @@ envoy_cc_test( external_deps = ["ssl"], shard_count = 4, deps = [ + ":test_private_key_method_provider_test_lib", "//include/envoy/network:transport_socket_interface", "//source/common/buffer:buffer_lib", "//source/common/common:empty_string", @@ -41,14 +42,17 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:context_lib", "//source/extensions/transport_sockets/tls:ssl_socket_lib", "//source/extensions/transport_sockets/tls:utility_lib", + "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "//test/extensions/transport_sockets/tls/test_data:cert_infos", "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], @@ -75,6 +79,7 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", "//test/mocks/server:server_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", ], @@ -109,3 +114,23 @@ envoy_cc_test_library( "//test/test_common:environment_lib", ], ) + +envoy_cc_test_library( + name = "test_private_key_method_provider_test_lib", + srcs = [ + "test_private_key_method_provider.cc", + ], + hdrs = [ + "test_private_key_method_provider.h", + ], + external_deps = ["ssl"], + deps = [ + "//include/envoy/api:api_interface", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/server:transport_socket_config_interface", + "//include/envoy/ssl/private_key:private_key_config_interface", + "//include/envoy/ssl/private_key:private_key_interface", + "//source/common/config:utility_lib", + "//source/common/protobuf:utility_lib", + ], +) diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index ef82b26fff43..4eb0f4261f51 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -17,6 +17,7 @@ #include "test/extensions/transport_sockets/tls/test_data/san_dns3_cert_info.h" #include "test/mocks/secret/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" @@ -1185,6 +1186,124 @@ TEST_F(ServerContextConfigImplTest, InvalidIgnoreCertsNoCA) { EXPECT_NO_THROW(ServerContextConfigImpl server_context_config(tls_context, factory_context_)); } +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureNoProvider) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock context_manager; + NiceMock private_key_method_manager; + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + EXPECT_THROW_WITH_REGEX( + ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException, + "Failed to load incomplete certificate from "); +} + +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureNoMethod) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + tls_context.mutable_common_tls_context()->add_tls_certificates(); + Stats::IsolatedStoreImpl store; + NiceMock context_manager; + NiceMock private_key_method_manager; + auto private_key_method_provider_ptr = + std::make_shared>(); + Event::SimulatedTimeSystem time_system; + ContextManagerImpl manager(time_system); + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + EXPECT_CALL(private_key_method_manager, createPrivateKeyMethodProvider(_, _)) + .WillOnce(Return(private_key_method_provider_ptr)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + ServerContextConfigImpl server_context_config(tls_context, factory_context_); + EXPECT_THROW_WITH_MESSAGE( + Envoy::Ssl::ServerContextSharedPtr server_ctx( + manager.createSslServerContext(store, server_context_config, std::vector{})), + EnvoyException, "Failed to get BoringSSL private key method from provider"); +} + +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadSuccess) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock context_manager; + NiceMock private_key_method_manager; + auto private_key_method_provider_ptr = + std::make_shared>(); + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + EXPECT_CALL(private_key_method_manager, createPrivateKeyMethodProvider(_, _)) + .WillOnce(Return(private_key_method_provider_ptr)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + ServerContextConfigImpl server_context_config(tls_context, factory_context_); +} + +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureBothKeyAndMethod) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock context_manager; + NiceMock private_key_method_manager; + auto private_key_method_provider_ptr = + std::make_shared>(); + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + EXPECT_CALL(private_key_method_manager, createPrivateKeyMethodProvider(_, _)) + .WillOnce(Return(private_key_method_provider_ptr)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_key.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + EXPECT_THROW_WITH_MESSAGE( + ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException, + "Certificate configuration can't have both private_key and private_key_provider"); +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 07daedca6ca0..8b6dbb4b0263 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -16,6 +16,7 @@ #include "extensions/filters/listener/tls_inspector/tls_inspector.h" #include "extensions/transport_sockets/tls/context_config_impl.h" #include "extensions/transport_sockets/tls/context_impl.h" +#include "extensions/transport_sockets/tls/private_key/private_key_manager_impl.h" #include "extensions/transport_sockets/tls/ssl_socket.h" #include "test/extensions/transport_sockets/tls/ssl_certs_test.h" @@ -25,13 +26,16 @@ #include "test/extensions/transport_sockets/tls/test_data/san_dns_cert_info.h" #include "test/extensions/transport_sockets/tls/test_data/san_uri_cert_info.h" #include "test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert_info.h" +#include "test/extensions/transport_sockets/tls/test_private_key_method_provider.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/secret/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "absl/strings/str_replace.h" @@ -95,7 +99,9 @@ class TestUtilOptions : public TestUtilOptionsBase { TestUtilOptions(const std::string& client_ctx_yaml, const std::string& server_ctx_yaml, bool expect_success, Network::Address::IpVersion version) : TestUtilOptionsBase(expect_success, version), client_ctx_yaml_(client_ctx_yaml), - server_ctx_yaml_(server_ctx_yaml), expect_no_cert_(false), expect_no_cert_chain_(false) { + server_ctx_yaml_(server_ctx_yaml), expect_no_cert_(false), expect_no_cert_chain_(false), + expect_private_key_method_(false), + expected_server_close_event_(Network::ConnectionEvent::RemoteClose) { if (expect_success) { setExpectedServerStats("ssl.handshake"); } else { @@ -204,12 +210,28 @@ class TestUtilOptions : public TestUtilOptionsBase { return expected_expiration_peer_cert_; } + TestUtilOptions& setPrivateKeyMethodExpected(bool expected_method) { + expect_private_key_method_ = expected_method; + return *this; + } + + bool expectedPrivateKeyMethod() const { return expect_private_key_method_; } + + TestUtilOptions& setExpectedServerCloseEvent(Network::ConnectionEvent expected_event) { + expected_server_close_event_ = expected_event; + return *this; + } + + Network::ConnectionEvent expectedServerCloseEvent() const { return expected_server_close_event_; } + private: const std::string client_ctx_yaml_; const std::string server_ctx_yaml_; bool expect_no_cert_; bool expect_no_cert_chain_; + bool expect_private_key_method_; + Network::ConnectionEvent expected_server_close_event_; std::string expected_digest_; std::vector expected_local_uri_; std::string expected_serial_number_; @@ -231,6 +253,21 @@ void testUtil(const TestUtilOptions& options) { server_factory_context; ON_CALL(server_factory_context, api()).WillByDefault(ReturnRef(*server_api)); + // For private key method testing. + NiceMock context_manager; + Extensions::PrivateKeyMethodProvider::TestPrivateKeyMethodFactory test_factory; + Registry::InjectFactory + test_private_key_method_factory(test_factory); + PrivateKeyMethodManagerImpl private_key_method_manager; + if (options.expectedPrivateKeyMethod()) { + EXPECT_CALL(server_factory_context, sslContextManager()) + .WillOnce(ReturnRef(context_manager)) + .WillRepeatedly(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)) + .WillRepeatedly(ReturnRef(private_key_method_manager)); + } + envoy::api::v2::auth::DownstreamTlsContext server_tls_context; TestUtility::loadFromYaml(TestEnvironment::substitute(options.serverCtxYaml()), server_tls_context); @@ -376,7 +413,7 @@ void testUtil(const TestUtilOptions& options) { } else { EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { close_second_time(); })); - EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) + EXPECT_CALL(server_connection_callbacks, onEvent(options.expectedServerCloseEvent())) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { close_second_time(); })); } @@ -4145,6 +4182,525 @@ TEST_P(SslReadBufferLimitTest, SmallReadsIntoSameSlice) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +// Test asynchronous signing (ECDHE) using a private key provider. +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncSignSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test asynchronous decryption (RSA). +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncDecryptSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + sync_mode: false + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test synchronous signing (ECDHE). +TEST_P(SslSocketTest, RsaPrivateKeyProviderSyncSignSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test synchronous decryption (RSA). +TEST_P(SslSocketTest, RsaPrivateKeyProviderSyncDecryptSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + sync_mode: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test asynchronous signing (ECDHE) failure (invalid signature). +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncSignFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + crypto_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test synchronous signing (ECDHE) failure (invalid signature). +TEST_P(SslSocketTest, RsaPrivateKeyProviderSyncSignFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: true + crypto_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test the sign operation return with an error. +TEST_P(SslSocketTest, RsaPrivateKeyProviderSignFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test the decrypt operation return with an error. +TEST_P(SslSocketTest, RsaPrivateKeyProviderDecryptFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test the sign operation return with an error in complete. +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncSignCompleteFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + async_method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true) + .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose) + .setExpectedServerStats("ssl.connection_error")); +} + +// Test the decrypt operation return with an error in complete. +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncDecryptCompleteFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + async_method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true) + .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose) + .setExpectedServerStats("ssl.connection_error")); +} + +// Test having one cert with private key method and another with just +// private key. +TEST_P(SslSocketTest, RsaPrivateKeyProviderMultiCertSuccess) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" +)EOF"; + + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); + testUtil(test_options.setPrivateKeyMethodExpected(true)); +} + +// Test having two certs with private key methods. This will +// synchronously fail because the second certificate is a ECDSA one and +// the RSA method can't handle it. +TEST_P(SslSocketTest, RsaPrivateKeyProviderMultiCertFail) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + sync_mode: false + mode: rsa +)EOF"; + + TestUtilOptions failing_test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); + EXPECT_THROW_WITH_MESSAGE(testUtil(failing_test_options.setPrivateKeyMethodExpected(true)), + EnvoyException, "Private key is not RSA.") +} + +// Test ECDSA private key method provider mode. +TEST_P(SslSocketTest, EcdsaPrivateKeyProviderSuccess) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + mode: ecdsa +)EOF"; + + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); + testUtil(test_options.setPrivateKeyMethodExpected(true)); +} + +// Test having two certs with different private key method modes. It's expected that the ECDSA +// provider mode is being used. RSA provider mode is set to fail with "async_method_error", but +// that's not happening. +TEST_P(SslSocketTest, RsaAndEcdsaPrivateKeyProviderMultiCertSuccess) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + async_method_error: true + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + mode: ecdsa +)EOF"; + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); + testUtil(test_options.setPrivateKeyMethodExpected(true)); +} + +// Test having two certs with different private key method modes. ECDSA provider is set to fail. +TEST_P(SslSocketTest, RsaAndEcdsaPrivateKeyProviderMultiCertFail) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + async_method_error: true + mode: ecdsa +)EOF"; + TestUtilOptions failing_test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true) + .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose) + .setExpectedServerStats("ssl.connection_error")); +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/extensions/transport_sockets/tls/test_private_key_method_provider.cc b/test/extensions/transport_sockets/tls/test_private_key_method_provider.cc new file mode 100644 index 000000000000..cf78fbf3a304 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_private_key_method_provider.cc @@ -0,0 +1,377 @@ +#include "test/extensions/transport_sockets/tls/test_private_key_method_provider.h" + +#include + +#include "envoy/api/api.h" + +#include "openssl/ssl.h" + +namespace Envoy { +namespace Extensions { +namespace PrivateKeyMethodProvider { + +void TestPrivateKeyConnection::delayed_op() { + const std::chrono::milliseconds timeout_0ms{0}; + + timer_ = dispatcher_.createTimer([this]() -> void { + finished_ = true; + this->cb_.onPrivateKeyMethodComplete(); + }); + timer_->enableTimer(timeout_0ms); +} + +static int calculateDigest(const EVP_MD* md, const uint8_t* in, size_t in_len, unsigned char* hash, + unsigned int* hash_len) { + bssl::ScopedEVP_MD_CTX ctx; + + // Calculate the message digest for signing. + if (!EVP_DigestInit_ex(ctx.get(), md, nullptr) || !EVP_DigestUpdate(ctx.get(), in, in_len) || + !EVP_DigestFinal_ex(ctx.get(), hash, hash_len)) { + return 0; + } + return 1; +} + +static ssl_private_key_result_t ecdsaPrivateKeySign(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, uint16_t signature_algorithm, + const uint8_t* in, size_t in_len) { + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + TestPrivateKeyConnection* ops = static_cast( + SSL_get_ex_data(ssl, TestPrivateKeyMethodProvider::ecdsaConnectionIndex())); + unsigned int out_len_unsigned; + + if (!ops) { + return ssl_private_key_failure; + } + + if (ops->test_options_.method_error_) { + // Have an artificial test failure. + return ssl_private_key_failure; + } + + if (!ops->test_options_.sign_expected_) { + return ssl_private_key_failure; + } + + const EVP_MD* md = SSL_get_signature_algorithm_digest(signature_algorithm); + if (!md) { + return ssl_private_key_failure; + } + + if (!calculateDigest(md, in, in_len, hash, &hash_len)) { + return ssl_private_key_failure; + } + + bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(ops->getPrivateKey())); + if (!ec_key) { + return ssl_private_key_failure; + } + + // Borrow "out" because it has been already initialized to the max_out size. + if (!ECDSA_sign(0, hash, hash_len, out, &out_len_unsigned, ec_key.get())) { + return ssl_private_key_failure; + } + + if (ops->test_options_.sync_mode_) { + // Return immediately with the results. + if (out_len_unsigned > max_out) { + return ssl_private_key_failure; + } + *out_len = out_len_unsigned; + return ssl_private_key_success; + } + + ops->output_.assign(out, out + out_len_unsigned); + // Tell SSL socket that the operation is ready to be called again. + ops->delayed_op(); + + return ssl_private_key_retry; +} + +static ssl_private_key_result_t ecdsaPrivateKeyDecrypt(SSL*, uint8_t*, size_t*, size_t, + const uint8_t*, size_t) { + return ssl_private_key_failure; +} + +static ssl_private_key_result_t rsaPrivateKeySign(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, uint16_t signature_algorithm, + const uint8_t* in, size_t in_len) { + TestPrivateKeyConnection* ops = static_cast( + SSL_get_ex_data(ssl, TestPrivateKeyMethodProvider::rsaConnectionIndex())); + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + + if (!ops) { + return ssl_private_key_failure; + } + + if (ops->test_options_.method_error_) { + return ssl_private_key_failure; + } + + if (!ops->test_options_.sign_expected_) { + return ssl_private_key_failure; + } + + const EVP_MD* md = SSL_get_signature_algorithm_digest(signature_algorithm); + if (!md) { + return ssl_private_key_failure; + } + + if (!calculateDigest(md, in, in_len, hash, &hash_len)) { + return ssl_private_key_failure; + } + + RSA* rsa = EVP_PKEY_get0_RSA(ops->getPrivateKey()); + if (rsa == nullptr) { + return ssl_private_key_failure; + } + + if (ops->test_options_.crypto_error_) { + // Flip the bits in the first byte of the digest so that the handshake will fail. + hash[0] ^= hash[0]; + } + + // Perform RSA signing. + if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) { + RSA_sign_pss_mgf1(rsa, out_len, out, max_out, hash, hash_len, md, nullptr, -1); + } else { + unsigned int out_len_unsigned; + if (!RSA_sign(EVP_MD_type(md), hash, hash_len, out, &out_len_unsigned, rsa)) { + return ssl_private_key_failure; + } + if (out_len_unsigned > max_out) { + return ssl_private_key_failure; + } + *out_len = out_len_unsigned; + } + + if (ops->test_options_.sync_mode_) { + return ssl_private_key_success; + } + + ops->output_.assign(out, out + *out_len); + ops->delayed_op(); + + return ssl_private_key_retry; +} + +static ssl_private_key_result_t rsaPrivateKeyDecrypt(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, const uint8_t* in, + size_t in_len) { + TestPrivateKeyConnection* ops = static_cast( + SSL_get_ex_data(ssl, TestPrivateKeyMethodProvider::rsaConnectionIndex())); + + if (!ops) { + return ssl_private_key_failure; + } + + if (ops->test_options_.method_error_) { + return ssl_private_key_failure; + } + + if (!ops->test_options_.decrypt_expected_) { + return ssl_private_key_failure; + } + + RSA* rsa = EVP_PKEY_get0_RSA(ops->getPrivateKey()); + if (rsa == nullptr) { + return ssl_private_key_failure; + } + + if (!RSA_decrypt(rsa, out_len, out, max_out, in, in_len, RSA_NO_PADDING)) { + return ssl_private_key_failure; + } + + if (ops->test_options_.sync_mode_) { + return ssl_private_key_success; + } + + ops->output_.assign(out, out + *out_len); + ops->delayed_op(); + + return ssl_private_key_retry; +} + +static ssl_private_key_result_t privateKeyComplete(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, int id) { + TestPrivateKeyConnection* ops = static_cast(SSL_get_ex_data(ssl, id)); + + if (!ops->finished_) { + // The operation didn't finish yet, retry. + return ssl_private_key_retry; + } + + if (ops->test_options_.async_method_error_) { + return ssl_private_key_failure; + } + + if (ops->output_.size() > max_out) { + return ssl_private_key_failure; + } + + std::copy(ops->output_.begin(), ops->output_.end(), out); + *out_len = ops->output_.size(); + + return ssl_private_key_success; +} + +static ssl_private_key_result_t rsaPrivateKeyComplete(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out) { + return privateKeyComplete(ssl, out, out_len, max_out, + TestPrivateKeyMethodProvider::rsaConnectionIndex()); +} + +static ssl_private_key_result_t ecdsaPrivateKeyComplete(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out) { + return privateKeyComplete(ssl, out, out_len, max_out, + TestPrivateKeyMethodProvider::ecdsaConnectionIndex()); +} + +Ssl::BoringSslPrivateKeyMethodSharedPtr +TestPrivateKeyMethodProvider::getBoringSslPrivateKeyMethod() { + return method_; +} + +bool TestPrivateKeyMethodProvider::checkFips() { + if (mode_ == "rsa") { + RSA* rsa_private_key = EVP_PKEY_get0_RSA(pkey_.get()); + if (rsa_private_key == nullptr || !RSA_check_fips(rsa_private_key)) { + return false; + } + } else { // if (mode_ == "ecdsa") + const EC_KEY* ecdsa_private_key = EVP_PKEY_get0_EC_KEY(pkey_.get()); + if (ecdsa_private_key == nullptr || !EC_KEY_check_fips(ecdsa_private_key)) { + return false; + } + } + return true; +} + +TestPrivateKeyConnection::TestPrivateKeyConnection( + Ssl::PrivateKeyConnectionCallbacks& cb, Event::Dispatcher& dispatcher, + bssl::UniquePtr pkey, TestPrivateKeyConnectionTestOptions& test_options) + : test_options_(test_options), cb_(cb), dispatcher_(dispatcher), pkey_(std::move(pkey)) {} + +void TestPrivateKeyMethodProvider::registerPrivateKeyMethod(SSL* ssl, + Ssl::PrivateKeyConnectionCallbacks& cb, + Event::Dispatcher& dispatcher) { + TestPrivateKeyConnection* ops; + // In multi-cert case, when the same provider is used in different modes with the same SSL object, + // we need to keep both rsa and ecdsa connection objects in store because the test options for the + // two certificates may be different. We need to be able to deduct in the signing, decryption, and + // completion functions which options to use, so we associate the connection objects to the same + // SSL object using different user data indexes. + // + // Another way to do this would be to store both test options in one connection object. + int index = mode_ == "rsa" ? TestPrivateKeyMethodProvider::rsaConnectionIndex() + : TestPrivateKeyMethodProvider::ecdsaConnectionIndex(); + + // Check if there is another certificate of the same mode associated with the context. This would + // be an error. + ops = static_cast(SSL_get_ex_data(ssl, index)); + if (ops != nullptr) { + throw EnvoyException( + "Can't distinguish between two registered providers for the same SSL object."); + } + + ops = new TestPrivateKeyConnection(cb, dispatcher, bssl::UpRef(pkey_), test_options_); + SSL_set_ex_data(ssl, index, ops); +} + +void TestPrivateKeyMethodProvider::unregisterPrivateKeyMethod(SSL* ssl) { + int index = mode_ == "rsa" ? TestPrivateKeyMethodProvider::rsaConnectionIndex() + : TestPrivateKeyMethodProvider::ecdsaConnectionIndex(); + TestPrivateKeyConnection* ops = + static_cast(SSL_get_ex_data(ssl, index)); + SSL_set_ex_data(ssl, index, nullptr); + delete ops; +} + +static int createIndex() { + int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + RELEASE_ASSERT(index >= 0, "Failed to get SSL user data index."); + return index; +} + +int TestPrivateKeyMethodProvider::rsaConnectionIndex() { + CONSTRUCT_ON_FIRST_USE(int, createIndex()); +} + +int TestPrivateKeyMethodProvider::ecdsaConnectionIndex() { + CONSTRUCT_ON_FIRST_USE(int, createIndex()); +} + +TestPrivateKeyMethodProvider::TestPrivateKeyMethodProvider( + const ProtobufWkt::Struct& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) { + std::string private_key_path; + + for (auto& value_it : config.fields()) { + auto& value = value_it.second; + if (value_it.first == "private_key_file" && + value.kind_case() == ProtobufWkt::Value::kStringValue) { + private_key_path = value.string_value(); + } + if (value_it.first == "sync_mode" && value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.sync_mode_ = value.bool_value(); + } + if (value_it.first == "crypto_error" && value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.crypto_error_ = value.bool_value(); + } + if (value_it.first == "method_error" && value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.method_error_ = value.bool_value(); + } + if (value_it.first == "async_method_error" && + value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.async_method_error_ = value.bool_value(); + } + if (value_it.first == "expected_operation" && + value.kind_case() == ProtobufWkt::Value::kStringValue) { + if (value.string_value() == "decrypt") { + test_options_.decrypt_expected_ = true; + } else if (value.string_value() == "sign") { + test_options_.sign_expected_ = true; + } + } + if (value_it.first == "mode" && value.kind_case() == ProtobufWkt::Value::kStringValue) { + mode_ = value.string_value(); + } + } + + std::string private_key = factory_context.api().fileSystem().fileReadToEnd(private_key_path); + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); + bssl::UniquePtr pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); + if (pkey == nullptr) { + throw EnvoyException("Failed to read private key from disk."); + } + + method_ = std::make_shared(); + + // Have two modes, "rsa" and "ecdsa", for testing multi-cert use cases. + if (mode_ == "rsa") { + if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_RSA) { + throw EnvoyException("Private key is not RSA."); + } + method_->sign = rsaPrivateKeySign; + method_->decrypt = rsaPrivateKeyDecrypt; + method_->complete = rsaPrivateKeyComplete; + } else if (mode_ == "ecdsa") { + if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_EC) { + throw EnvoyException("Private key is not ECDSA."); + } + method_->sign = ecdsaPrivateKeySign; + method_->decrypt = ecdsaPrivateKeyDecrypt; + method_->complete = ecdsaPrivateKeyComplete; + } else { + throw EnvoyException("Unknown test provider mode, supported modes are \"rsa\" and \"ecdsa\"."); + } + + pkey_ = std::move(pkey); +} + +} // namespace PrivateKeyMethodProvider +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/test_private_key_method_provider.h b/test/extensions/transport_sockets/tls/test_private_key_method_provider.h new file mode 100644 index 000000000000..6aadf9301077 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_private_key_method_provider.h @@ -0,0 +1,96 @@ +#pragma once + +#include "envoy/event/dispatcher.h" +#include "envoy/server/transport_socket_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/private_key/private_key_config.h" + +#include "common/config/utility.h" +#include "common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace PrivateKeyMethodProvider { + +struct TestPrivateKeyConnectionTestOptions { + // Return private key method value directly without asynchronous operation. + bool sync_mode_{}; + + // The "decrypt" private key method is expected to he called. + bool decrypt_expected_{}; + + // The "sign" private key method is expected to he called. + bool sign_expected_{}; + + // Add a cryptographic error (invalid signature, incorrect decryption). + bool crypto_error_{}; + + // Return an error from the private key method. + bool method_error_{}; + + // Return an error from the private key method completion function. + bool async_method_error_{}; +}; + +// An example private key method provider for testing the decrypt() and sign() +// functionality. +class TestPrivateKeyConnection { +public: + TestPrivateKeyConnection(Ssl::PrivateKeyConnectionCallbacks& cb, Event::Dispatcher& dispatcher, + bssl::UniquePtr pkey, + TestPrivateKeyConnectionTestOptions& test_options); + EVP_PKEY* getPrivateKey() { return pkey_.get(); } + void delayed_op(); + // Store the output data temporarily. + std::vector output_; + // The complete callback can return other value than "retry" only after + // onPrivateKeyMethodComplete() function has been called. This is controlled by "finished" + // variable. + bool finished_{}; + TestPrivateKeyConnectionTestOptions& test_options_; + +private: + Ssl::PrivateKeyConnectionCallbacks& cb_; + Event::Dispatcher& dispatcher_; + bssl::UniquePtr pkey_; + // A zero-length timer controls the callback. + Event::TimerPtr timer_; +}; + +class TestPrivateKeyMethodProvider : public virtual Ssl::PrivateKeyMethodProvider { +public: + TestPrivateKeyMethodProvider( + const ProtobufWkt::Struct& config, + Server::Configuration::TransportSocketFactoryContext& factory_context); + // Ssl::PrivateKeyMethodProvider + void registerPrivateKeyMethod(SSL* ssl, Ssl::PrivateKeyConnectionCallbacks& cb, + Event::Dispatcher& dispatcher) override; + void unregisterPrivateKeyMethod(SSL* ssl) override; + bool checkFips() override; + Ssl::BoringSslPrivateKeyMethodSharedPtr getBoringSslPrivateKeyMethod() override; + + static int rsaConnectionIndex(); + static int ecdsaConnectionIndex(); + +private: + Ssl::BoringSslPrivateKeyMethodSharedPtr method_{}; + bssl::UniquePtr pkey_; + TestPrivateKeyConnectionTestOptions test_options_; + std::string mode_; +}; + +class TestPrivateKeyMethodFactory : public Ssl::PrivateKeyMethodProviderInstanceFactory { +public: + // Ssl::PrivateKeyMethodProviderInstanceFactory + Ssl::PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProviderInstance( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) override { + return std::make_shared(config.config(), factory_context); + } + + std::string name() const override { return std::string("test"); }; +}; + +} // namespace PrivateKeyMethodProvider +} // namespace Extensions +} // namespace Envoy diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index 4ff1eda3140c..6c8e54c3968a 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -116,6 +116,7 @@ class MockDispatcher : public Dispatcher { MOCK_METHOD1(setTrackedObject, const ScopeTrackedObject*(const ScopeTrackedObject* object)); MOCK_CONST_METHOD0(isThreadSafe, bool()); Buffer::WatermarkFactory& getWatermarkFactory() override { return buffer_factory_; } + MOCK_METHOD0(getCurrentThreadId, Thread::ThreadId()); GlobalTimeSystem time_system_; std::list to_delete_; diff --git a/test/mocks/ssl/mocks.cc b/test/mocks/ssl/mocks.cc index 72702de823ed..50ed3f3ae6c0 100644 --- a/test/mocks/ssl/mocks.cc +++ b/test/mocks/ssl/mocks.cc @@ -18,5 +18,11 @@ MockClientContextConfig::~MockClientContextConfig() = default; MockServerContextConfig::MockServerContextConfig() = default; MockServerContextConfig::~MockServerContextConfig() = default; +MockPrivateKeyMethodManager::MockPrivateKeyMethodManager() = default; +MockPrivateKeyMethodManager::~MockPrivateKeyMethodManager() = default; + +MockPrivateKeyMethodProvider::MockPrivateKeyMethodProvider() = default; +MockPrivateKeyMethodProvider::~MockPrivateKeyMethodProvider() = default; + } // namespace Ssl } // namespace Envoy diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 20621bd29ca4..957cbf05c87c 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -29,6 +29,7 @@ class MockContextManager : public ContextManager { const std::vector& server_names)); MOCK_CONST_METHOD0(daysUntilFirstCertExpires, size_t()); MOCK_METHOD1(iterateContexts, void(std::function callback)); + MOCK_METHOD0(privateKeyMethodManager, Ssl::PrivateKeyMethodManager&()); }; class MockConnectionInfo : public ConnectionInfo { @@ -108,5 +109,28 @@ class MockServerContextConfig : public ServerContextConfig { MOCK_CONST_METHOD0(sessionTicketKeys, const std::vector&()); }; +class MockPrivateKeyMethodManager : public PrivateKeyMethodManager { +public: + MockPrivateKeyMethodManager(); + ~MockPrivateKeyMethodManager() override; + + MOCK_METHOD2(createPrivateKeyMethodProvider, + PrivateKeyMethodProviderSharedPtr( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Envoy::Server::Configuration::TransportSocketFactoryContext& factory_context)); +}; + +class MockPrivateKeyMethodProvider : public PrivateKeyMethodProvider { +public: + MockPrivateKeyMethodProvider(); + ~MockPrivateKeyMethodProvider() override; + + MOCK_METHOD3(registerPrivateKeyMethod, + void(SSL* ssl, PrivateKeyConnectionCallbacks& cb, Event::Dispatcher& dispatcher)); + MOCK_METHOD1(unregisterPrivateKeyMethod, void(SSL* ssl)); + MOCK_METHOD0(checkFips, bool()); + MOCK_METHOD0(getBoringSslPrivateKeyMethod, BoringSslPrivateKeyMethodSharedPtr()); +}; + } // namespace Ssl } // namespace Envoy diff --git a/tools/check_format.py b/tools/check_format.py index 54eda668bc4f..4c3850a4796c 100755 --- a/tools/check_format.py +++ b/tools/check_format.py @@ -141,7 +141,6 @@ "extensions/common/tap", "extensions/transport_sockets/raw_buffer", "extensions/transport_sockets/tap", - "extensions/transport_sockets/tls", "extensions/tracers/zipkin", "extensions/tracers/dynamic_ot", "extensions/tracers/opencensus", diff --git a/tools/spelling_dictionary.txt b/tools/spelling_dictionary.txt index 50abfff89e48..777b37a130a9 100644 --- a/tools/spelling_dictionary.txt +++ b/tools/spelling_dictionary.txt @@ -198,6 +198,7 @@ POSTs PREBIND PRNG PROT +PSS QPS QUIC RAII @@ -268,6 +269,7 @@ TLSv TLV TMPDIR TODO +TPM TPROXY TSAN TSI