diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto index a7b9360d24..0fd4d1edc3 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.transport_sockets.tls.v3; import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/type/matcher/v3/string.proto"; import "google/protobuf/any.proto"; @@ -191,7 +192,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 13] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -337,4 +338,16 @@ message CertificateValidationContext { // Certificate trust chain verification mode. TrustChainVerification trust_chain_verification = 10 [(validate.rules).enum = {defined_only: true}]; + + // The configuration of an extension specific certificate validator. + // If specified, all validation is done by the specified validator, + // and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + // Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + // The following names are available here: + // + // .. _extension_envoy.tls.cert_validator.spiffe: + // + // **envoy.tls.cert_validator.spiffe**: `SPIFFE `_ certificate validator. + // Please refer to :ref:`envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig ` for more information. + config.core.v3.TypedExtensionConfig custom_validator_config = 12; } diff --git a/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto b/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto new file mode 100644 index 0000000000..b6fb921d65 --- /dev/null +++ b/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package envoy.extensions.transport_sockets.tls.v3; + +import "envoy/config/core/v3/base.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v3"; +option java_outer_classname = "TlsSpiffeValidatorConfigProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SPIFFE Certificate Validator] + +// Configuration specific to the SPIFFE certificate validator provided at +// :ref:`envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext.custom_validator_config`. +// +// Example: +// +// .. code-block:: yaml +// +// custom_validator_config: +// name: envoy.tls.cert_validator.spiffe +// typed_config: +// "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig +// trust_domains: +// - name: foo.com +// trust_bundle: +// filename: "foo.pem" +// - name: envoy.com +// trust_bundle: +// filename: "envoy.pem" +// +// In this example, a presented peer certificate whose SAN matches `spiffe//foo.com/**` is validated against +// the "foo.pem" x.509 certificate. All the trust bundles are isolated from each other, so no trust domain can mint +// a SVID belonging to another trust domain. That means, in this example, a SVID signed by `envoy.com`'s CA with `spiffe//foo.com/**` +// SAN would be rejected since Envoy selects the trust bundle according to the presented SAN before validate the certificate. +message SPIFFECertValidatorConfig { + message TrustDomain { + // Name of the trust domain, `example.com`, `foo.bar.gov` for example. + // Note that this must *not* have "spiffe://" prefix. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Specify a data source holding x.509 trust bundle used for validating incoming SVID(s) in this trust domain. + config.core.v3.DataSource trust_bundle = 2; + } + + // This field specifies trust domains used for validating incoming X.509-SVID(s). + repeated TrustDomain trust_domains = 1 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 3608f93ffe..e3f725c4b8 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.transport_sockets.tls.v4alpha; import "envoy/config/core/v4alpha/base.proto"; +import "envoy/config/core/v4alpha/extension.proto"; import "envoy/type/matcher/v4alpha/string.proto"; import "google/protobuf/any.proto"; @@ -193,7 +194,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 13] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext"; @@ -339,4 +340,16 @@ message CertificateValidationContext { // Certificate trust chain verification mode. TrustChainVerification trust_chain_verification = 10 [(validate.rules).enum = {defined_only: true}]; + + // The configuration of an extension specific certificate validator. + // If specified, all validation is done by the specified validator, + // and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + // Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + // The following names are available here: + // + // .. _extension_envoy.tls.cert_validator.spiffe: + // + // **envoy.tls.cert_validator.spiffe**: `SPIFFE `_ certificate validator. + // Please refer to :ref:`envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig ` for more information. + config.core.v4alpha.TypedExtensionConfig custom_validator_config = 12; } diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto new file mode 100644 index 0000000000..27770eece8 --- /dev/null +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package envoy.extensions.transport_sockets.tls.v4alpha; + +import "envoy/config/core/v4alpha/base.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v4alpha"; +option java_outer_classname = "TlsSpiffeValidatorConfigProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#protodoc-title: SPIFFE Certificate Validator] + +// Configuration specific to the SPIFFE certificate validator provided at +// :ref:`envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext.custom_validator_config`. +// +// Example: +// +// .. code-block:: yaml +// +// custom_validator_config: +// name: envoy.tls.cert_validator.spiffe +// typed_config: +// "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig +// trust_domains: +// - name: foo.com +// trust_bundle: +// filename: "foo.pem" +// - name: envoy.com +// trust_bundle: +// filename: "envoy.pem" +// +// In this example, a presented peer certificate whose SAN matches `spiffe//foo.com/**` is validated against +// the "foo.pem" x.509 certificate. All the trust bundles are isolated from each other, so no trust domain can mint +// a SVID belonging to another trust domain. That means, in this example, a SVID signed by `envoy.com`'s CA with `spiffe//foo.com/**` +// SAN would be rejected since Envoy selects the trust bundle according to the presented SAN before validate the certificate. +message SPIFFECertValidatorConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig"; + + message TrustDomain { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig.TrustDomain"; + + // Name of the trust domain, `example.com`, `foo.bar.gov` for example. + // Note that this must *not* have "spiffe://" prefix. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Specify a data source holding x.509 trust bundle used for validating incoming SVID(s) in this trust domain. + config.core.v4alpha.DataSource trust_bundle = 2; + } + + // This field specifies trust domains used for validating incoming X.509-SVID(s). + repeated TrustDomain trust_domains = 1 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f50abcf6e4..df0f79aa76 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -108,5 +108,7 @@ New Features * xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. +* tls peer certificate validation: added :ref:`SPIFFE validator ` for supporting isolated multiple trust bundles in a single listener or cluster. + Deprecated ---------- diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto index dee271f310..390dd4a362 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.transport_sockets.tls.v3; import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/type/matcher/v3/string.proto"; import "google/protobuf/any.proto"; @@ -190,7 +191,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 13] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -335,5 +336,17 @@ message CertificateValidationContext { TrustChainVerification trust_chain_verification = 10 [(validate.rules).enum = {defined_only: true}]; + // The configuration of an extension specific certificate validator. + // If specified, all validation is done by the specified validator, + // and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + // Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + // The following names are available here: + // + // .. _extension_envoy.tls.cert_validator.spiffe: + // + // **envoy.tls.cert_validator.spiffe**: `SPIFFE `_ certificate validator. + // Please refer to :ref:`envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig ` for more information. + config.core.v3.TypedExtensionConfig custom_validator_config = 12; + repeated string hidden_envoy_deprecated_verify_subject_alt_name = 4 [deprecated = true]; } diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto new file mode 100644 index 0000000000..b6fb921d65 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package envoy.extensions.transport_sockets.tls.v3; + +import "envoy/config/core/v3/base.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v3"; +option java_outer_classname = "TlsSpiffeValidatorConfigProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SPIFFE Certificate Validator] + +// Configuration specific to the SPIFFE certificate validator provided at +// :ref:`envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext.custom_validator_config`. +// +// Example: +// +// .. code-block:: yaml +// +// custom_validator_config: +// name: envoy.tls.cert_validator.spiffe +// typed_config: +// "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig +// trust_domains: +// - name: foo.com +// trust_bundle: +// filename: "foo.pem" +// - name: envoy.com +// trust_bundle: +// filename: "envoy.pem" +// +// In this example, a presented peer certificate whose SAN matches `spiffe//foo.com/**` is validated against +// the "foo.pem" x.509 certificate. All the trust bundles are isolated from each other, so no trust domain can mint +// a SVID belonging to another trust domain. That means, in this example, a SVID signed by `envoy.com`'s CA with `spiffe//foo.com/**` +// SAN would be rejected since Envoy selects the trust bundle according to the presented SAN before validate the certificate. +message SPIFFECertValidatorConfig { + message TrustDomain { + // Name of the trust domain, `example.com`, `foo.bar.gov` for example. + // Note that this must *not* have "spiffe://" prefix. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Specify a data source holding x.509 trust bundle used for validating incoming SVID(s) in this trust domain. + config.core.v3.DataSource trust_bundle = 2; + } + + // This field specifies trust domains used for validating incoming X.509-SVID(s). + repeated TrustDomain trust_domains = 1 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 3608f93ffe..e3f725c4b8 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.transport_sockets.tls.v4alpha; import "envoy/config/core/v4alpha/base.proto"; +import "envoy/config/core/v4alpha/extension.proto"; import "envoy/type/matcher/v4alpha/string.proto"; import "google/protobuf/any.proto"; @@ -193,7 +194,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 13] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext"; @@ -339,4 +340,16 @@ message CertificateValidationContext { // Certificate trust chain verification mode. TrustChainVerification trust_chain_verification = 10 [(validate.rules).enum = {defined_only: true}]; + + // The configuration of an extension specific certificate validator. + // If specified, all validation is done by the specified validator, + // and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + // Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + // The following names are available here: + // + // .. _extension_envoy.tls.cert_validator.spiffe: + // + // **envoy.tls.cert_validator.spiffe**: `SPIFFE `_ certificate validator. + // Please refer to :ref:`envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig ` for more information. + config.core.v4alpha.TypedExtensionConfig custom_validator_config = 12; } diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto new file mode 100644 index 0000000000..27770eece8 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package envoy.extensions.transport_sockets.tls.v4alpha; + +import "envoy/config/core/v4alpha/base.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.tls.v4alpha"; +option java_outer_classname = "TlsSpiffeValidatorConfigProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#protodoc-title: SPIFFE Certificate Validator] + +// Configuration specific to the SPIFFE certificate validator provided at +// :ref:`envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext.custom_validator_config`. +// +// Example: +// +// .. code-block:: yaml +// +// custom_validator_config: +// name: envoy.tls.cert_validator.spiffe +// typed_config: +// "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig +// trust_domains: +// - name: foo.com +// trust_bundle: +// filename: "foo.pem" +// - name: envoy.com +// trust_bundle: +// filename: "envoy.pem" +// +// In this example, a presented peer certificate whose SAN matches `spiffe//foo.com/**` is validated against +// the "foo.pem" x.509 certificate. All the trust bundles are isolated from each other, so no trust domain can mint +// a SVID belonging to another trust domain. That means, in this example, a SVID signed by `envoy.com`'s CA with `spiffe//foo.com/**` +// SAN would be rejected since Envoy selects the trust bundle according to the presented SAN before validate the certificate. +message SPIFFECertValidatorConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig"; + + message TrustDomain { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig.TrustDomain"; + + // Name of the trust domain, `example.com`, `foo.bar.gov` for example. + // Note that this must *not* have "spiffe://" prefix. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Specify a data source holding x.509 trust bundle used for validating incoming SVID(s) in this trust domain. + config.core.v4alpha.DataSource trust_bundle = 2; + } + + // This field specifies trust domains used for validating incoming X.509-SVID(s). + repeated TrustDomain trust_domains = 1 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/include/envoy/ssl/BUILD b/include/envoy/ssl/BUILD index 38e300142e..cce9c38587 100644 --- a/include/envoy/ssl/BUILD +++ b/include/envoy/ssl/BUILD @@ -57,6 +57,7 @@ envoy_cc_library( envoy_cc_library( name = "certificate_validation_context_config_interface", hdrs = ["certificate_validation_context_config.h"], + external_deps = ["abseil_optional"], deps = [ "//source/common/common:matchers_lib", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", @@ -67,14 +68,11 @@ envoy_cc_library( envoy_cc_library( name = "ssl_socket_extended_info_interface", hdrs = ["ssl_socket_extended_info.h"], - deps = [ - ], ) envoy_cc_library( name = "ssl_socket_state", hdrs = ["ssl_socket_state.h"], - deps = [], ) envoy_cc_library( diff --git a/include/envoy/ssl/certificate_validation_context_config.h b/include/envoy/ssl/certificate_validation_context_config.h index 5011cb1226..4098c0b736 100644 --- a/include/envoy/ssl/certificate_validation_context_config.h +++ b/include/envoy/ssl/certificate_validation_context_config.h @@ -4,10 +4,13 @@ #include #include +#include "envoy/api/api.h" #include "envoy/common/pure.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "envoy/type/matcher/v3/string.pb.h" +#include "absl/types/optional.h" + namespace Envoy { namespace Ssl { @@ -69,6 +72,17 @@ class CertificateValidationContextConfig { virtual envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: TrustChainVerification trustChainVerification() const PURE; + + /** + * @return the configuration for the custom certificate validator if configured. + */ + virtual const absl::optional& + customValidatorConfig() const PURE; + + /** + * @return a reference to the api object. + */ + virtual Api::Api& api() const PURE; }; using CertificateValidationContextConfigPtr = std::unique_ptr; diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD index 0be754cc48..c02d221dd5 100644 --- a/source/common/ssl/BUILD +++ b/source/common/ssl/BUILD @@ -26,6 +26,7 @@ envoy_cc_library( name = "certificate_validation_context_config_impl_lib", srcs = ["certificate_validation_context_config_impl.cc"], hdrs = ["certificate_validation_context_config_impl.h"], + external_deps = ["abseil_optional"], deps = [ "//include/envoy/api:api_interface", "//include/envoy/ssl:certificate_validation_context_config_interface", diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index 2f4a1ac8bc..8198290b6a 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -32,8 +32,14 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl( verify_certificate_spki_list_(config.verify_certificate_spki().begin(), config.verify_certificate_spki().end()), allow_expired_certificate_(config.allow_expired_certificate()), - trust_chain_verification_(config.trust_chain_verification()) { - if (ca_cert_.empty()) { + trust_chain_verification_(config.trust_chain_verification()), + custom_validator_config_( + config.has_custom_validator_config() + ? absl::make_optional( + config.custom_validator_config()) + : absl::nullopt), + api_(api) { + if (ca_cert_.empty() && custom_validator_config_ == absl::nullopt) { if (!certificate_revocation_list_.empty()) { throw EnvoyException(fmt::format("Failed to load CRL from {} without trusted CA", certificateRevocationListPath())); diff --git a/source/common/ssl/certificate_validation_context_config_impl.h b/source/common/ssl/certificate_validation_context_config_impl.h index 1636c2ed07..56765baa74 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.h +++ b/source/common/ssl/certificate_validation_context_config_impl.h @@ -44,6 +44,13 @@ class CertificateValidationContextConfigImpl : public CertificateValidationConte return trust_chain_verification_; } + const absl::optional& + customValidatorConfig() const override { + return custom_validator_config_; + } + + Api::Api& api() const override { return api_; } + private: const std::string ca_cert_; const std::string ca_cert_path_; @@ -56,6 +63,8 @@ class CertificateValidationContextConfigImpl : public CertificateValidationConte const bool allow_expired_certificate_; const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: TrustChainVerification trust_chain_verification_; + const absl::optional custom_validator_config_; + Api::Api& api_; }; } // namespace Ssl diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 279d955d28..d694b49ab9 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -218,6 +218,11 @@ EXTENSIONS = { "envoy.watchdog.profile_action": "//source/extensions/watchdog/profile_action:config", "envoy.watchdog.abort_action": "//source/extensions/watchdog/abort_action:config", + # + # TLS peer certification validators + # + + "envoy.tls.cert_validator.spiffe": "//source/extensions/transport_sockets/tls/cert_validator/spiffe:config", } # These can be changed to ["//visibility:public"], for downstream builds which diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc index 08ce684a42..f1c186a087 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier.cc @@ -1,6 +1,7 @@ #include "extensions/quic_listeners/quiche/envoy_quic_proof_verifier.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/tls/cert_validator/default_validator.h" #include "quiche/quic/core/crypto/certificate_view.h" diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index e25c45f7bd..6caa0d0f41 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -141,12 +141,13 @@ envoy_cc_library( "abseil_node_hash_set", "abseil_synchronization", "ssl", - "bssl_wrapper_lib", + "bssl_wrapper_lib", ], # TLS is core functionality. visibility = ["//visibility:public"], deps = [ ":openssl_impl_lib", + ":stats_lib", ":utility_lib", "//include/envoy/ssl:context_config_interface", "//include/envoy/ssl:context_interface", @@ -164,6 +165,7 @@ envoy_cc_library( "//source/common/runtime:runtime_features_lib", "//source/common/stats:symbol_table_lib", "//source/common/stats:utility_lib", + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", "//source/extensions/transport_sockets/tls/ocsp:ocsp_lib", "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", @@ -171,6 +173,21 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "stats_lib", + srcs = ["stats.cc"], + hdrs = ["stats.h"], + external_deps = [ + "ssl", + ], + deps = [ + "//include/envoy/stats:stats_interface", + "//include/envoy/stats:stats_macros", + "//source/common/stats:symbol_table_lib", + "//source/common/stats:utility_lib", + ], +) + envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], diff --git a/source/extensions/transport_sockets/tls/cert_validator/BUILD b/source/extensions/transport_sockets/tls/cert_validator/BUILD new file mode 100644 index 0000000000..e0c495a33a --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/BUILD @@ -0,0 +1,45 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "cert_validator_lib", + srcs = [ + "default_validator.cc", + "factory.cc", + "utility.cc", + ], + hdrs = [ + "cert_validator.h", + "default_validator.h", + "factory.h", + "utility.h", + "well_known_names.h", + ], + external_deps = [ + "ssl", + "bssl_wrapper_lib", + "abseil_base", + "abseil_hash", + ], + visibility = ["//visibility:public"], + deps = [ + "//include/envoy/ssl:context_config_interface", + "//include/envoy/ssl:ssl_socket_extended_info_interface", + "//source/common/common:assert_lib", + "//source/common/common:base64_lib", + "//source/common/common:hex_lib", + "//source/common/common:utility_lib", + "//source/common/runtime:runtime_features_lib", + "//source/common/stats:symbol_table_lib", + "//source/common/stats:utility_lib", + "//source/extensions/transport_sockets/tls:stats_lib", + "//source/extensions/transport_sockets/tls:utility_lib", + ], +) diff --git a/source/extensions/transport_sockets/tls/cert_validator/cert_validator.h b/source/extensions/transport_sockets/tls/cert_validator/cert_validator.h new file mode 100644 index 0000000000..9b55c1cf6f --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/cert_validator.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/matchers.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/transport_sockets/tls/stats.h" + +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class CertValidator { +public: + virtual ~CertValidator() = default; + + /** + * Called to add the client validation context information to a given ssl context + * + * @param context the store context + * @param require_client_cert whether or not client cert is required + */ + virtual void addClientValidationContext(SSL_CTX* context, bool require_client_cert) PURE; + + /** + * Called by verifyCallback to do the actual cert chain verification. + * + * @param store_ctx the store context + * @param ssl_extended_info the info for storing the validation status + * @param leaf_cert the peer certificate to verify + * @return 1 to indicate verification success and 0 to indicate verification failure. + */ + virtual int + doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) PURE; + + /** + * Called to initialize all ssl contexts + * + * @param contexts the store context + * @param handshaker_provides_certificates whether or not a handshaker implementation provides + * certificates itself. + * @return the ssl verification mode flag + */ + virtual int initializeSslContexts(std::vector contexts, + bool handshaker_provides_certificates) PURE; + + /** + * Called when calculation hash for session context ids + * + * @param md the store context + * @param hash_buffer the buffer used for digest calculation + * @param hash_length the expected length of hash + */ + virtual void updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, + uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) PURE; + + virtual size_t daysUntilFirstCertExpires() const PURE; + virtual std::string getCaFileName() const PURE; + virtual Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const PURE; +}; + +using CertValidatorPtr = std::unique_ptr; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc new file mode 100644 index 0000000000..a523100f49 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc @@ -0,0 +1,488 @@ +#include "extensions/transport_sockets/tls/cert_validator/default_validator.h" + +#include +#include +#include +#include +#include + +#include "envoy/network/transport_socket.h" +#include "envoy/registry/registry.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/assert.h" +#include "common/common/base64.h" +#include "common/common/fmt.h" +#include "common/common/hex.h" +#include "common/common/matchers.h" +#include "common/common/utility.h" +#include "common/network/address_impl.h" +#include "common/protobuf/utility.h" +#include "common/runtime/runtime_features.h" +#include "common/stats/symbol_table_impl.h" +#include "common/stats/utility.h" + +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" +#include "extensions/transport_sockets/tls/cert_validator/factory.h" +#include "extensions/transport_sockets/tls/cert_validator/utility.h" +#include "extensions/transport_sockets/tls/cert_validator/well_known_names.h" +#include "extensions/transport_sockets/tls/stats.h" +#include "extensions/transport_sockets/tls/utility.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/err.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +DefaultCertValidator::DefaultCertValidator( + const Envoy::Ssl::CertificateValidationContextConfig* config, SslStats& stats, + TimeSource& time_source) + : config_(config), stats_(stats), time_source_(time_source) { + if (config_ != nullptr) { + allow_untrusted_certificate_ = config_->trustChainVerification() == + envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED; + } +}; + +int DefaultCertValidator::initializeSslContexts(std::vector contexts, + bool provides_certificates) { + + int verify_mode = SSL_VERIFY_NONE; + int verify_mode_validation_context = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + if (config_ != nullptr) { + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification verification = config_->trustChainVerification(); + if (verification == envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED) { + verify_mode = SSL_VERIFY_PEER; // Ensure client-certs will be requested even if we have + // nothing to verify against + verify_mode_validation_context = SSL_VERIFY_PEER; + } + } + + if (config_ != nullptr && !config_->caCert().empty() && !provides_certificates) { + ca_file_path_ = config_->caCertPath(); + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->caCert().data()), config_->caCert().size())); + RELEASE_ASSERT(bio != nullptr, ""); + // Based on BoringSSL's X509_load_cert_crl_file(). + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificates from ", config_->caCertPath())); + } + + for (auto& ctx : contexts) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + bool has_crl = false; + for (const X509_INFO* item : list.get()) { + if (item->x509) { + X509_STORE_add_cert(store, item->x509); + if (ca_cert_ == nullptr) { + X509_up_ref(item->x509); + ca_cert_.reset(item->x509); + } + } + if (item->crl) { + X509_STORE_add_crl(store, item->crl); + has_crl = true; + } + } + if (ca_cert_ == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificates from ", config_->caCertPath())); + } + if (has_crl) { + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + verify_mode = SSL_VERIFY_PEER; + verify_trusted_ca_ = true; + + // NOTE: We're using SSL_CTX_set_cert_verify_callback() instead of X509_verify_cert() + // directly. However, our new callback is still calling X509_verify_cert() under + // the hood. Therefore, to ignore cert expiration, we need to set the callback + // for X509_verify_cert to ignore that error. + if (config_->allowExpiredCertificate()) { + X509_STORE_set_verify_cb(store, CertValidatorUtil::ignoreCertificateExpirationCallback); + } + } + } + + if (config_ != nullptr && !config_->certificateRevocationList().empty()) { + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->certificateRevocationList().data()), + config_->certificateRevocationList().size())); + RELEASE_ASSERT(bio != nullptr, ""); + + // Based on BoringSSL's X509_load_cert_crl_file(). + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load CRL from ", config_->certificateRevocationListPath())); + } + + for (auto& ctx : contexts) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + for (const X509_INFO* item : list.get()) { + if (item->crl) { + X509_STORE_add_crl(store, item->crl); + } + } + + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + } + + const Envoy::Ssl::CertificateValidationContextConfig* cert_validation_config = config_; + if (cert_validation_config != nullptr) { + if (!cert_validation_config->verifySubjectAltNameList().empty()) { + verify_subject_alt_name_list_ = cert_validation_config->verifySubjectAltNameList(); + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->subjectAltNameMatchers().empty()) { + for (const envoy::type::matcher::v3::StringMatcher& matcher : + cert_validation_config->subjectAltNameMatchers()) { + subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher)); + } + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->verifyCertificateHashList().empty()) { + for (auto hash : cert_validation_config->verifyCertificateHashList()) { + // Remove colons from the 95 chars long colon-separated "fingerprint" + // in order to get the hex-encoded string. + if (hash.size() == 95) { + hash.erase(std::remove(hash.begin(), hash.end(), ':'), hash.end()); + } + const auto& decoded = Hex::decode(hash); + if (decoded.size() != SHA256_DIGEST_LENGTH) { + throw EnvoyException(absl::StrCat("Invalid hex-encoded SHA-256 ", hash)); + } + verify_certificate_hash_list_.push_back(decoded); + } + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->verifyCertificateSpkiList().empty()) { + for (const auto& hash : cert_validation_config->verifyCertificateSpkiList()) { + const auto decoded = Base64::decode(hash); + if (decoded.size() != SHA256_DIGEST_LENGTH) { + throw EnvoyException(absl::StrCat("Invalid base64-encoded SHA-256 ", hash)); + } + verify_certificate_spki_list_.emplace_back(decoded.begin(), decoded.end()); + } + verify_mode = verify_mode_validation_context; + } + } + + return verify_mode; +} + +int DefaultCertValidator::doVerifyCertChain( + X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) { + if (verify_trusted_ca_) { + int ret = X509_verify_cert(store_ctx); + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus( + ret == 1 ? Envoy::Ssl::ClientValidationStatus::Validated + : Envoy::Ssl::ClientValidationStatus::Failed); + } + + if (ret <= 0) { + stats_.fail_verify_error_.inc(); + return allow_untrusted_certificate_ ? 1 : ret; + } + } + + Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( + &leaf_cert, + transport_socket_options && + !transport_socket_options->verifySubjectAltNameListOverride().empty() + ? transport_socket_options->verifySubjectAltNameListOverride() + : verify_subject_alt_name_list_, + subject_alt_name_matchers_); + + if (ssl_extended_info) { + if (ssl_extended_info->certificateValidationStatus() == + Envoy::Ssl::ClientValidationStatus::NotValidated) { + ssl_extended_info->setCertificateValidationStatus(validated); + } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { + ssl_extended_info->setCertificateValidationStatus(validated); + } + } + + return allow_untrusted_certificate_ ? 1 + : (validated != Envoy::Ssl::ClientValidationStatus::Failed); +} + +Envoy::Ssl::ClientValidationStatus DefaultCertValidator::verifyCertificate( + X509* cert, const std::vector& verify_san_list, + const std::vector& subject_alt_name_matchers) { + Envoy::Ssl::ClientValidationStatus validated = Envoy::Ssl::ClientValidationStatus::NotValidated; + + if (!verify_san_list.empty()) { + if (!verifySubjectAltName(cert, verify_san_list)) { + stats_.fail_verify_san_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + validated = Envoy::Ssl::ClientValidationStatus::Validated; + } + + if (!subject_alt_name_matchers.empty() && !matchSubjectAltName(cert, subject_alt_name_matchers)) { + stats_.fail_verify_san_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + + if (!verify_certificate_hash_list_.empty() || !verify_certificate_spki_list_.empty()) { + const bool valid_certificate_hash = + !verify_certificate_hash_list_.empty() && + verifyCertificateHashList(cert, verify_certificate_hash_list_); + const bool valid_certificate_spki = + !verify_certificate_spki_list_.empty() && + verifyCertificateSpkiList(cert, verify_certificate_spki_list_); + + if (!valid_certificate_hash && !valid_certificate_spki) { + stats_.fail_verify_cert_hash_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + + validated = Envoy::Ssl::ClientValidationStatus::Validated; + } + + return validated; +} + +bool DefaultCertValidator::verifySubjectAltName(X509* cert, + const std::vector& subject_alt_names) { + bssl::UniquePtr san_names( + static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + if (san_names == nullptr) { + return false; + } + for (const GENERAL_NAME* general_name : san_names.get()) { + const std::string san = Utility::generalNameAsString(general_name); + for (auto& config_san : subject_alt_names) { + if (general_name->type == GEN_DNS ? dnsNameMatch(config_san, san.c_str()) + : config_san == san) { + return true; + } + } + } + return false; +} + +bool DefaultCertValidator::dnsNameMatch(const absl::string_view dns_name, + const absl::string_view pattern) { + if (dns_name == pattern) { + return true; + } + + size_t pattern_len = pattern.length(); + if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') { + if (dns_name.length() > pattern_len - 1) { + const size_t off = dns_name.length() - pattern_len + 1; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fix_wildcard_matching")) { + return dns_name.substr(0, off).find('.') == std::string::npos && + dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); + } else { + return dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); + } + } + } + + return false; +} + +bool DefaultCertValidator::matchSubjectAltName( + X509* cert, const std::vector& subject_alt_name_matchers) { + bssl::UniquePtr san_names( + static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + if (san_names == nullptr) { + return false; + } + for (const GENERAL_NAME* general_name : san_names.get()) { + const std::string san = Utility::generalNameAsString(general_name); + for (auto& config_san_matcher : subject_alt_name_matchers) { + // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics. + if (general_name->type == GEN_DNS && + config_san_matcher.matcher().match_pattern_case() == + envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact + ? dnsNameMatch(config_san_matcher.matcher().exact(), absl::string_view(san)) + : config_san_matcher.match(san)) { + return true; + } + } + } + return false; +} + +bool DefaultCertValidator::verifyCertificateSpkiList( + X509* cert, const std::vector>& expected_hashes) { + X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(cert); + if (pubkey == nullptr) { + return false; + } + uint8_t* spki = nullptr; + const int len = i2d_X509_PUBKEY(pubkey, &spki); + if (len < 0) { + return false; + } + bssl::UniquePtr free_spki(spki); + + std::vector computed_hash(SHA256_DIGEST_LENGTH); + SHA256(spki, len, computed_hash.data()); + + for (const auto& expected_hash : expected_hashes) { + if (computed_hash == expected_hash) { + return true; + } + } + return false; +} + +bool DefaultCertValidator::verifyCertificateHashList( + X509* cert, const std::vector>& expected_hashes) { + std::vector computed_hash(SHA256_DIGEST_LENGTH); + unsigned int n; + X509_digest(cert, EVP_sha256(), computed_hash.data(), &n); + RELEASE_ASSERT(n == computed_hash.size(), ""); + + for (const auto& expected_hash : expected_hashes) { + if (computed_hash == expected_hash) { + return true; + } + } + return false; +} + +void DefaultCertValidator::updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, + uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) { + int rc; + + // Hash all the settings that affect whether the server will allow/accept + // the client connection. This ensures that the client is always validated against + // the correct settings, even if session resumption across different listeners + // is enabled. + if (ca_cert_ != nullptr) { + rc = X509_digest(ca_cert_.get(), EVP_sha256(), hash_buffer, &hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + RELEASE_ASSERT(hash_length == SHA256_DIGEST_LENGTH, + fmt::format("invalid SHA256 hash length {}", hash_length)); + + rc = EVP_DigestUpdate(md.get(), hash_buffer, hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + + // verify_subject_alt_name_list_ can only be set with a ca_cert + for (const std::string& name : verify_subject_alt_name_list_) { + rc = EVP_DigestUpdate(md.get(), name.data(), name.size()); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + } + + for (const auto& hash : verify_certificate_hash_list_) { + rc = EVP_DigestUpdate(md.get(), hash.data(), + hash.size() * + sizeof(std::remove_reference::type::value_type)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + + for (const auto& hash : verify_certificate_spki_list_) { + rc = EVP_DigestUpdate(md.get(), hash.data(), + hash.size() * + sizeof(std::remove_reference::type::value_type)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } +} + +void DefaultCertValidator::addClientValidationContext(SSL_CTX* ctx, bool require_client_cert) { + if (config_ == nullptr || config_->caCert().empty()) { + return; + } + + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->caCert().data()), config_->caCert().size())); + RELEASE_ASSERT(bio != nullptr, ""); + // Based on BoringSSL's SSL_add_file_cert_subjects_to_stack(). + bssl::UniquePtr list(sk_X509_NAME_new( + [](const X509_NAME* const* a, const X509_NAME* const* b) -> int { return X509_NAME_cmp(*a, *b); })); + RELEASE_ASSERT(list != nullptr, ""); + for (;;) { + bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + if (cert == nullptr) { + break; + } + X509_NAME* name = X509_get_subject_name(cert.get()); + if (name == nullptr) { + throw EnvoyException(absl::StrCat("Failed to load trusted client CA certificates from ", + config_->caCertPath())); + } + // Check for duplicates. + // Note that BoringSSL call only returns 0 or 1. + // OpenSSL can also return -1, for example on sk_find calls in an empty list + if (sk_X509_NAME_find(list.get(), nullptr, name) == 1) { + continue; + } + bssl::UniquePtr name_dup(X509_NAME_dup(name)); + if (name_dup == nullptr || !sk_X509_NAME_push(list.get(), name_dup.release())) { + throw EnvoyException(absl::StrCat("Failed to load trusted client CA certificates from ", + config_->caCertPath())); + } + } + + // Check for EOF. + const uint32_t err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { + ERR_clear_error(); + } else { + throw EnvoyException( + absl::StrCat("Failed to load trusted client CA certificates from ", config_->caCertPath())); + } + SSL_CTX_set_client_CA_list(ctx, list.release()); + + if (require_client_cert) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } +} + +Envoy::Ssl::CertificateDetailsPtr DefaultCertValidator::getCaCertInformation() const { + if (ca_cert_ == nullptr) { + return nullptr; + } + return Utility::certificateDetails(ca_cert_.get(), getCaFileName(), time_source_); +} + +size_t DefaultCertValidator::daysUntilFirstCertExpires() const { + return Utility::getDaysUntilExpiration(ca_cert_.get(), time_source_); +} + +class DefaultCertValidatorFactory : public CertValidatorFactory { +public: + CertValidatorPtr createCertValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, + SslStats& stats, TimeSource& time_source) override { + return std::make_unique(config, stats, time_source); + } + + absl::string_view name() override { return CertValidatorNames::get().Default; } +}; + +REGISTER_FACTORY(DefaultCertValidatorFactory, CertValidatorFactory); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/default_validator.h b/source/extensions/transport_sockets/tls/cert_validator/default_validator.h new file mode 100644 index 0000000000..54dd4be783 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/matchers.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" +#include "extensions/transport_sockets/tls/stats.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class DefaultCertValidator : public CertValidator { +public: + DefaultCertValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, + SslStats& stats, TimeSource& time_source); + + ~DefaultCertValidator() override = default; + + // Tls::CertValidator + void addClientValidationContext(SSL_CTX* context, bool require_client_cert) override; + + int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) override; + + int initializeSslContexts(std::vector contexts, bool provides_certificates) override; + + void updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) override; + + size_t daysUntilFirstCertExpires() const override; + std::string getCaFileName() const override { return ca_file_path_; }; + Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const override; + + // Utility functions. + Envoy::Ssl::ClientValidationStatus + verifyCertificate(X509* cert, const std::vector& verify_san_list, + const std::vector& subject_alt_name_matchers); + + /** + * Verifies certificate hash for pinning. The hash is a hex-encoded SHA-256 of the DER-encoded + * certificate. + * + * @param ssl the certificate to verify + * @param expected_hashes the configured list of certificate hashes to match + * @return true if the verification succeeds + */ + static bool verifyCertificateHashList(X509* cert, + const std::vector>& expected_hashes); + + /** + * Verifies certificate hash for pinning. The hash is a base64-encoded SHA-256 of the DER-encoded + * Subject Public Key Information (SPKI) of the certificate. + * + * @param ssl the certificate to verify + * @param expected_hashes the configured list of certificate hashes to match + * @return true if the verification succeeds + */ + static bool verifyCertificateSpkiList(X509* cert, + const std::vector>& expected_hashes); + + /** + * Performs subjectAltName verification + * @param ssl the certificate to verify + * @param subject_alt_names the configured subject_alt_names to match + * @return true if the verification succeeds + */ + static bool verifySubjectAltName(X509* cert, const std::vector& subject_alt_names); + + /** + * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard. + * NOTE: public for testing + * @param dns_name the DNS name to match + * @param pattern the pattern to match against (*.example.com) + * @return true if the san matches pattern + */ + static bool dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern); + + /** + * Performs subjectAltName matching with the provided matchers. + * @param ssl the certificate to verify + * @param subject_alt_name_matchers the configured matchers to match + * @return true if the verification succeeds + */ + static bool + matchSubjectAltName(X509* cert, + const std::vector& subject_alt_name_matchers); + +private: + const Envoy::Ssl::CertificateValidationContextConfig* config_; + SslStats& stats_; + TimeSource& time_source_; + + bool allow_untrusted_certificate_{false}; + bssl::UniquePtr ca_cert_; + std::string ca_file_path_; + std::vector subject_alt_name_matchers_; + std::vector> verify_certificate_hash_list_; + std::vector> verify_certificate_spki_list_; + std::vector verify_subject_alt_name_list_; + bool verify_trusted_ca_{false}; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/factory.cc b/source/extensions/transport_sockets/tls/cert_validator/factory.cc new file mode 100644 index 0000000000..349b04e260 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/factory.cc @@ -0,0 +1,21 @@ +#include "extensions/transport_sockets/tls/cert_validator/factory.h" + +#include "envoy/ssl/context_config.h" + +#include "extensions/transport_sockets/tls/cert_validator/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +std::string getCertValidatorName(const Envoy::Ssl::CertificateValidationContextConfig* config) { + return config != nullptr && config->customValidatorConfig().has_value() + ? config->customValidatorConfig().value().name() + : CertValidatorNames::get().Default; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/factory.h b/source/extensions/transport_sockets/tls/cert_validator/factory.h new file mode 100644 index 0000000000..590bf15fcd --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/factory.h @@ -0,0 +1,36 @@ +#pragma once + +#include "envoy/common/pure.h" +#include "envoy/ssl/context_config.h" + +#include "common/common/utility.h" + +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" +#include "extensions/transport_sockets/tls/stats.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +std::string getCertValidatorName(const Envoy::Ssl::CertificateValidationContextConfig* config); + +class CertValidatorFactory { +public: + virtual ~CertValidatorFactory() = default; + + virtual CertValidatorPtr + createCertValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, SslStats& stats, + TimeSource& time_source) PURE; + + virtual absl::string_view name() PURE; + + std::string category() { return "envoy.tls.cert_validator"; } +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD b/source/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD new file mode 100644 index 0000000000..9e35116eb4 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD @@ -0,0 +1,44 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = [ + "spiffe_validator.cc", + ], + hdrs = [ + "spiffe_validator.h", + ], + external_deps = [ + "ssl", + "abseil_base", + "abseil_hash", + ], + security_posture = "unknown", + status = "wip", + visibility = ["//visibility:public"], + deps = [ + "//include/envoy/ssl:context_config_interface", + "//include/envoy/ssl:ssl_socket_extended_info_interface", + "//source/common/common:assert_lib", + "//source/common/common:base64_lib", + "//source/common/common:c_smart_ptr_lib", + "//source/common/common:hex_lib", + "//source/common/common:utility_lib", + "//source/common/config:datasource_lib", + "//source/common/config:utility_lib", + "//source/common/stats:symbol_table_lib", + "//source/common/stats:utility_lib", + "//source/extensions/transport_sockets/tls:stats_lib", + "//source/extensions/transport_sockets/tls:utility_lib", + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc new file mode 100644 index 0000000000..13393061e7 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc @@ -0,0 +1,308 @@ +#include "extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h" + +#include "envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.pb.h" +#include "envoy/network/transport_socket.h" +#include "envoy/registry/registry.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/config/datasource.h" +#include "common/config/utility.h" +#include "common/protobuf/message_validator_impl.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/transport_sockets/tls/cert_validator/factory.h" +#include "extensions/transport_sockets/tls/cert_validator/utility.h" +#include "extensions/transport_sockets/tls/cert_validator/well_known_names.h" +#include "extensions/transport_sockets/tls/stats.h" +#include "extensions/transport_sockets/tls/utility.h" + +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +using SPIFFEConfig = envoy::extensions::transport_sockets::tls::v3::SPIFFECertValidatorConfig; + +SPIFFEValidator::SPIFFEValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, + SslStats& stats, TimeSource& time_source) + : stats_(stats), time_source_(time_source) { + ASSERT(config != nullptr); + allow_expired_certificate_ = config->allowExpiredCertificate(); + + SPIFFEConfig message; + Config::Utility::translateOpaqueConfig(config->customValidatorConfig().value().typed_config(), + ProtobufWkt::Struct(), + ProtobufMessage::getStrictValidationVisitor(), message); + + if (!config->subjectAltNameMatchers().empty()) { + for (const auto& matcher : config->subjectAltNameMatchers()) { + subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher)); + } + } + + const auto size = message.trust_domains().size(); + trust_bundle_stores_.reserve(size); + for (auto& domain : message.trust_domains()) { + if (trust_bundle_stores_.find(domain.name()) != trust_bundle_stores_.end()) { + throw EnvoyException(absl::StrCat( + "Multiple trust bundles are given for one trust domain for ", domain.name())); + } + + auto cert = Config::DataSource::read(domain.trust_bundle(), true, config->api()); + bssl::UniquePtr bio(BIO_new_mem_buf(const_cast(cert.data()), cert.size())); + RELEASE_ASSERT(bio != nullptr, ""); + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr || sk_X509_INFO_num(list.get()) == 0) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificate for ", domain.name())); + } + + auto store = X509StorePtr(X509_STORE_new()); + bool has_crl = false; + bool ca_loaded = false; + for (const X509_INFO* item : list.get()) { + if (item->x509) { + X509_STORE_add_cert(store.get(), item->x509); + ca_certs_.push_back(bssl::UniquePtr(item->x509)); + X509_up_ref(item->x509); + if (!ca_loaded) { + // TODO: With the current interface, we cannot return the multiple + // cert information on getCaCertInformation method. + // So temporarily we return the first CA's info here. + ca_loaded = true; + ca_file_name_ = absl::StrCat(domain.name(), ": ", + domain.trust_bundle().filename().empty() + ? "" + : domain.trust_bundle().filename()); + } + } + + if (item->crl) { + has_crl = true; + X509_STORE_add_crl(store.get(), item->crl); + } + } + if (has_crl) { + X509_STORE_set_flags(store.get(), X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + trust_bundle_stores_[domain.name()] = std::move(store); + } +} + +void SPIFFEValidator::addClientValidationContext(SSL_CTX* ctx, bool) { + bssl::UniquePtr list(sk_X509_NAME_new( + [](const X509_NAME* const* a, const X509_NAME* const* b) -> int { return X509_NAME_cmp(*a, *b); })); + + for (auto& ca : ca_certs_) { + X509_NAME* name = X509_get_subject_name(ca.get()); + + // Check for duplicates. + // Note that BoringSSL call only returns 0 or 1. + // OpenSSL can also return -1, for example on sk_find calls in an empty list + if (sk_X509_NAME_find(list.get(), nullptr, name) >= 0) { + continue; + } + + bssl::UniquePtr name_dup(X509_NAME_dup(name)); + if (name_dup == nullptr || !sk_X509_NAME_push(list.get(), name_dup.release())) { + throw EnvoyException(absl::StrCat("Failed to load trusted client CA certificate")); + } + } + SSL_CTX_set_client_CA_list(ctx, list.release()); +} + +void SPIFFEValidator::updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, + uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) { + int rc; + for (auto& ca : ca_certs_) { + rc = X509_digest(ca.get(), EVP_sha256(), hash_buffer, &hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + RELEASE_ASSERT(hash_length == SHA256_DIGEST_LENGTH, + fmt::format("invalid SHA256 hash length {}", hash_length)); + rc = EVP_DigestUpdate(md.get(), hash_buffer, hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } +} + +int SPIFFEValidator::initializeSslContexts(std::vector, bool) { + return SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; +} + +int SPIFFEValidator::doVerifyCertChain(X509_STORE_CTX* store_ctx, + Ssl::SslExtendedSocketInfo* ssl_extended_info, + X509& leaf_cert, const Network::TransportSocketOptions*) { + if (!SPIFFEValidator::certificatePrecheck(&leaf_cert)) { + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus(Envoy::Ssl::ClientValidationStatus::Failed); + } + stats_.fail_verify_error_.inc(); + return 0; + } + + auto trust_bundle = getTrustBundleStore(&leaf_cert); + if (!trust_bundle) { + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus(Envoy::Ssl::ClientValidationStatus::Failed); + } + stats_.fail_verify_error_.inc(); + return 0; + } + + // Set the trust bundle's certificate store on the context, and do the verification. + bssl::UniquePtr verify_ctx(X509_STORE_CTX_new()); + // We make a copy of X509_VERIFY_PARAMs in the store_ctx that we received as a parameter. + // This is a precaution mostly, as Envoy doesn't configure any X509_VERIFY_PARAMs. + // Note that there's no api to copy crls from one store_ctx to another; the assumption is that + // X509_V_FLAG_CRL_CHECK/X509_V_FLAG_CRL_CHECK_ALL verify_params are not used. + // Should this change, consider opening up X509_STORE_CTX struct, which is internal atm. + X509_STORE_CTX_init(verify_ctx.get(), trust_bundle, &leaf_cert, X509_STORE_CTX_get0_chain(store_ctx)); + X509_VERIFY_PARAM* verify_params = X509_VERIFY_PARAM_new(); + X509_VERIFY_PARAM_inherit(verify_params, X509_STORE_CTX_get0_param(store_ctx)); + X509_STORE_CTX_set0_param(verify_ctx.get(), verify_params); + if (allow_expired_certificate_) { + X509_STORE_CTX_set_verify_cb(verify_ctx.get(), CertValidatorUtil::ignoreCertificateExpirationCallback); + } + auto ret = X509_verify_cert(verify_ctx.get()); + if (!ret) { + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus(Envoy::Ssl::ClientValidationStatus::Failed); + } + stats_.fail_verify_error_.inc(); + return 0; + } + + // Do SAN matching. + const bool san_match = subject_alt_name_matchers_.empty() ? true : matchSubjectAltName(leaf_cert); + if (!san_match) { + stats_.fail_verify_error_.inc(); + } + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus( + san_match ? Envoy::Ssl::ClientValidationStatus::Validated + : Envoy::Ssl::ClientValidationStatus::Failed); + } + X509_STORE_CTX_cleanup(verify_ctx.get()); + return san_match; +} + +X509_STORE* SPIFFEValidator::getTrustBundleStore(X509* leaf_cert) { + bssl::UniquePtr san_names(static_cast( + X509_get_ext_d2i(leaf_cert, NID_subject_alt_name, nullptr, nullptr))); + if (!san_names) { + return nullptr; + } + + std::string trust_domain; + for (const GENERAL_NAME* general_name : san_names.get()) { + if (general_name->type != GEN_URI) { + continue; + } + + const std::string san = Utility::generalNameAsString(general_name); + trust_domain = SPIFFEValidator::extractTrustDomain(san); + // We can assume that valid SVID has only one URI san. + break; + } + + if (trust_domain.empty()) { + return nullptr; + } + + auto target_store = trust_bundle_stores_.find(trust_domain); + return target_store != trust_bundle_stores_.end() ? target_store->second.get() : nullptr; +} + +bool SPIFFEValidator::certificatePrecheck(X509* leaf_cert) { + // Check basic constrains and key usage. + // https://github.com/spiffe/spiffe/blob/master/standards/X509-SVID.md#52-leaf-validation + const auto ext = X509_get_extension_flags(leaf_cert); + if (ext & EXFLAG_CA) { + return false; + } + + const auto us = X509_get_key_usage(leaf_cert); + return (us & (KU_CRL_SIGN | KU_KEY_CERT_SIGN)) == 0; +} + +bool SPIFFEValidator::matchSubjectAltName(X509& leaf_cert) { + bssl::UniquePtr san_names(static_cast( + X509_get_ext_d2i(&leaf_cert, NID_subject_alt_name, nullptr, nullptr))); + // We must not have san_names == nullptr here because this function is called after the + // SPIFFE cert validation algorithm succeeded, which requires exactly one URI SAN in the leaf + // cert. + ASSERT(san_names != nullptr, + "san_names should have at least one name after SPIFFE cert validation"); + + // Only match against URI SAN since SPIFFE specification does not restrict values in other SAN + // types. See the discussion: https://github.com/envoyproxy/envoy/issues/15392 + for (const GENERAL_NAME* general_name : san_names.get()) { + if (general_name->type == GEN_URI) { + const std::string san = Utility::generalNameAsString(general_name); + for (const auto& config_san_matcher : subject_alt_name_matchers_) { + if (config_san_matcher.match(san)) { + return true; + } + } + } + } + return false; +} + +std::string SPIFFEValidator::extractTrustDomain(const std::string& san) { + static const std::string prefix = "spiffe://"; + if (!absl::StartsWith(san, prefix)) { + return ""; + } + + auto pos = san.find('/', prefix.size()); + if (pos != std::string::npos) { + return san.substr(prefix.size(), pos - prefix.size()); + } + return ""; +} + +size_t SPIFFEValidator::daysUntilFirstCertExpires() const { + if (ca_certs_.empty()) { + return 0; + } + size_t ret = SIZE_MAX; + for (auto& cert : ca_certs_) { + size_t tmp = Utility::getDaysUntilExpiration(cert.get(), time_source_); + if (tmp < ret) { + ret = tmp; + } + } + return ret; +} + +Envoy::Ssl::CertificateDetailsPtr SPIFFEValidator::getCaCertInformation() const { + if (ca_certs_.empty()) { + return nullptr; + } + // TODO(mathetake): With the current interface, we cannot pass the multiple cert information. + // So temporarily we return the first CA's info here. + return Utility::certificateDetails(ca_certs_[0].get(), getCaFileName(), time_source_); +}; + +class SPIFFEValidatorFactory : public CertValidatorFactory { +public: + CertValidatorPtr createCertValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, + SslStats& stats, TimeSource& time_source) override { + return std::make_unique(config, stats, time_source); + } + + absl::string_view name() override { return CertValidatorNames::get().SPIFFE; } +}; + +REGISTER_FACTORY(SPIFFEValidatorFactory, CertValidatorFactory); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h new file mode 100644 index 0000000000..55b2fa3cc0 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/c_smart_ptr.h" +#include "common/common/matchers.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" +#include "extensions/transport_sockets/tls/stats.h" + +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +using X509StorePtr = CSmartPtr; + +class SPIFFEValidator : public CertValidator { +public: + SPIFFEValidator(SslStats& stats, TimeSource& time_source) + : stats_(stats), time_source_(time_source){}; + SPIFFEValidator(const Envoy::Ssl::CertificateValidationContextConfig* config, SslStats& stats, + TimeSource& time_source); + ~SPIFFEValidator() override = default; + + // Tls::CertValidator + void addClientValidationContext(SSL_CTX* context, bool require_client_cert) override; + + int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, + X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) override; + + int initializeSslContexts(std::vector contexts, bool provides_certificates) override; + + void updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md, uint8_t hash_buffer[EVP_MAX_MD_SIZE], + unsigned hash_length) override; + + size_t daysUntilFirstCertExpires() const override; + std::string getCaFileName() const override { return ca_file_name_; } + Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const override; + + // Utility functions + X509_STORE* getTrustBundleStore(X509* leaf_cert); + static std::string extractTrustDomain(const std::string& san); + static bool certificatePrecheck(X509* leaf_cert); + absl::flat_hash_map& trustBundleStores() { + return trust_bundle_stores_; + }; + + bool matchSubjectAltName(X509& leaf_cert); + +private: + bool allow_expired_certificate_{false}; + std::vector> ca_certs_; + std::string ca_file_name_; + std::vector subject_alt_name_matchers_{}; + absl::flat_hash_map trust_bundle_stores_; + + SslStats& stats_; + TimeSource& time_source_; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/utility.cc b/source/extensions/transport_sockets/tls/cert_validator/utility.cc new file mode 100644 index 0000000000..53a3fd41ec --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/utility.cc @@ -0,0 +1,21 @@ +#include "extensions/transport_sockets/tls/cert_validator/utility.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +int CertValidatorUtil::ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx) { + if (!ok) { + int err = X509_STORE_CTX_get_error(store_ctx); + if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_CERT_NOT_YET_VALID) { + return 1; + } + } + return ok; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/utility.h b/source/extensions/transport_sockets/tls/cert_validator/utility.h new file mode 100644 index 0000000000..ff982fb07c --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/utility.h @@ -0,0 +1,19 @@ +#pragma once + +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class CertValidatorUtil { +public: + // Callback for allow_expired_certificate option + static int ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx); +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/cert_validator/well_known_names.h b/source/extensions/transport_sockets/tls/cert_validator/well_known_names.h new file mode 100644 index 0000000000..226830cd51 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/well_known_names.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +/** + * Well-known certificate validator's names. + */ +class CertValidatorValues { +public: + // default certificate validator + const std::string Default = "envoy.tls.cert_validator.default"; + + // SPIFFE(https://github.com/spiffe/spiffe) + const std::string SPIFFE = "envoy.tls.cert_validator.spiffe"; +}; + +using CertValidatorNames = ConstSingleton; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 27ff5cd48a..5411096661 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -22,6 +22,8 @@ #include "common/runtime/runtime_features.h" #include "common/stats/utility.h" +#include "extensions/transport_sockets/tls/cert_validator/factory.h" +#include "extensions/transport_sockets/tls/stats.h" #include "extensions/transport_sockets/tls/openssl_impl.h" #include "extensions/transport_sockets/tls/utility.h" @@ -57,7 +59,7 @@ int ContextImpl::sslExtendedSocketInfoIndex() { ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source) - : scope_(scope), stats_(generateStats(scope)), time_source_(time_source), + : scope_(scope), stats_(generateSslStats(scope)), time_source_(time_source), tls_max_version_(config.maxProtocolVersion()), stat_name_set_(scope.symbolTable().makeSet("TransportSockets::Tls")), unknown_ssl_cipher_(stat_name_set_->add("unknown_ssl_cipher")), @@ -68,6 +70,19 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ssl_versions_(stat_name_set_->add("ssl.versions")), ssl_curves_(stat_name_set_->add("ssl.curves")), ssl_sigalgs_(stat_name_set_->add("ssl.sigalgs")), capabilities_(config.capabilities()) { + + auto cert_validator_name = getCertValidatorName(config.certificateValidationContext()); + auto cert_validator_factory = + Registry::FactoryRegistry::getFactory(cert_validator_name); + + if (!cert_validator_factory) { + throw EnvoyException( + absl::StrCat("Failed to get certificate validator factory for ", cert_validator_name)); + } + + cert_validator_ = cert_validator_factory->createCertValidator( + config.certificateValidationContext(), stats_, time_source_); + const auto tls_certificates = config.tlsCertificates(); tls_context_.cert_contexts_.resize(std::max(static_cast(1), tls_certificates.size())); @@ -82,7 +97,8 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c rc = SSL_CTX_set_max_proto_version(tls_context_.ssl_ctx_.get(), config.maxProtocolVersion()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - if (!Envoy::Extensions::TransportSockets::Tls::set_strict_cipher_list(tls_context_.ssl_ctx_.get(), config.cipherSuites().c_str())) { + if (!capabilities_.provides_ciphers_and_curves && + !Envoy::Extensions::TransportSockets::Tls::set_strict_cipher_list(tls_context_.ssl_ctx_.get(), config.cipherSuites().c_str())) { // Break up a set of ciphers into each individual cipher and try them each individually in // order to attempt to log which specific one failed. Example of config.cipherSuites(): // "-ALL:[ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]:ECDHE-ECDSA-AES128-SHA". @@ -110,154 +126,20 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c config.cipherSuites(), absl::StrJoin(bad_ciphers, ", "))); } - if (!SSL_CTX_set1_curves_list(tls_context_.ssl_ctx_.get(), config.ecdhCurves().c_str())) { + if (!capabilities_.provides_ciphers_and_curves && + !SSL_CTX_set1_curves_list(tls_context_.ssl_ctx_.get(), config.ecdhCurves().c_str())) { throw EnvoyException(absl::StrCat("Failed to initialize ECDH curves ", config.ecdhCurves())); } - int verify_mode = SSL_VERIFY_NONE; - int verify_mode_validation_context = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - - if (config.certificateValidationContext() != nullptr) { - envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: - TrustChainVerification verification = - config.certificateValidationContext()->trustChainVerification(); - if (verification == envoy::extensions::transport_sockets::tls::v3:: - CertificateValidationContext::ACCEPT_UNTRUSTED) { - verify_mode = SSL_VERIFY_PEER; // Ensure client-certs will be requested even if we have - // nothing to verify against - verify_mode_validation_context = SSL_VERIFY_PEER; - } - } - - if (config.certificateValidationContext() != nullptr && - !config.certificateValidationContext()->caCert().empty() && - !config.capabilities().provides_certificates) { - ca_file_path_ = config.certificateValidationContext()->caCertPath(); - bssl::UniquePtr bio( - BIO_new_mem_buf(const_cast(config.certificateValidationContext()->caCert().data()), - config.certificateValidationContext()->caCert().size())); - RELEASE_ASSERT(bio != nullptr, ""); - // Based on BoringSSL's X509_load_cert_crl_file(). - bssl::UniquePtr list( - PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); - if (list == nullptr) { - throw EnvoyException(absl::StrCat("Failed to load trusted CA certificates from ", - config.certificateValidationContext()->caCertPath())); - } - - X509_STORE* store = SSL_CTX_get_cert_store(tls_context_.ssl_ctx_.get()); - bool has_crl = false; - for (const X509_INFO* item : list.get()) { - if (item->x509) { - X509_STORE_add_cert(store, item->x509); - if (ca_cert_ == nullptr) { - X509_up_ref(item->x509); - ca_cert_.reset(item->x509); - } - } - if (item->crl) { - X509_STORE_add_crl(store, item->crl); - has_crl = true; - } - - // TODO (dmitri-d) why do we do this here? Upstream doesn't - Envoy::Extensions::TransportSockets::Tls::ssl_ctx_add_client_CA(tls_context_.ssl_ctx_.get(), - item->x509); - } - if (ca_cert_ == nullptr) { - throw EnvoyException(fmt::format("Failed to load trusted CA certificates from {}", - config.certificateValidationContext()->caCertPath())); - } - if (has_crl) { - X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); - } - verify_mode = SSL_VERIFY_PEER; - verify_trusted_ca_ = true; - - // NOTE: We're using SSL_CTX_set_cert_verify_callback() instead of X509_verify_cert() - // directly. However, our new callback is still calling X509_verify_cert() under - // the hood. Therefore, to ignore cert expiration, we need to set the callback - // for X509_verify_cert to ignore that error. - if (config.certificateValidationContext()->allowExpiredCertificate()) { - X509_STORE_set_verify_cb(store, ContextImpl::ignoreCertificateExpirationCallback); - } - } - - if (config.certificateValidationContext() != nullptr && - !config.certificateValidationContext()->certificateRevocationList().empty()) { - bssl::UniquePtr bio(BIO_new_mem_buf( - const_cast( - config.certificateValidationContext()->certificateRevocationList().data()), - config.certificateValidationContext()->certificateRevocationList().size())); - RELEASE_ASSERT(bio != nullptr, ""); - - // Based on BoringSSL's X509_load_cert_crl_file(). - bssl::UniquePtr list( - PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); - if (list == nullptr) { - throw EnvoyException( - absl::StrCat("Failed to load CRL from ", - config.certificateValidationContext()->certificateRevocationListPath())); - } - - X509_STORE* store = SSL_CTX_get_cert_store(tls_context_.ssl_ctx_.get()); - for (const X509_INFO* item : list.get()) { - if (item->crl) { - X509_STORE_add_crl(store, item->crl); - } - } - - X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); - } - - const Envoy::Ssl::CertificateValidationContextConfig* cert_validation_config = - config.certificateValidationContext(); - if (cert_validation_config != nullptr) { - if (!cert_validation_config->verifySubjectAltNameList().empty()) { - verify_subject_alt_name_list_ = cert_validation_config->verifySubjectAltNameList(); - verify_mode = verify_mode_validation_context; - } - - if (!cert_validation_config->subjectAltNameMatchers().empty()) { - for (const envoy::type::matcher::v3::StringMatcher& matcher : - cert_validation_config->subjectAltNameMatchers()) { - subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher)); - } - verify_mode = verify_mode_validation_context; - } - - if (!cert_validation_config->verifyCertificateHashList().empty()) { - for (auto hash : cert_validation_config->verifyCertificateHashList()) { - // Remove colons from the 95 chars long colon-separated "fingerprint" - // in order to get the hex-encoded string. - if (hash.size() == 95) { - hash.erase(std::remove(hash.begin(), hash.end(), ':'), hash.end()); - } - const auto& decoded = Hex::decode(hash); - if (decoded.size() != SHA256_DIGEST_LENGTH) { - throw EnvoyException(absl::StrCat("Invalid hex-encoded SHA-256 ", hash)); - } - verify_certificate_hash_list_.push_back(decoded); - } - verify_mode = verify_mode_validation_context; - } - - if (!cert_validation_config->verifyCertificateSpkiList().empty()) { - for (const auto& hash : cert_validation_config->verifyCertificateSpkiList()) { - const auto decoded = Base64::decode(hash); - if (decoded.size() != SHA256_DIGEST_LENGTH) { - throw EnvoyException(absl::StrCat("Invalid base64-encoded SHA-256 ", hash)); - } - verify_certificate_spki_list_.emplace_back(decoded.begin(), decoded.end()); - } - verify_mode = verify_mode_validation_context; - } - } - + // We only maintain one SSL_CTX under OpenSSL, but to keep maintenance simple[r], + // initializeSslContexts() parameter list was kept unchanged from upstream, + // hence construction of a vector of ssl contexts... + auto verify_mode = cert_validator_->initializeSslContexts( + { tls_context_.ssl_ctx_.get() }, config.capabilities().provides_certificates); if (!capabilities_.verifies_peer_certificates) { if (verify_mode != SSL_VERIFY_NONE) { SSL_CTX_set_verify(tls_context_.ssl_ctx_.get(), verify_mode, nullptr); - SSL_CTX_set_cert_verify_callback(tls_context_.ssl_ctx_.get(), ContextImpl::verifyCallback, this); + SSL_CTX_set_cert_verify_callback(tls_context_.ssl_ctx_.get(), verifyCallback, this); } } @@ -316,12 +198,13 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c if (must_staple == must_staple_ext_value) { cert_context.is_must_staple_ = true; } + bssl::UniquePtr public_key(X509_get_pubkey(cert_context.cert_chain_.get())); const int pkey_id = EVP_PKEY_id(public_key.get()); if (!cert_pkey_ids.insert(pkey_id).second) { throw EnvoyException(fmt::format("Failed to load certificate chain from {}, at most one " "certificate of a given type may be specified", - cert_context.cert_chain_file_path_)); + cert_context.cert_chain_file_path_)); } cert_context.is_ecdsa_ = pkey_id == EVP_PKEY_EC; switch (pkey_id) { @@ -351,7 +234,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c fmt::format("Failed to load certificate chain from {}, only RSA " "certificates with 2048-bit or larger keys are supported", cert_context.cert_chain_file_path_)); - } + } } break; } @@ -372,7 +255,7 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c */ } else { // Load private key. - bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), + bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), tls_certificate.privateKey().size())); RELEASE_ASSERT(bio != nullptr, ""); bssl::UniquePtr pkey( @@ -381,8 +264,9 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ? const_cast(tls_certificate.password().c_str()) : nullptr)); if (pkey == nullptr || !SSL_CTX_use_PrivateKey(tls_context_.ssl_ctx_.get(), pkey.get())) { - throw EnvoyException( - absl::StrCat("Failed to load private key from ", tls_certificate.privateKeyPath())); + throw EnvoyException(fmt::format("Failed to load private key from {}, Cause: {}", + tls_certificate.privateKeyPath(), + Utility::getLastCryptoError().value_or("unknown"))); } } } @@ -391,13 +275,6 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c // use the server's cipher list preferences SSL_CTX_set_options(tls_context_.ssl_ctx_.get(), SSL_OP_CIPHER_SERVER_PREFERENCE); - if (config.certificateValidationContext() != nullptr) { - allow_untrusted_certificate_ = - config.certificateValidationContext()->trustChainVerification() == - envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: - ACCEPT_UNTRUSTED; - } - parsed_alpn_protocols_ = parseAlpnProtocols(config.alpnProtocols()); // To enumerate the required builtin ciphers, curves, algorithms, and @@ -480,17 +357,6 @@ bssl::UniquePtr ContextImpl::newSsl(const Network::TransportSocketOptions*) return bssl::UniquePtr(SSL_new(tls_context_.ssl_ctx_.get())); } -int ContextImpl::ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* ctx) { - if (!ok) { - int err = X509_STORE_CTX_get_error(ctx); - if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_CERT_NOT_YET_VALID) { - return 1; - } - } - - return ok; -} - int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { ContextImpl* impl = reinterpret_cast(arg); SSL* ssl = reinterpret_cast( @@ -501,88 +367,13 @@ int ContextImpl::verifyCallback(X509_STORE_CTX* store_ctx, void* arg) { cert = X509_STORE_CTX_get0_cert(store_ctx); } - return impl->doVerifyCertChain( + return impl->cert_validator_->doVerifyCertChain( store_ctx, reinterpret_cast( SSL_get_ex_data(ssl, ContextImpl::sslExtendedSocketInfoIndex())), *cert, static_cast(SSL_get_app_data(ssl))); } -int ContextImpl::doVerifyCertChain( - X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, X509& leaf_cert, - const Network::TransportSocketOptions* transport_socket_options) { - if (verify_trusted_ca_) { - int ret = X509_verify_cert(store_ctx); - if (ssl_extended_info) { - ssl_extended_info->setCertificateValidationStatus( - ret == 1 ? Envoy::Ssl::ClientValidationStatus::Validated - : Envoy::Ssl::ClientValidationStatus::Failed); - } - - if (ret <= 0) { - stats_.fail_verify_error_.inc(); - return allow_untrusted_certificate_ ? 1 : ret; - } - } - - Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( - &leaf_cert, - transport_socket_options && - !transport_socket_options->verifySubjectAltNameListOverride().empty() - ? transport_socket_options->verifySubjectAltNameListOverride() - : verify_subject_alt_name_list_, - subject_alt_name_matchers_); - - if (ssl_extended_info) { - if (ssl_extended_info->certificateValidationStatus() == - Envoy::Ssl::ClientValidationStatus::NotValidated) { - ssl_extended_info->setCertificateValidationStatus(validated); - } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { - ssl_extended_info->setCertificateValidationStatus(validated); - } - } - - return allow_untrusted_certificate_ ? 1 - : (validated != Envoy::Ssl::ClientValidationStatus::Failed); -} - -Envoy::Ssl::ClientValidationStatus ContextImpl::verifyCertificate( - X509* cert, const std::vector& verify_san_list, - const std::vector& subject_alt_name_matchers) { - Envoy::Ssl::ClientValidationStatus validated = Envoy::Ssl::ClientValidationStatus::NotValidated; - - if (!verify_san_list.empty()) { - if (!verifySubjectAltName(cert, verify_san_list)) { - stats_.fail_verify_san_.inc(); - return Envoy::Ssl::ClientValidationStatus::Failed; - } - validated = Envoy::Ssl::ClientValidationStatus::Validated; - } - - if (!subject_alt_name_matchers.empty() && !matchSubjectAltName(cert, subject_alt_name_matchers)) { - stats_.fail_verify_san_.inc(); - return Envoy::Ssl::ClientValidationStatus::Failed; - } - - if (!verify_certificate_hash_list_.empty() || !verify_certificate_spki_list_.empty()) { - const bool valid_certificate_hash = - !verify_certificate_hash_list_.empty() && - verifyCertificateHashList(cert, verify_certificate_hash_list_); - const bool valid_certificate_spki = - !verify_certificate_spki_list_.empty() && - verifyCertificateSpkiList(cert, verify_certificate_spki_list_); - - if (!valid_certificate_hash && !valid_certificate_spki) { - stats_.fail_verify_cert_hash_.inc(); - return Envoy::Ssl::ClientValidationStatus::Failed; - } - - validated = Envoy::Ssl::ClientValidationStatus::Validated; - } - - return validated; -} - void ContextImpl::incCounter(const Stats::StatName name, absl::string_view value, const Stats::StatName fallback) const { Stats::Counter& counter = Stats::Utility::counterFromElements( @@ -653,108 +444,6 @@ std::vector ContextImpl::getPrivateKeyMe return providers; } -bool ContextImpl::matchSubjectAltName( - X509* cert, const std::vector& subject_alt_name_matchers) { - bssl::UniquePtr san_names( - static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); - if (san_names == nullptr) { - return false; - } - for (const GENERAL_NAME* general_name : san_names.get()) { - const std::string san = Utility::generalNameAsString(general_name); - for (auto& config_san_matcher : subject_alt_name_matchers) { - // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics. - if (general_name->type == GEN_DNS && - config_san_matcher.matcher().match_pattern_case() == - envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact - ? dnsNameMatch(config_san_matcher.matcher().exact(), absl::string_view(san)) - : config_san_matcher.match(san)) { - return true; - } - } - } - return false; -} - -bool ContextImpl::verifySubjectAltName(X509* cert, - const std::vector& subject_alt_names) { - bssl::UniquePtr san_names( - static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); - if (san_names == nullptr) { - return false; - } - for (const GENERAL_NAME* general_name : san_names.get()) { - const std::string san = Utility::generalNameAsString(general_name); - for (auto& config_san : subject_alt_names) { - if (general_name->type == GEN_DNS ? dnsNameMatch(config_san, san.c_str()) - : config_san == san) { - return true; - } - } - } - return false; -} - -bool ContextImpl::dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern) { - if (dns_name == pattern) { - return true; - } - - size_t pattern_len = pattern.length(); - if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') { - if (dns_name.length() > pattern_len - 1) { - const size_t off = dns_name.length() - pattern_len + 1; - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fix_wildcard_matching")) { - return dns_name.substr(0, off).find('.') == std::string::npos && - dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); - } else { - return dns_name.substr(off, pattern_len - 1) == pattern.substr(1, pattern_len - 1); - } - } - } - - return false; -} - -bool ContextImpl::verifyCertificateHashList( - X509* cert, const std::vector>& expected_hashes) { - std::vector computed_hash(SHA256_DIGEST_LENGTH); - unsigned int n; - X509_digest(cert, EVP_sha256(), computed_hash.data(), &n); - RELEASE_ASSERT(n == computed_hash.size(), ""); - - for (const auto& expected_hash : expected_hashes) { - if (computed_hash == expected_hash) { - return true; - } - } - return false; -} - -bool ContextImpl::verifyCertificateSpkiList( - X509* cert, const std::vector>& expected_hashes) { - X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(cert); - if (pubkey == nullptr) { - return false; - } - uint8_t* spki = nullptr; - const int len = i2d_X509_PUBKEY(pubkey, &spki); - if (len < 0) { - return false; - } - bssl::UniquePtr free_spki(spki); - - std::vector computed_hash(SHA256_DIGEST_LENGTH); - SHA256(spki, len, computed_hash.data()); - - for (const auto& expected_hash : expected_hashes) { - if (computed_hash == expected_hash) { - return true; - } - } - return false; -} - SslStats ContextImpl::generateStats(Stats::Scope& store) { std::string prefix("ssl."); return {ALL_SSL_STATS(POOL_COUNTER_PREFIX(store, prefix), POOL_GAUGE_PREFIX(store, prefix), @@ -762,7 +451,7 @@ SslStats ContextImpl::generateStats(Stats::Scope& store) { } size_t ContextImpl::daysUntilFirstCertExpires() const { - int daysUntilExpiration = Utility::getDaysUntilExpiration(ca_cert_.get(), time_source_); + int daysUntilExpiration = cert_validator_->daysUntilFirstCertExpires(); for (auto& cert : tls_context_.cert_contexts_) { daysUntilExpiration = std::min( Utility::getDaysUntilExpiration(cert.cert_chain_.get(), time_source_), daysUntilExpiration); @@ -787,10 +476,7 @@ absl::optional ContextImpl::secondsUntilFirstOcspResponseExpires() con } Envoy::Ssl::CertificateDetailsPtr ContextImpl::getCaCertInformation() const { - if (ca_cert_ == nullptr) { - return nullptr; - } - return certificateDetails(ca_cert_.get(), getCaFileName(), nullptr); + return cert_validator_->getCaCertInformation(); } std::vector ContextImpl::getCertChainInformation() const { @@ -799,51 +485,21 @@ std::vector ContextImpl::getCertChainInformat if (cert.cert_chain_ == nullptr) { continue; } - cert_details.emplace_back(certificateDetails(cert.cert_chain_.get(), cert.getCertChainFileName(), - cert.ocsp_response_.get())); - } + auto detail = Utility::certificateDetails(cert.cert_chain_.get(), cert.getCertChainFileName(), + time_source_); + auto ocsp_resp = cert.ocsp_response_.get(); + if (ocsp_resp) { + auto* ocsp_details = detail->mutable_ocsp_details(); + ProtobufWkt::Timestamp* valid_from = ocsp_details->mutable_valid_from(); + TimestampUtil::systemClockToTimestamp(ocsp_resp->getThisUpdate(), *valid_from); + ProtobufWkt::Timestamp* expiration = ocsp_details->mutable_expiration(); + TimestampUtil::systemClockToTimestamp(ocsp_resp->getNextUpdate(), *expiration); + } + cert_details.push_back(std::move(detail)); + } return cert_details; } -Envoy::Ssl::CertificateDetailsPtr -ContextImpl::certificateDetails(X509* cert, const std::string& path, - const Ocsp::OcspResponseWrapper* ocsp_response) const { - Envoy::Ssl::CertificateDetailsPtr certificate_details = - std::make_unique(); - certificate_details->set_path(path); - certificate_details->set_serial_number(Utility::getSerialNumberFromCertificate(*cert)); - certificate_details->set_days_until_expiration( - Utility::getDaysUntilExpiration(cert, time_source_)); - if (ocsp_response) { - auto* ocsp_details = certificate_details->mutable_ocsp_details(); - ProtobufWkt::Timestamp* valid_from = ocsp_details->mutable_valid_from(); - TimestampUtil::systemClockToTimestamp(ocsp_response->getThisUpdate(), *valid_from); - ProtobufWkt::Timestamp* expiration = ocsp_details->mutable_expiration(); - TimestampUtil::systemClockToTimestamp(ocsp_response->getNextUpdate(), *expiration); - } - ProtobufWkt::Timestamp* valid_from = certificate_details->mutable_valid_from(); - TimestampUtil::systemClockToTimestamp(Utility::getValidFrom(*cert), *valid_from); - ProtobufWkt::Timestamp* expiration_time = certificate_details->mutable_expiration_time(); - TimestampUtil::systemClockToTimestamp(Utility::getExpirationTime(*cert), *expiration_time); - - for (auto& dns_san : Utility::getSubjectAltNames(*cert, GEN_DNS)) { - envoy::admin::v3::SubjectAlternateName& subject_alt_name = - *certificate_details->add_subject_alt_names(); - subject_alt_name.set_dns(dns_san); - } - for (auto& uri_san : Utility::getSubjectAltNames(*cert, GEN_URI)) { - envoy::admin::v3::SubjectAlternateName& subject_alt_name = - *certificate_details->add_subject_alt_names(); - subject_alt_name.set_uri(uri_san); - } - for (auto& ip_san : Utility::getSubjectAltNames(*cert, GEN_IPADD)) { - envoy::admin::v3::SubjectAlternateName& subject_alt_name = - *certificate_details->add_subject_alt_names(); - subject_alt_name.set_ip_address(ip_san); - } - return certificate_details; -} - ClientContextImpl::ClientContextImpl(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, TimeSource& time_source) @@ -1005,11 +661,9 @@ ServerContextImpl::ServerContextImpl(Stats::Scope& scope, const SessionContextID session_id = generateHashForSessionContextId(server_names); const auto tls_certificates = config.tlsCertificates(); - if (!config.capabilities().verifies_peer_certificates && - config.certificateValidationContext() != nullptr && - !config.certificateValidationContext()->caCert().empty()) { - tls_context_.addClientValidationContext(*config.certificateValidationContext(), - config.requireClientCertificate()); + if (!config.capabilities().verifies_peer_certificates) { + cert_validator_->addClientValidationContext(tls_context_.ssl_ctx_.get(), + config.requireClientCertificate()); } if (!parsed_alpn_protocols_.empty() && !config.capabilities().handles_alpn_selection) { @@ -1085,12 +739,11 @@ ServerContextImpl::ServerContextImpl(Stats::Scope& scope, ServerContextImpl::SessionContextID ServerContextImpl::generateHashForSessionContextId(const std::vector& server_names) { uint8_t hash_buffer[EVP_MAX_MD_SIZE]; - unsigned hash_length; + unsigned hash_length = 0; - // md is released before we return from the method - EVP_MD_CTX* md = Envoy::Extensions::TransportSockets::Tls::newEVP_MD_CTX(); + bssl::ScopedEVP_MD_CTX md; - int rc = EVP_DigestInit(md, EVP_sha256()); + int rc = EVP_DigestInit(md.get(), EVP_sha256()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); // Hash the CommonName/SANs of all the server certificates. This makes sure that sessions can only @@ -1114,7 +767,7 @@ ServerContextImpl::generateHashForSessionContextId(const std::vectortype) { case GEN_IPADD: - rc = EVP_DigestUpdate(md, san->d.iPAddress->data, san->d.iPAddress->length); + rc = EVP_DigestUpdate(md.get(), san->d.iPAddress->data, san->d.iPAddress->length); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); ++san_count; break; case GEN_DNS: - rc = EVP_DigestUpdate(md, ASN1_STRING_data(san->d.dNSName), + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.dNSName), ASN1_STRING_length(san->d.dNSName)); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); ++san_count; break; case GEN_URI: - rc = EVP_DigestUpdate(md, ASN1_STRING_data(san->d.uniformResourceIdentifier), + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.uniformResourceIdentifier), ASN1_STRING_length(san->d.uniformResourceIdentifier)); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); ++san_count; @@ -1157,48 +810,16 @@ ServerContextImpl::generateHashForSessionContextId(const std::vector::type::value_type)); + rc = EVP_DigestUpdate(md.get(), hash_buffer, hash_length); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); } - for (const auto& hash : verify_certificate_spki_list_) { - rc = EVP_DigestUpdate(md, hash.data(), - hash.size() * - sizeof(std::remove_reference::type::value_type)); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - } + cert_validator_->updateDigestForSessionId(md, hash_buffer, hash_length); // Hash configured SNIs for this context, so that sessions cannot be resumed across different // filter chains, even when using the same server certificate. for (const auto& name : server_names) { - rc = EVP_DigestUpdate(md, name.data(), name.size()); + rc = EVP_DigestUpdate(md.get(), name.data(), name.size()); RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); } @@ -1209,12 +830,11 @@ ServerContextImpl::generateHashForSessionContextId(const std::vectorsecond.get(); } -OcspStapleAction ServerContextImpl::ocspStapleAction(const ContextImpl::CertContext& cert_context, +OcspStapleAction ServerContextImpl::ocspStapleAction(const CertContext& cert_context, bool client_ocsp_capable) { if (!client_ocsp_capable) { return OcspStapleAction::ClientNotCapable; @@ -1373,52 +993,6 @@ int ServerContextImpl::handleOcspStapling(SSL* ssl, void*) { return SSL_TLSEXT_ERR_OK; } -void ServerContextImpl::TlsContext::addClientValidationContext( - const Envoy::Ssl::CertificateValidationContextConfig& config, bool require_client_cert) { - bssl::UniquePtr bio( - BIO_new_mem_buf(const_cast(config.caCert().data()), config.caCert().size())); - RELEASE_ASSERT(bio != nullptr, ""); - // Based on BoringSSL's SSL_add_file_cert_subjects_to_stack(). - bssl::UniquePtr list(sk_X509_NAME_new( - [](const X509_NAME* const* a, const X509_NAME* const* b) -> int { return X509_NAME_cmp(*a, *b); })); - RELEASE_ASSERT(list != nullptr, ""); - for (;;) { - bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); - if (cert == nullptr) { - break; - } - X509_NAME* name = X509_get_subject_name(cert.get()); - if (name == nullptr) { - throw EnvoyException( - absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); - } - // Check for duplicates. - if (sk_X509_NAME_find(list.get(), nullptr, name)) { - continue; - } - bssl::UniquePtr name_dup(X509_NAME_dup(name)); - if (name_dup == nullptr || !sk_X509_NAME_push(list.get(), name_dup.release())) { - throw EnvoyException( - absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); - } - } - // Check for EOF. - const uint32_t err = ERR_peek_last_error(); - if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { - ERR_clear_error(); - } else { - throw EnvoyException( - absl::StrCat("Failed to load trusted client CA certificates from ", config.caCertPath())); - } - if (sk_X509_NAME_num(list.get()) > 0) - SSL_CTX_set_client_CA_list(ssl_ctx_.get(), list.release()); - - // SSL_VERIFY_PEER or stronger mode was already set in ContextImpl::ContextImpl(). - if (require_client_cert) { - SSL_CTX_set_verify(ssl_ctx_.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); - } -} - bool ContextImpl::verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediates, std::string& error_details) { bssl::UniquePtr ctx(X509_STORE_CTX_new()); @@ -1430,7 +1004,7 @@ bool ContextImpl::verifyCertChain(X509& leaf_cert, STACK_OF(X509) & intermediate return false; } - int res = doVerifyCertChain(ctx.get(), nullptr, leaf_cert, nullptr); + int res = cert_validator_->doVerifyCertChain(ctx.get(), nullptr, leaf_cert, nullptr); if (res <= 0) { const int n = X509_STORE_CTX_get_error(ctx.get()); const int depth = X509_STORE_CTX_get_error_depth(ctx.get()); diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index bc1961e991..efcc2cb842 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -17,8 +17,10 @@ #include "common/common/matchers.h" #include "common/stats/symbol_table_impl.h" +#include "extensions/transport_sockets/tls/cert_validator/cert_validator.h" #include "extensions/transport_sockets/tls/context_manager_impl.h" #include "extensions/transport_sockets/tls/ocsp/ocsp.h" +#include "extensions/transport_sockets/tls/stats.h" #include "extensions/transport_sockets/tls/openssl_impl.h" #include "absl/synchronization/mutex.h" @@ -33,25 +35,30 @@ namespace Extensions { namespace TransportSockets { namespace Tls { -#define ALL_SSL_STATS(COUNTER, GAUGE, HISTOGRAM) \ - COUNTER(connection_error) \ - COUNTER(handshake) \ - COUNTER(session_reused) \ - COUNTER(no_certificate) \ - COUNTER(fail_verify_no_cert) \ - COUNTER(fail_verify_error) \ - COUNTER(fail_verify_san) \ - COUNTER(fail_verify_cert_hash) \ - COUNTER(ocsp_staple_failed) \ - COUNTER(ocsp_staple_omitted) \ - COUNTER(ocsp_staple_responses) \ - COUNTER(ocsp_staple_requests) - -/** - * Wrapper struct for SSL stats. @see stats_macros.h - */ -struct SslStats { - ALL_SSL_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) +struct CertContext { + bssl::UniquePtr cert_chain_; + std::string cert_chain_file_path_; + Ocsp::OcspResponseWrapperPtr ocsp_response_; + bool is_ecdsa_{}; + bool is_must_staple_{}; + std::string getCertChainFileName() const { return cert_chain_file_path_; }; + Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { + return private_key_method_provider_; + } +}; + +// Use a single context for certificates instead of one context per certificate as in the BoringSSL case. +// A single context is required to hold all certificates for OpenSSL, certificate selection is handled by OpenSSL. +struct TlsContext { + bssl::UniquePtr ssl_ctx_; + std::vector cert_contexts_; + // a map of cert hashes as calculated by X509_digest with EVP_sha1 to cert contexts + absl::flat_hash_map> cert_context_lookup_; + + void addClientValidationContext(const Envoy::Ssl::CertificateValidationContextConfig& config, + bool require_client_cert); + bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); }; class ContextImpl : public virtual Envoy::Ssl::Context { @@ -64,33 +71,6 @@ class ContextImpl : public virtual Envoy::Ssl::Context { */ void logHandshake(SSL* ssl) const; - /** - * Performs subjectAltName verification - * @param ssl the certificate to verify - * @param subject_alt_names the configured subject_alt_names to match - * @return true if the verification succeeds - */ - static bool verifySubjectAltName(X509* cert, const std::vector& subject_alt_names); - - /** - * Performs subjectAltName matching with the provided matchers. - * @param ssl the certificate to verify - * @param subject_alt_name_matchers the configured matchers to match - * @return true if the verification succeeds - */ - static bool - matchSubjectAltName(X509* cert, - const std::vector& subject_alt_name_matchers); - - /** - * Determines whether the given name matches 'pattern' which may optionally begin with a wildcard. - * NOTE: public for testing - * @param dns_name the DNS name to match - * @param pattern the pattern to match against (*.example.com) - * @return true if the san matches pattern - */ - static bool dnsNameMatch(const absl::string_view dns_name, const absl::string_view pattern); - SslStats& stats() { return stats_; } /** @@ -118,94 +98,22 @@ class ContextImpl : public virtual Envoy::Ssl::Context { */ static int sslContextIndex(); - // A X509_STORE_CTX_verify_cb callback for ignoring cert expiration in X509_verify_cert(). - static int ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx); - // A SSL_CTX_set_cert_verify_callback for custom cert validation. static int verifyCallback(X509_STORE_CTX* store_ctx, void* arg); - // Called by verifyCallback to do the actual cert chain verification. - int doVerifyCertChain(X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, - X509& leaf_cert, - const Network::TransportSocketOptions* transport_socket_options); - - Envoy::Ssl::ClientValidationStatus - verifyCertificate(X509* cert, const std::vector& verify_san_list, - const std::vector& subject_alt_name_matchers); - - /** - * Verifies certificate hash for pinning. The hash is a hex-encoded SHA-256 of the DER-encoded - * certificate. - * - * @param ssl the certificate to verify - * @param expected_hashes the configured list of certificate hashes to match - * @return true if the verification succeeds - */ - static bool verifyCertificateHashList(X509* cert, - const std::vector>& expected_hashes); - - /** - * Verifies certificate hash for pinning. The hash is a base64-encoded SHA-256 of the DER-encoded - * Subject Public Key Information (SPKI) of the certificate. - * - * @param ssl the certificate to verify - * @param expected_hashes the configured list of certificate hashes to match - * @return true if the verification succeeds - */ - static bool verifyCertificateSpkiList(X509* cert, - const std::vector>& expected_hashes); - bool parseAndSetAlpn(const std::vector& alpn, SSL& ssl); std::vector parseAlpnProtocols(const std::string& alpn_protocols); static SslStats generateStats(Stats::Scope& scope); - std::string getCaFileName() const { return ca_file_path_; }; void incCounter(const Stats::StatName name, absl::string_view value, const Stats::StatName fallback) const; - Envoy::Ssl::CertificateDetailsPtr - certificateDetails(X509* cert, const std::string& path, - const Ocsp::OcspResponseWrapper* ocsp_response) const; - - struct CertContext { - bssl::UniquePtr cert_chain_; - std::string cert_chain_file_path_; - Ocsp::OcspResponseWrapperPtr ocsp_response_; - bool is_ecdsa_{}; - bool is_must_staple_{}; - std::string getCertChainFileName() const { return cert_chain_file_path_; }; - Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; - Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { - return private_key_method_provider_; - } - }; - - // Use a single context for certificates instead of one context per certificate as in the BoringSSL case. - // A single context is required to hold all certificates for OpenSSL, certificate selection is handled by OpenSSL. - struct TlsContext { - bssl::UniquePtr ssl_ctx_; - std::vector cert_contexts_; - // a map of cert hashes as calculated by X509_digest with EVP_sha1 to cert contexts - absl::flat_hash_map> cert_context_lookup_; - - void addClientValidationContext(const Envoy::Ssl::CertificateValidationContextConfig& config, - bool require_client_cert); - bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); - }; - TlsContext tls_context_; - bool verify_trusted_ca_{false}; - std::vector verify_subject_alt_name_list_; - std::vector subject_alt_name_matchers_; - std::vector> verify_certificate_hash_list_; - std::vector> verify_certificate_spki_list_; - bool allow_untrusted_certificate_{false}; + CertValidatorPtr cert_validator_; Stats::Scope& scope_; SslStats stats_; std::vector parsed_alpn_protocols_; - bssl::UniquePtr ca_cert_; bssl::UniquePtr cert_chain_; - std::string ca_file_path_; std::string cert_chain_file_path_; TimeSource& time_source_; const unsigned tls_max_version_; @@ -260,9 +168,8 @@ class ServerContextImpl : public ContextImpl, public Envoy::Ssl::ServerContext { bool isClientOcspCapable(SSL* ssl); // returns a reference to a CertContext created in ContextImpl ctor // matching cert SHA1 digest - const ContextImpl::CertContext& certificateContext(X509* cert); - OcspStapleAction ocspStapleAction(const ContextImpl::CertContext& cert_context, - bool client_ocsp_capable); + const CertContext& certificateContext(X509* cert); + OcspStapleAction ocspStapleAction(const CertContext& cert_context, bool client_ocsp_capable); int handleOcspStapling(SSL* ssl, void*); SessionContextID generateHashForSessionContextId(const std::vector& server_names); diff --git a/source/extensions/transport_sockets/tls/openssl_impl.cc b/source/extensions/transport_sockets/tls/openssl_impl.cc index 81cb5d3ac1..1fe1cbb58b 100644 --- a/source/extensions/transport_sockets/tls/openssl_impl.cc +++ b/source/extensions/transport_sockets/tls/openssl_impl.cc @@ -97,11 +97,6 @@ void allowRenegotiation(SSL* ssl) { // SSL_set_renegotiate_mode(ssl, mode); } -EVP_MD_CTX* newEVP_MD_CTX() { - EVP_MD_CTX* md(EVP_MD_CTX_new()); - return md; -} - SSL_SESSION* ssl_session_from_bytes(SSL* client_ssl_socket, const SSL_CTX* client_ssl_context, const std::string& client_session) { SSL_SESSION* client_ssl_session = SSL_get_session(client_ssl_socket); diff --git a/source/extensions/transport_sockets/tls/openssl_impl.h b/source/extensions/transport_sockets/tls/openssl_impl.h index 0ad97b3720..2c083d67c5 100644 --- a/source/extensions/transport_sockets/tls/openssl_impl.h +++ b/source/extensions/transport_sockets/tls/openssl_impl.h @@ -30,8 +30,6 @@ STACK_OF(X509)* SSL_get_peer_full_cert_chain(const SSL *ssl); void allowRenegotiation(SSL* ssl); -EVP_MD_CTX* newEVP_MD_CTX(); - SSL_SESSION* ssl_session_from_bytes(SSL* client_ssl_socket, const SSL_CTX* client_ssl_context, const std::string& client_session); diff --git a/source/extensions/transport_sockets/tls/stats.cc b/source/extensions/transport_sockets/tls/stats.cc new file mode 100644 index 0000000000..f162e9db75 --- /dev/null +++ b/source/extensions/transport_sockets/tls/stats.cc @@ -0,0 +1,20 @@ +#include "extensions/transport_sockets/tls/stats.h" + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +SslStats generateSslStats(Stats::Scope& store) { + std::string prefix("ssl."); + return {ALL_SSL_STATS(POOL_COUNTER_PREFIX(store, prefix), POOL_GAUGE_PREFIX(store, prefix), + POOL_HISTOGRAM_PREFIX(store, prefix))}; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/stats.h b/source/extensions/transport_sockets/tls/stats.h new file mode 100644 index 0000000000..05023af677 --- /dev/null +++ b/source/extensions/transport_sockets/tls/stats.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +#define ALL_SSL_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(connection_error) \ + COUNTER(handshake) \ + COUNTER(session_reused) \ + COUNTER(no_certificate) \ + COUNTER(fail_verify_no_cert) \ + COUNTER(fail_verify_error) \ + COUNTER(fail_verify_san) \ + COUNTER(fail_verify_cert_hash) \ + COUNTER(ocsp_staple_failed) \ + COUNTER(ocsp_staple_omitted) \ + COUNTER(ocsp_staple_responses) \ + COUNTER(ocsp_staple_requests) + +/** + * Wrapper struct for SSL stats. @see stats_macros.h + */ +struct SslStats { + ALL_SSL_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) +}; + +SslStats generateSslStats(Stats::Scope& store); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/utility.cc b/source/extensions/transport_sockets/tls/utility.cc index fd68e483ed..8cf1011407 100644 --- a/source/extensions/transport_sockets/tls/utility.cc +++ b/source/extensions/transport_sockets/tls/utility.cc @@ -3,6 +3,7 @@ #include "openssl/err.h" #include "common/common/assert.h" #include "common/network/address_impl.h" +#include "common/protobuf/utility.h" #include "absl/strings/str_join.h" #include "openssl/x509v3.h" @@ -12,6 +13,38 @@ namespace Extensions { namespace TransportSockets { namespace Tls { +Envoy::Ssl::CertificateDetailsPtr Utility::certificateDetails(X509* cert, const std::string& path, + TimeSource& time_source) { + Envoy::Ssl::CertificateDetailsPtr certificate_details = + std::make_unique(); + certificate_details->set_path(path); + certificate_details->set_serial_number(Utility::getSerialNumberFromCertificate(*cert)); + certificate_details->set_days_until_expiration( + Utility::getDaysUntilExpiration(cert, time_source)); + + ProtobufWkt::Timestamp* valid_from = certificate_details->mutable_valid_from(); + TimestampUtil::systemClockToTimestamp(Utility::getValidFrom(*cert), *valid_from); + ProtobufWkt::Timestamp* expiration_time = certificate_details->mutable_expiration_time(); + TimestampUtil::systemClockToTimestamp(Utility::getExpirationTime(*cert), *expiration_time); + + for (auto& dns_san : Utility::getSubjectAltNames(*cert, GEN_DNS)) { + envoy::admin::v3::SubjectAlternateName& subject_alt_name = + *certificate_details->add_subject_alt_names(); + subject_alt_name.set_dns(dns_san); + } + for (auto& uri_san : Utility::getSubjectAltNames(*cert, GEN_URI)) { + envoy::admin::v3::SubjectAlternateName& subject_alt_name = + *certificate_details->add_subject_alt_names(); + subject_alt_name.set_uri(uri_san); + } + for (auto& ip_san : Utility::getSubjectAltNames(*cert, GEN_IPADD)) { + envoy::admin::v3::SubjectAlternateName& subject_alt_name = + *certificate_details->add_subject_alt_names(); + subject_alt_name.set_ip_address(ip_san); + } + return certificate_details; +} + namespace { enum class CertName { Issuer, Subject }; @@ -110,6 +143,11 @@ std::string Utility::generalNameAsString(const GENERAL_NAME* general_name) { san.assign(reinterpret_cast(ASN1_STRING_data(str)), ASN1_STRING_length(str)); break; } + case GEN_EMAIL: { + ASN1_STRING* str = general_name->d.rfc822Name; + san.assign(reinterpret_cast(ASN1_STRING_data(str)), ASN1_STRING_length(str)); + break; + } case GEN_IPADD: { if (general_name->d.ip->length == 4) { sockaddr_in sin; diff --git a/source/extensions/transport_sockets/tls/utility.h b/source/extensions/transport_sockets/tls/utility.h index 531ae02a3a..31d5f92e47 100644 --- a/source/extensions/transport_sockets/tls/utility.h +++ b/source/extensions/transport_sockets/tls/utility.h @@ -3,6 +3,8 @@ #include #include +#include "envoy/ssl/context.h" + #include "common/common/utility.h" #include "absl/types/optional.h" @@ -18,6 +20,9 @@ namespace TransportSockets { namespace Tls { namespace Utility { +Envoy::Ssl::CertificateDetailsPtr certificateDetails(X509* cert, const std::string& path, + TimeSource& time_source); + /** * Retrieves the serial number of a certificate. * @param cert the certificate diff --git a/test/config/utility.cc b/test/config/utility.cc index b248c80d56..4eda66a9a2 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -955,10 +955,15 @@ void ConfigHelper::initializeTls( common_tls_context.add_alpn_protocols(Http::Utility::AlpnNames::get().Http11); auto* validation_context = common_tls_context.mutable_validation_context(); - validation_context->mutable_trusted_ca()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); - validation_context->add_verify_certificate_hash( - options.expect_client_ecdsa_cert_ ? TEST_CLIENT_ECDSA_CERT_HASH : TEST_CLIENT_CERT_HASH); + if (options.custom_validator_config_) { + validation_context->set_allocated_custom_validator_config(options.custom_validator_config_); + } else { + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash( + options.expect_client_ecdsa_cert_ ? TEST_CLIENT_ECDSA_CERT_HASH : TEST_CLIENT_CERT_HASH); + } + validation_context->set_allow_expired_certificate(options.allow_expired_certificate_); // We'll negotiate up to TLSv1.3 for the tests that care, but it really // depends on what the client sets. @@ -987,6 +992,10 @@ void ConfigHelper::initializeTls( "test/config/integration/certs/server_ecdsa_ocsp_resp.der")); } } + if (!options.san_matchers_.empty()) { + *validation_context->mutable_match_subject_alt_names() = {options.san_matchers_.begin(), + options.san_matchers_.end()}; + } } void ConfigHelper::renameListener(const std::string& name) { diff --git a/test/config/utility.h b/test/config/utility.h index af87144d03..173deee1dc 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -31,6 +31,11 @@ class ConfigHelper { using HttpConnectionManager = envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager; struct ServerSslOptions { + ServerSslOptions& setAllowExpiredCertificate(bool allow) { + allow_expired_certificate_ = allow; + return *this; + } + ServerSslOptions& setRsaCert(bool rsa_cert) { rsa_cert_ = rsa_cert; return *this; @@ -66,6 +71,20 @@ class ConfigHelper { return *this; } + ServerSslOptions& setCustomValidatorConfig( + envoy::config::core::v3::TypedExtensionConfig* custom_validator_config) { + custom_validator_config_ = custom_validator_config; + return *this; + } + + ServerSslOptions& + setSanMatchers(std::vector san_matchers) { + san_matchers_ = san_matchers; + return *this; + } + + bool allow_expired_certificate_{}; + envoy::config::core::v3::TypedExtensionConfig* custom_validator_config_; bool rsa_cert_{true}; bool rsa_cert_ocsp_staple_{true}; bool ecdsa_cert_{false}; @@ -73,6 +92,7 @@ class ConfigHelper { bool ocsp_staple_required_{false}; bool tlsv1_3_{false}; bool expect_client_ecdsa_cert_{false}; + std::vector san_matchers_{}; }; // Set up basic config, using the specified IpVersion for all connections: listeners, upstream, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index cbf66f511f..7993c776c8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -70,6 +70,8 @@ class TestGetProofCallback : public quic::ProofSource::Callback { .WillByDefault(ReturnRef(empty_string_list)); ON_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) .WillByDefault(ReturnRef(empty_string_list)); + const absl::optional nullopt = absl::nullopt; + ON_CALL(cert_validation_ctx_config_, customValidatorConfig()).WillByDefault(ReturnRef(nullopt)); verifier_ = std::make_unique(store_, client_context_config_, time_system_); } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 4a1dfe144d..3f5212b095 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -65,6 +65,8 @@ class EnvoyQuicProofVerifierTest : public testing::Test { .WillRepeatedly(ReturnRef(empty_string_list_)); EXPECT_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList()) .WillRepeatedly(ReturnRef(empty_string_list_)); + EXPECT_CALL(cert_validation_ctx_config_, customValidatorConfig()) + .WillRepeatedly(ReturnRef(custom_validator_config_)); verifier_ = std::make_unique(store_, client_context_config_, time_system_); } @@ -79,6 +81,8 @@ class EnvoyQuicProofVerifierTest : public testing::Test { const std::string cert_chain_{quic::test::kTestCertificateChainPem}; const std::string root_ca_cert_; const std::string leaf_cert_; + const absl::optional custom_validator_config_{ + absl::nullopt}; NiceMock store_; Event::GlobalTimeSystem time_system_; NiceMock client_context_config_; diff --git a/test/extensions/transport_sockets/tls/cert_validator/BUILD b/test/extensions/transport_sockets/tls/cert_validator/BUILD new file mode 100644 index 0000000000..b16c619783 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/BUILD @@ -0,0 +1,59 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_cc_test_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "default_validator_test", + srcs = [ + "default_validator_test.cc", + ], + data = [ + "//test/extensions/transport_sockets/tls/test_data:certs", + ], + deps = [ + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", + "//test/extensions/transport_sockets/tls:ssl_test_utils", + "//test/extensions/transport_sockets/tls/cert_validator:test_common", + "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", + ], +) + +envoy_cc_test( + name = "factory_test", + srcs = [ + "factory_test.cc", + ], + deps = [ + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", + "//test/extensions/transport_sockets/tls/cert_validator:test_common", + ], +) + +envoy_cc_test( + name = "utility_test", + srcs = [ + "utility_test.cc", + ], + deps = [ + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", + ], +) + +envoy_cc_test_library( + name = "test_common", + hdrs = ["test_common.h"], + deps = [ + "//include/envoy/ssl:context_config_interface", + "//include/envoy/ssl:ssl_socket_extended_info_interface", + "//source/common/common:macros", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc new file mode 100644 index 0000000000..2edbe80b49 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc @@ -0,0 +1,163 @@ +#include +#include + +#include "extensions/transport_sockets/tls/cert_validator/default_validator.h" + +#include "test/extensions/transport_sockets/tls/ssl_test_utility.h" +#include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +TEST(DefaultCertValidatorTest, TestDnsNameMatching) { + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("lyft.com", "lyft.com")); + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("a.lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("foo.test.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "")); +} + +TEST(DefaultCertValidatorTest, TestDnsNameMatchingLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("lyft.com", "lyft.com")); + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("a.lyft.com", "*.lyft.com")); + // Legacy behavior + EXPECT_TRUE(DefaultCertValidator::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("foo.test.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*.lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("alyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("", "*lyft.com")); + EXPECT_FALSE(DefaultCertValidator::dnsNameMatch("lyft.com", "")); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameDNSMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"server1.example.com", + "server2.example.com"}; + EXPECT_TRUE(DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameDNSMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_hidden_envoy_deprecated_regex(".*.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameWildcardDNSMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("api.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMultiLevelMatch) { + // san_multiple_dns_cert matches *.example.com + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("foo.api.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestMultiLevelMatchLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact("foo.api.example.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameURIMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + std::vector verify_subject_alt_name_list = {"spiffe://lyft.com/fake-team", + "spiffe://lyft.com/test-team"}; + EXPECT_TRUE(DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltMultiDomain) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; + EXPECT_FALSE( + DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltMultiDomainLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; + EXPECT_TRUE(DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameURIMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_hidden_envoy_deprecated_regex("spiffe://lyft.com/.*-team"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameNotMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + std::vector verify_subject_alt_name_list = {"foo", "bar"}; + EXPECT_FALSE( + DefaultCertValidator::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); +} + +TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameNotMatched) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_hidden_envoy_deprecated_regex(".*.foo.com"); + std::vector subject_alt_name_matchers; + subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); + EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/cert_validator/factory_test.cc b/test/extensions/transport_sockets/tls/cert_validator/factory_test.cc new file mode 100644 index 0000000000..c06317b92f --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/factory_test.cc @@ -0,0 +1,28 @@ +#include + +#include "extensions/transport_sockets/tls/cert_validator/factory.h" + +#include "test/extensions/transport_sockets/tls/cert_validator/test_common.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +TEST(FactoryTest, TestGetCertValidatorName) { + EXPECT_EQ("envoy.tls.cert_validator.default", getCertValidatorName(nullptr)); + auto config = std::make_unique(); + EXPECT_EQ("envoy.tls.cert_validator.default", getCertValidatorName(config.get())); + + envoy::config::core::v3::TypedExtensionConfig custom_config = {}; + custom_config.set_name("envoy.tls.cert_validator.spiffe"); + config = std::make_unique(custom_config); + EXPECT_EQ(custom_config.name(), getCertValidatorName(config.get())); +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD b/test/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD new file mode 100644 index 0000000000..30d18f86c4 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD @@ -0,0 +1,49 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "spiffe_validator_test", + srcs = [ + "spiffe_validator_test.cc", + ], + data = [ + "//test/extensions/transport_sockets/tls/test_data:certs", + ], + extension_name = "envoy.tls.cert_validator.spiffe", + deps = [ + "//source/extensions/transport_sockets/tls/cert_validator/spiffe:config", + "//test/extensions/transport_sockets/tls:ssl_test_utils", + "//test/extensions/transport_sockets/tls/cert_validator:test_common", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_runtime_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "spiffe_validator_integration_test", + srcs = [ + "spiffe_validator_integration_test.cc", + "spiffe_validator_integration_test.h", + ], + data = [ + "//test/config/integration/certs", + "//test/extensions/transport_sockets/tls/test_data:certs", + ], + extension_name = "envoy.tls.cert_validator.spiffe", + deps = [ + "//source/extensions/transport_sockets/tls/cert_validator/spiffe:config", + "//test/integration:http_integration_lib", + ], +) diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc new file mode 100644 index 0000000000..caacf4ba6b --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc @@ -0,0 +1,258 @@ +#include "spiffe_validator_integration_test.h" + +#include + +#include "extensions/transport_sockets/tls/context_manager_impl.h" + +#include "test/integration/integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Ssl { + +void SslSPIFFECertValidatorIntegrationTest::initialize() { + config_helper_.addSslConfig(ConfigHelper::ServerSslOptions() + .setRsaCert(true) + .setTlsV13(true) + .setRsaCertOcspStaple(false) + .setCustomValidatorConfig(custom_validator_config_) + .setSanMatchers(san_matchers_) + .setAllowExpiredCertificate(allow_expired_cert_)); + HttpIntegrationTest::initialize(); + + context_manager_ = + std::make_unique(timeSystem()); + registerTestServerPorts({"http"}); +} + +void SslSPIFFECertValidatorIntegrationTest::TearDown() { + HttpIntegrationTest::cleanupUpstreamAndDownstream(); + codec_client_.reset(); + context_manager_.reset(); +} + +Network::ClientConnectionPtr SslSPIFFECertValidatorIntegrationTest::makeSslClientConnection( + const ClientSslTransportOptions& options, bool use_expired = false) { + ClientSslTransportOptions modified_options{options}; + modified_options.setTlsVersion(tls_version_); + modified_options.use_expired_spiffe_cert_ = use_expired; + + Network::Address::InstanceConstSharedPtr address = getSslAddress(version_, lookupPort("http")); + auto client_transport_socket_factory_ptr = + createClientSslTransportSocketFactory(modified_options, *context_manager_, *api_); + return dispatcher_->createClientConnection( + address, Network::Address::InstanceConstSharedPtr(), + client_transport_socket_factory_ptr->createTransportSocket({}), nullptr); +} + +void SslSPIFFECertValidatorIntegrationTest::checkVerifyErrorCouter(uint64_t value) { + Stats::CounterSharedPtr counter = + test_server_->counter(listenerStatPrefix("ssl.fail_verify_error")); + EXPECT_EQ(value, counter->value()); + counter->reset(); +} + +INSTANTIATE_TEST_SUITE_P( + IpVersionsClientVersions, SslSPIFFECertValidatorIntegrationTest, + testing::Combine( + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + testing::Values(envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2, + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3)), + SslSPIFFECertValidatorIntegrationTest::ipClientVersionTestParamsToString); + +// clientcert.pem's san is "spiffe://lyft.com/frontend-team" so it should be accepted. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorAccepted) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" + )EOF"), + *typed_conf); + + custom_validator_config_ = typed_conf; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection({}); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator); + checkVerifyErrorCouter(0); +} + +// Client certificate has expired but the config allows expired certificates, so this case should +// be accepted. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorExpiredButAccepcepted) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF"), + *typed_conf); + custom_validator_config_ = typed_conf; + allow_expired_cert_ = true; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + const bool use_expired_certificate = true; + return makeSslClientConnection({}, use_expired_certificate); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator); + checkVerifyErrorCouter(0); +} + +// clientcert.pem has "spiffe://lyft.com/frontend-team" URI SAN, so this case should be accepted. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorSANMatch) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" + )EOF"), + *typed_conf); + custom_validator_config_ = typed_conf; + + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_prefix("spiffe://lyft.com/"); + san_matchers_ = {matcher}; + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection({}); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator); + checkVerifyErrorCouter(0); +} + +// clientcert.pem has "spiffe://lyft.com/frontend-team" URI SAN, so this case should be rejected. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorSANNotMatch) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" + )EOF"), + *typed_conf); + custom_validator_config_ = typed_conf; + + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_prefix("spiffe://example.com/"); + // The cert has "DNS.1 = lyft.com" but SPIFFE validator must ignore SAN types other than URI. + matcher.set_prefix("www.lyft.com"); + san_matchers_ = {matcher}; + initialize(); + auto conn = makeSslClientConnection({}); + if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2) { + auto codec = makeRawHttpConnection(std::move(conn), absl::nullopt); + EXPECT_FALSE(codec->connected()); + } else { + auto codec = makeHttpConnection(std::move(conn)); + ASSERT_TRUE(codec->waitForDisconnect()); + codec->close(); + } + checkVerifyErrorCouter(1); +} + +// Client certificate has expired and the config does NOT allow expired certificates, so this case +// should be rejected. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorExpiredAndRejected) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF"), + *typed_conf); + custom_validator_config_ = typed_conf; + // Explicitly specify "false" just in case and for clarity. + allow_expired_cert_ = false; + initialize(); + auto conn = makeSslClientConnection({}); + if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2) { + auto codec = makeRawHttpConnection(std::move(conn), absl::nullopt); + EXPECT_FALSE(codec->connected()); + } else { + auto codec = makeHttpConnection(std::move(conn)); + ASSERT_TRUE(codec->waitForDisconnect()); + codec->close(); + } + checkVerifyErrorCouter(1); +} + +// clientcert.pem's san is "spiffe://lyft.com/frontend-team" so it should be rejected. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorRejected1) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" + )EOF"), + *typed_conf); + custom_validator_config_ = typed_conf; + initialize(); + auto conn = makeSslClientConnection({}); + if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2) { + auto codec = makeRawHttpConnection(std::move(conn), absl::nullopt); + EXPECT_FALSE(codec->connected()); + } else { + auto codec = makeHttpConnection(std::move(conn)); + ASSERT_TRUE(codec->waitForDisconnect()); + codec->close(); + } + checkVerifyErrorCouter(1); +} + +// clientcert.pem's san is "spiffe://lyft.com/frontend-team" but the corresponding trust bundle does +// not match with the client cert. So this should also be rejected. +TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorRejected2) { + auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig(); + TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/fake_ca_cert.pem" + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" + )EOF"), + *typed_conf); + custom_validator_config_ = typed_conf; + initialize(); + auto conn = makeSslClientConnection({}); + if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2) { + auto codec = makeRawHttpConnection(std::move(conn), absl::nullopt); + EXPECT_FALSE(codec->connected()); + } else { + auto codec = makeHttpConnection(std::move(conn)); + ASSERT_TRUE(codec->waitForDisconnect()); + codec->close(); + } + checkVerifyErrorCouter(1); +} + +} // namespace Ssl +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h new file mode 100644 index 0000000000..26a08fbbae --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "test/integration/http_integration.h" +#include "test/integration/server.h" +#include "test/integration/ssl_utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Ssl { + +class SslSPIFFECertValidatorIntegrationTest + : public testing::TestWithParam< + std::tuple>, + public HttpIntegrationTest { +public: + SslSPIFFECertValidatorIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, std::get<0>(GetParam())) {} + + void initialize() override; + void TearDown() override; + + virtual Network::ClientConnectionPtr + makeSslClientConnection(const ClientSslTransportOptions& options, bool use_expired); + void checkVerifyErrorCouter(uint64_t value); + + static std::string ipClientVersionTestParamsToString( + const ::testing::TestParamInfo< + std::tuple>& + params) { + return fmt::format("{}_TLSv1_{}", + std::get<0>(params.param) == Network::Address::IpVersion::v4 ? "IPv4" + : "IPv6", + std::get<1>(params.param) - 1); + } + +protected: + bool allow_expired_cert_{}; + envoy::config::core::v3::TypedExtensionConfig* custom_validator_config_{nullptr}; + std::unique_ptr context_manager_; + std::vector san_matchers_; + const envoy::extensions::transport_sockets::tls::v3::TlsParameters::TlsProtocol tls_version_{ + std::get<1>(GetParam())}; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc new file mode 100644 index 0000000000..3d01460525 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc @@ -0,0 +1,580 @@ +#include +#include +#include +#include + +#include "envoy/common/exception.h" + +#include "common/common/c_smart_ptr.h" +#include "common/event/real_time_system.h" + +#include "extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h" +#include "extensions/transport_sockets/tls/stats.h" + +#include "test/extensions/transport_sockets/tls/cert_validator/test_common.h" +#include "test/extensions/transport_sockets/tls/ssl_test_utility.h" +#include "test/test_common/environment.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +using TestCertificateValidationContextConfigPtr = + std::unique_ptr; +using SPIFFEValidatorPtr = std::unique_ptr; +using ASN1IA5StringPtr = CSmartPtr; +using GeneralNamesPtr = CSmartPtr; +using X509StoreContextPtr = CSmartPtr; +using X509Ptr = CSmartPtr; +using SSLContextPtr = CSmartPtr; + +class TestSPIFFEValidator : public testing::Test { +public: + TestSPIFFEValidator() : stats_(generateSslStats(store_)) {} + void initialize(std::string yaml, TimeSource& time_source) { + envoy::config::core::v3::TypedExtensionConfig typed_conf; + TestUtility::loadFromYaml(yaml, typed_conf); + config_ = std::make_unique( + typed_conf, allow_expired_certificate_, san_matchers_); + validator_ = std::make_unique(config_.get(), stats_, time_source); + } + + void initialize(std::string yaml) { + envoy::config::core::v3::TypedExtensionConfig typed_conf; + TestUtility::loadFromYaml(yaml, typed_conf); + config_ = std::make_unique( + typed_conf, allow_expired_certificate_, san_matchers_); + validator_ = + std::make_unique(config_.get(), stats_, config_->api().timeSource()); + }; + + void initialize() { validator_ = std::make_unique(stats_, time_system_); } + + // Getter. + SPIFFEValidator& validator() { return *validator_; } + SslStats& stats() { return stats_; } + + // Setter. + void setAllowExpiredCertificate(bool val) { allow_expired_certificate_ = val; } + void setSanMatchers(std::vector san_matchers) { + san_matchers_ = san_matchers; + }; + +private: + bool allow_expired_certificate_{false}; + TestCertificateValidationContextConfigPtr config_; + std::vector san_matchers_{}; + Stats::TestUtil::TestStore store_; + SslStats stats_; + Event::TestRealTimeSystem time_system_; + SPIFFEValidatorPtr validator_; +}; + +TEST_F(TestSPIFFEValidator, InvalidCA) { + // Invalid trust bundle. + EXPECT_THROW_WITH_MESSAGE(initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: hello.com + trust_bundle: + inline_string: "invalid" + )EOF")), + EnvoyException, "Failed to load trusted CA certificate for hello.com"); +} + +// Multiple trust bundles are given for the same trust domain. +TEST_F(TestSPIFFEValidator, Constructor) { + EXPECT_THROW_WITH_MESSAGE(initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: hello.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert_with_crl.pem" + - name: hello.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert_with_crl.pem" + )EOF")), + EnvoyException, + "Multiple trust bundles are given for one trust domain for hello.com"); + + // Single trust bundle. + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: hello.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert_with_crl.pem" + )EOF")); + + EXPECT_EQ(1, validator().trustBundleStores().size()); + EXPECT_NE(validator().getCaFileName().find("test_data/ca_cert_with_crl.pem"), std::string::npos); + EXPECT_NE(validator().getCaFileName().find("hello.com"), std::string::npos); + + // Multiple trust bundles. + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: hello.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + - name: k8s-west.example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.pem" + )EOF")); + + EXPECT_EQ(2, validator().trustBundleStores().size()); +} + +TEST(SPIFFEValidator, TestExtractTrustDomain) { + EXPECT_EQ("", SPIFFEValidator::extractTrustDomain("foo")); + EXPECT_EQ("", SPIFFEValidator::extractTrustDomain("abc.com/")); + EXPECT_EQ("", SPIFFEValidator::extractTrustDomain("abc.com/workload/")); + EXPECT_EQ("", SPIFFEValidator::extractTrustDomain("spiffe://")); + EXPECT_EQ("abc.com", SPIFFEValidator::extractTrustDomain("spiffe://abc.com/")); + EXPECT_EQ("dev.envoy.com", + SPIFFEValidator::extractTrustDomain("spiffe://dev.envoy.com/workload1")); + EXPECT_EQ("k8s-west.example.com", SPIFFEValidator::extractTrustDomain( + "spiffe://k8s-west.example.com/ns/staging/sa/default")); +} + +TEST(SPIFFEValidator, TestCertificatePrecheck) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + // basicConstraints: CA:True, + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem")); + EXPECT_FALSE(SPIFFEValidator::certificatePrecheck(cert.get())); + + cert = readCertFromFile(TestEnvironment::substitute( + // basicConstraints CA:False, keyUsage has keyCertSign + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.pem")); + EXPECT_FALSE(SPIFFEValidator::certificatePrecheck(cert.get())); + + cert = readCertFromFile(TestEnvironment::substitute( + // basicConstraints CA:False, keyUsage has cRLSign + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.pem")); + EXPECT_FALSE(SPIFFEValidator::certificatePrecheck(cert.get())); + + cert = readCertFromFile(TestEnvironment::substitute( + // basicConstraints CA:False, keyUsage does not have keyCertSign and cRLSign + // should be considered valid (i.e. return 1). + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/extensions_cert.pem")); + EXPECT_TRUE(SPIFFEValidator::certificatePrecheck(cert.get())); +} + +TEST_F(TestSPIFFEValidator, TestInitializeSslContexts) { + initialize(); + EXPECT_EQ(SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + validator().initializeSslContexts({}, false)); +} + +TEST_F(TestSPIFFEValidator, TestGetTrustBundleStore) { + initialize(); + + // No SAN + auto cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/extensions_cert.pem")); + EXPECT_FALSE(validator().getTrustBundleStore(cert.get())); + + // Non-SPIFFE SAN + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem")); + EXPECT_FALSE(validator().getTrustBundleStore(cert.get())); + + // SPIFFE SAN + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem")); + + // Trust bundle not provided. + EXPECT_FALSE(validator().getTrustBundleStore(cert.get())); + + // Trust bundle provided. + validator().trustBundleStores().emplace("example.com", X509StorePtr(X509_STORE_new())); + EXPECT_TRUE(validator().getTrustBundleStore(cert.get())); +} + +TEST_F(TestSPIFFEValidator, TestDoVerifyCertChainPrecheckFailure) { + initialize(); + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + // basicConstraints: CA:True + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem")); + + TestSslExtendedSocketInfo info; + EXPECT_FALSE(validator().doVerifyCertChain(store_ctx.get(), &info, *cert, nullptr)); + EXPECT_EQ(1, stats().fail_verify_error_.value()); + EXPECT_EQ(info.certificateValidationStatus(), Envoy::Ssl::ClientValidationStatus::Failed); +} + +TEST_F(TestSPIFFEValidator, TestDoVerifyCertChainSingleTrustDomain) { + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF")); + + X509StorePtr ssl_ctx = X509_STORE_new(); + + // Trust domain matches so should be accepted. + auto cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_TRUE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + // Different trust domain so should be rejected. + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem")); + + store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_FALSE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + // Does not have san. + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/extensions_cert.pem")); + + store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_FALSE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + EXPECT_EQ(2, stats().fail_verify_error_.value()); +} + +TEST_F(TestSPIFFEValidator, TestDoVerifyCertChainMultipleTrustDomain) { + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF")); + + X509StorePtr ssl_ctx = X509_STORE_new(); + + // Trust domain matches so should be accepted. + auto cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_TRUE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem")); + store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_TRUE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + // Trust domain matches but it has expired. + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert.pem")); + store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_FALSE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + // Does not have san. + cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/extensions_cert.pem")); + + store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_FALSE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + EXPECT_EQ(2, stats().fail_verify_error_.value()); +} + +TEST_F(TestSPIFFEValidator, TestDoVerifyCertChainMultipleTrustDomainAllowExpired) { + setAllowExpiredCertificate(true); + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF")); + + X509StorePtr ssl_ctx = X509_STORE_new(); + + // Trust domain matches and it has expired but allow_expired_certificate is true, so this + // should be accepted. + auto cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir " + "}}/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert.pem")); + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + EXPECT_TRUE(validator().doVerifyCertChain(store_ctx.get(), nullptr, *cert, nullptr)); + + EXPECT_EQ(0, stats().fail_verify_error_.value()); +} + +TEST_F(TestSPIFFEValidator, TestDoVerifyCertChainSANMatching) { + const auto config = TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF"); + + X509StorePtr ssl_ctx = X509_STORE_new(); + + // URI SAN = spiffe://lyft.com/test-team + const auto cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + EXPECT_TRUE(X509_STORE_CTX_init(store_ctx.get(), ssl_ctx.get(), cert.get(), nullptr)); + TestSslExtendedSocketInfo info; + info.setCertificateValidationStatus(Envoy::Ssl::ClientValidationStatus::NotValidated); + { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_prefix("spiffe://lyft.com/"); + setSanMatchers({matcher}); + initialize(config); + EXPECT_TRUE(validator().doVerifyCertChain(store_ctx.get(), &info, *cert, nullptr)); + EXPECT_EQ(info.certificateValidationStatus(), Envoy::Ssl::ClientValidationStatus::Validated); + } + { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_prefix("spiffe://example.com/"); + setSanMatchers({matcher}); + initialize(config); + EXPECT_FALSE(validator().doVerifyCertChain(store_ctx.get(), &info, *cert, nullptr)); + EXPECT_EQ(1, stats().fail_verify_error_.value()); + EXPECT_EQ(info.certificateValidationStatus(), Envoy::Ssl::ClientValidationStatus::Failed); + stats().fail_verify_error_.reset(); + } +} + +void addIA5StringGenNameExt(X509* cert, int type, const std::string name) { + GeneralNamesPtr gens = sk_GENERAL_NAME_new_null(); + GENERAL_NAME* gen = GENERAL_NAME_new(); // ownership taken by "gens" + ASN1IA5StringPtr ia5 = ASN1_IA5STRING_new(); + EXPECT_TRUE(ASN1_STRING_set(ia5.get(), name.data(), name.length())); + GENERAL_NAME_set0_value(gen, type, ia5.release()); + sk_GENERAL_NAME_push(gens.get(), gen); + EXPECT_TRUE(X509_add1_ext_i2d(cert, NID_subject_alt_name, gens.get(), 0, X509V3_ADD_DEFAULT)); +} + +TEST_F(TestSPIFFEValidator, TestMatchSubjectAltNameWithURISan) { + envoy::type::matcher::v3::StringMatcher exact_matcher, prefix_matcher, regex_matcher; + exact_matcher.set_exact("spiffe://example.com/workload"); + prefix_matcher.set_prefix("spiffe://envoy.com"); + regex_matcher.mutable_safe_regex()->mutable_google_re2(); + regex_matcher.mutable_safe_regex()->set_regex("spiffe:\\/\\/([a-z]+)\\.myorg\\.com\\/.+"); + setSanMatchers({exact_matcher, prefix_matcher, regex_matcher}); + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF")); + + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_URI, "spiffe://envoy.com/myapp"); + EXPECT_TRUE(validator().matchSubjectAltName(*leaf.get())); + } + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_URI, "spiffe://example.com/workload"); + EXPECT_TRUE(validator().matchSubjectAltName(*leaf.get())); + } + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_URI, "spiffe://example.com/otherworkload"); + EXPECT_FALSE(validator().matchSubjectAltName(*leaf.get())); + } + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_URI, "spiffe://foo.myorg.com/workload"); + EXPECT_TRUE(validator().matchSubjectAltName(*leaf.get())); + } + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_URI, "spiffe://bar.myorg.com/workload"); + EXPECT_TRUE(validator().matchSubjectAltName(*leaf.get())); + } +} + +// SPIFFE validator ignores any SANs other than URI. +TEST_F(TestSPIFFEValidator, TestMatchSubjectAltNameWithoutURISan) { + envoy::type::matcher::v3::StringMatcher exact_matcher, prefix_matcher; + exact_matcher.set_exact("spiffe://example.com/workload"); + prefix_matcher.set_prefix("envoy"); + setSanMatchers({exact_matcher, prefix_matcher}); + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF")); + + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_DNS, "envoy.com/workload"); + EXPECT_FALSE(validator().matchSubjectAltName(*leaf.get())); + } + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_DNS, "spiffe://example.com/workload"); + EXPECT_FALSE(validator().matchSubjectAltName(*leaf.get())); + } + { + X509Ptr leaf = X509_new(); + addIA5StringGenNameExt(leaf.get(), GEN_EMAIL, "envoy@example.co.jp"); + EXPECT_FALSE(validator().matchSubjectAltName(*leaf.get())); + } +} + +TEST_F(TestSPIFFEValidator, TestGetCaCertInformation) { + initialize(); + + // No cert is set so this should be nullptr. + EXPECT_FALSE(validator().getCaCertInformation()); + + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem" + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF")); + + auto actual = validator().getCaCertInformation(); + EXPECT_TRUE(actual); +} + +TEST_F(TestSPIFFEValidator, TestDaysUntilFirstCertExpires) { + initialize(); + EXPECT_EQ(0, validator().daysUntilFirstCertExpires()); + + Event::SimulatedTimeSystem time_system; + time_system.setSystemTime(std::chrono::milliseconds(0)); + + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem" + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/intermediate_ca_cert.pem" + )EOF"), + time_system); + EXPECT_EQ(19231, validator().daysUntilFirstCertExpires()); + time_system.setSystemTime(std::chrono::milliseconds(864000000)); + EXPECT_EQ(19221, validator().daysUntilFirstCertExpires()); +} + +TEST_F(TestSPIFFEValidator, TestAddClientValidationContext) { + Event::TestRealTimeSystem time_system; + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem" + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + - name: foo.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF"), + time_system); + + bool foundTestServer = false; + bool foundTestCA = false; + SSLContextPtr ctx = SSL_CTX_new(TLS_method()); + validator().addClientValidationContext(ctx.get(), false); + for (X509_NAME* name : SSL_CTX_get_client_CA_list(ctx.get())) { + const int cn_index = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + EXPECT_TRUE(cn_index >= 0); + X509_NAME_ENTRY* cn_entry = X509_NAME_get_entry(name, cn_index); + EXPECT_TRUE(cn_entry); + ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); + EXPECT_TRUE(cn_asn1); + + auto cn_str = std::string(reinterpret_cast(ASN1_STRING_data(cn_asn1))); + if (cn_str == "Test Server") { + foundTestServer = true; + } else if (cn_str == "Test CA") { + foundTestCA = true; + } + } + + EXPECT_TRUE(foundTestServer); + EXPECT_TRUE(foundTestCA); +} + +TEST_F(TestSPIFFEValidator, TestUpdateDigestForSessionId) { + Event::TestRealTimeSystem time_system; + initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: lyft.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem" + - name: example.com + trust_bundle: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + )EOF"), + time_system); + uint8_t hash_buffer[EVP_MAX_MD_SIZE]; + bssl::ScopedEVP_MD_CTX md; + EVP_DigestInit(md.get(), EVP_sha256()); + validator().updateDigestForSessionId(md, hash_buffer, SHA256_DIGEST_LENGTH); +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/cert_validator/test_common.h b/test/extensions/transport_sockets/tls/cert_validator/test_common.h new file mode 100644 index 0000000000..9dac1b35cf --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/test_common.h @@ -0,0 +1,89 @@ +#include + +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/macros.h" +#include "common/common/matchers.h" + +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class TestSslExtendedSocketInfo : public Envoy::Ssl::SslExtendedSocketInfo { +public: + TestSslExtendedSocketInfo() = default; + + void setCertificateValidationStatus(Envoy::Ssl::ClientValidationStatus validated) override { + status_ = validated; + } + Envoy::Ssl::ClientValidationStatus certificateValidationStatus() const override { + return status_; + } + +private: + Envoy::Ssl::ClientValidationStatus status_; +}; + +class TestCertificateValidationContextConfig + : public Envoy::Ssl::CertificateValidationContextConfig { +public: + TestCertificateValidationContextConfig( + envoy::config::core::v3::TypedExtensionConfig config, bool allow_expired_certificate = false, + std::vector san_matchers = {}) + : allow_expired_certificate_(allow_expired_certificate), api_(Api::createApiForTest()), + custom_validator_config_(config), san_matchers_(san_matchers){}; + TestCertificateValidationContextConfig() + : api_(Api::createApiForTest()), custom_validator_config_(absl::nullopt){}; + + const std::string& caCert() const override { CONSTRUCT_ON_FIRST_USE(std::string, ""); } + const std::string& caCertPath() const override { CONSTRUCT_ON_FIRST_USE(std::string, ""); } + const std::string& certificateRevocationList() const override { + CONSTRUCT_ON_FIRST_USE(std::string, ""); + } + const std::string& certificateRevocationListPath() const final { + CONSTRUCT_ON_FIRST_USE(std::string, ""); + } + const std::vector& verifySubjectAltNameList() const override { + CONSTRUCT_ON_FIRST_USE(std::vector, {}); + } + const std::vector& + subjectAltNameMatchers() const override { + return san_matchers_; + } + const std::vector& verifyCertificateHashList() const override { + CONSTRUCT_ON_FIRST_USE(std::vector, {}); + } + const std::vector& verifyCertificateSpkiList() const override { + CONSTRUCT_ON_FIRST_USE(std::vector, {}); + } + bool allowExpiredCertificate() const override { return allow_expired_certificate_; } + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification + trustChainVerification() const override { + return envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification:: + CertificateValidationContext_TrustChainVerification_ACCEPT_UNTRUSTED; + } + + const absl::optional& + customValidatorConfig() const override { + return custom_validator_config_; + } + + Api::Api& api() const override { return *api_; } + +private: + bool allow_expired_certificate_{false}; + Api::ApiPtr api_; + const absl::optional custom_validator_config_; + const std::vector san_matchers_{}; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/cert_validator/utility_test.cc b/test/extensions/transport_sockets/tls/cert_validator/utility_test.cc new file mode 100644 index 0000000000..d97d0f161f --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/utility_test.cc @@ -0,0 +1,43 @@ +#include "common/common/c_smart_ptr.h" + +#include "extensions/transport_sockets/tls/cert_validator/utility.h" + +#include "gtest/gtest.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +using X509StoreContextPtr = CSmartPtr; + +TEST(CertValidatorUtil, ignoreCertificateExpirationCallback) { + // If ok = true, then true should be returned. + EXPECT_TRUE(CertValidatorUtil::ignoreCertificateExpirationCallback(true, nullptr)); + + // Expired case. + { + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + X509_STORE_CTX_set_error(store_ctx.get(), X509_V_ERR_CERT_HAS_EXPIRED); + EXPECT_TRUE(CertValidatorUtil::ignoreCertificateExpirationCallback(false, store_ctx.get())); + } + // Yet valid case. + { + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + X509_STORE_CTX_set_error(store_ctx.get(), X509_V_ERR_CERT_NOT_YET_VALID); + EXPECT_TRUE(CertValidatorUtil::ignoreCertificateExpirationCallback(false, store_ctx.get())); + } + // Other error + { + X509StoreContextPtr store_ctx = X509_STORE_CTX_new(); + X509_STORE_CTX_set_error(store_ctx.get(), X509_V_ERR_CERT_REVOKED); + EXPECT_FALSE(CertValidatorUtil::ignoreCertificateExpirationCallback(false, store_ctx.get())); + } +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 16dc6b221f..4b6da12e75 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -106,145 +106,6 @@ class SslContextImplTest : public SslCertsTest { ContextManagerImpl manager_{time_system_}; }; -TEST_F(SslContextImplTest, TestDnsNameMatching) { - EXPECT_TRUE(ContextImpl::dnsNameMatch("lyft.com", "lyft.com")); - EXPECT_TRUE(ContextImpl::dnsNameMatch("a.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("foo.test.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "")); -} - -TEST_F(SslContextImplTest, TestDnsNameMatchingLegacy) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); - EXPECT_TRUE(ContextImpl::dnsNameMatch("lyft.com", "lyft.com")); - EXPECT_TRUE(ContextImpl::dnsNameMatch("a.lyft.com", "*.lyft.com")); - // Legacy behavior - EXPECT_TRUE(ContextImpl::dnsNameMatch("a.b.lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("foo.test.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*.lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("alyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("", "*lyft.com")); - EXPECT_FALSE(ContextImpl::dnsNameMatch("lyft.com", "")); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltNameDNSMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"server1.example.com", - "server2.example.com"}; - EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameDNSMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_hidden_envoy_deprecated_regex(".*.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameWildcardDNSMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_exact("api.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestMultiLevelMatch) { - // san_multiple_dns_cert matches *.example.com - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_exact("foo.api.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_FALSE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestMultiLevelMatchLegacy) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_exact("foo.api.example.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltNameURIMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); - std::vector verify_subject_alt_name_list = {"spiffe://lyft.com/fake-team", - "spiffe://lyft.com/test-team"}; - EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltMultiDomain) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; - EXPECT_FALSE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltMultiDomainLegacy) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.fix_wildcard_matching", "false"}}); - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir " - "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"https://a.www.example.com"}; - EXPECT_TRUE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameURIMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_hidden_envoy_deprecated_regex("spiffe://lyft.com/.*-team"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_TRUE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - -TEST_F(SslContextImplTest, TestVerifySubjectAltNameNotMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - std::vector verify_subject_alt_name_list = {"foo", "bar"}; - EXPECT_FALSE(ContextImpl::verifySubjectAltName(cert.get(), verify_subject_alt_name_list)); -} - -TEST_F(SslContextImplTest, TestMatchSubjectAltNameNotMatched) { - bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_hidden_envoy_deprecated_regex(".*.foo.com"); - std::vector subject_alt_name_matchers; - subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher)); - EXPECT_FALSE(ContextImpl::matchSubjectAltName(cert.get(), subject_alt_name_matchers)); -} - TEST_F(SslContextImplTest, TestCipherSuites) { const std::string yaml = R"EOF( common_tls_context: diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index 279ffebec6..08879cf9c7 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -42,6 +42,7 @@ void SslIntegrationTestBase::initialize() { .setOcspStapleRequired(ocsp_staple_required_) .setTlsV13(server_tlsv1_3_) .setExpectClientEcdsaCert(client_ecdsa_cert_)); + HttpIntegrationTest::initialize(); context_manager_ = diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index f3ca3a2476..207e723d08 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -2743,7 +2743,6 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { auto socket = std::make_shared( Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockTcpListenerCallbacks callbacks; - Network::MockConnectionHandler connection_handler; Network::ListenerPtr listener = dispatcher_->createListener(socket, callbacks, true, ENVOY_TCP_BACKLOG_SIZE); diff --git a/test/extensions/transport_sockets/tls/test_data/certs.sh b/test/extensions/transport_sockets/tls/test_data/certs.sh index b1155f18d9..ced51ce569 100755 --- a/test/extensions/transport_sockets/tls/test_data/certs.sh +++ b/test/extensions/transport_sockets/tls/test_data/certs.sh @@ -260,3 +260,20 @@ generate_x509_cert_nosubject no_subject ca # Generate unit test certificate generate_rsa_key unittest generate_selfsigned_x509_cert unittest + +generate_rsa_key keyusage_cert_sign +generate_x509_cert keyusage_cert_sign ca + +generate_rsa_key keyusage_crl_sign +generate_x509_cert keyusage_crl_sign ca + +generate_rsa_key spiffe_san +generate_x509_cert spiffe_san ca + +generate_rsa_key non_spiffe_san +generate_x509_cert non_spiffe_san ca + +cp -f spiffe_san_cert.cfg expired_spiffe_san_cert.cfg +generate_rsa_key expired_spiffe_san +generate_x509_cert expired_spiffe_san ca -365 +rm -f expired_spiffe_san_cert.cfg diff --git a/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert.pem b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert.pem new file mode 100644 index 0000000000..4a169fa309 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIUSJ40AZSfus4RkmQ5Ej59YdSkZzAwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjEwMzExMTIyMDI3WhcNMjAw +MzExMTIyMDI3WjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2es6xMdYtBp2mj/Y14GzCZgI7LXAY1scb +zm4E6tm10CJ+ont9qh4jKW6v3CYR5t6S7N/ksenT9+iv65lhL/e0QuyoH3HOxy06 +XEjwtIHz4R1JcuXhbMj/CfNkzjKLRb1WWVRudH/kKEjTpP/Cg1febxnWqld2KjT2 +z0desRheK/dLBeQL5BFfILjRSG6tVIxH7O0TNMs91f0ILVwHyJi7XICu6uJ03eDL +w6Hdav3pt8P4TP96hyn1e+vubfUtqrTVJd82AdFFuyZgiCD1r+2+LCi6Xt/woNiw +MDdbj0/BSk9kuR3ey1cZfsZB1lxWkR9iPDY1Jck0YktG6Oo25fxLAgMBAAGjgbIw +ga8wDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMDMGA1UdEQQsMCqCCWVudm95LmNvbYYdc3BpZmZlOi8vZXhh +bXBsZS5jb20vd29ya2xvYWQwHQYDVR0OBBYEFItO4txU7bjtelhyzyyksnvWArL4 +MB8GA1UdIwQYMBaAFNPBCyPjw6jyMr5fVME/eDgTVQPZMA0GCSqGSIb3DQEBCwUA +A4IBAQCEQJQxAby6hAjSCR2M6XVFb3ILRhV03Lyrl49bsJJlvLtzOspFwqbczw5Z +IELtjVm5ValKZCzjUfNmTBtN91YxBGuCEJwodSpccYeg2EF3x7U96hUC2SUGMB4s +Ufrx3zMEOOE7xlYSdEhpYs5s5aVIcbrAFD0IBYxTJ5AhsvRo+L8rXMjDYA6ZarTT +aq0ctlwr6QqX5HVPVtkurLb43ySdXwc7AqlLQNgXxXnPM5hWa70QjCi6Qo+QcjAr +yh3anmFhclylR1TESXrmOuV54AW9HMLeIaB1QxXJHFkPwSeTXGKLaKs983msQKCy +P3Xrg0rBp3SUH7q0tXFNpzD3RIv1 +-----END CERTIFICATE----- diff --git a/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert_info.h b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert_info.h new file mode 100644 index 0000000000..54bcc1990e --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert_info.h @@ -0,0 +1,8 @@ +// NOLINT(namespace-envoy) +constexpr char TEST_EXPIRED_SPIFFE_SAN_CERT_256_HASH[] = + "c04e88df69b88617a03666ef598012e916eadd7db620f06aaa82689a61e97dfb"; +constexpr char TEST_EXPIRED_SPIFFE_SAN_CERT_1_HASH[] = "ffd8365902996d861de8faa3c40930c32d91a996"; +constexpr char TEST_EXPIRED_SPIFFE_SAN_CERT_SPKI[] = "ONtCrb9Ejc5lV5wMTkW1OztK/phOSAtoK69joGR3Iu0="; +constexpr char TEST_EXPIRED_SPIFFE_SAN_CERT_SERIAL[] = "489e3401949fbace11926439123e7d61d4a46730"; +constexpr char TEST_EXPIRED_SPIFFE_SAN_CERT_NOT_BEFORE[] = "Mar 11 12:20:27 2021 GMT"; +constexpr char TEST_EXPIRED_SPIFFE_SAN_CERT_NOT_AFTER[] = "Mar 11 12:20:27 2020 GMT"; diff --git a/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert_key.pem b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert_key.pem new file mode 100644 index 0000000000..85fe866cc4 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA2SXPYbcr5nKU1VYHJONwZzUgjl3uYlMQlz14iNuI7CSdMOTf +yTcuw581/dy5DhcWUI1Otc8193X7eZAndzlyivJGKyvgybMxi31rdX2nnOKPH6FT +2WOlIk4IkRKv5JwaHfBa6MTn3z4+6g1dsDvTkk+uR9wZsy4lDCnp1UIbJ3EpScVY +qkjMZ3pa1/IUidyBdJl4zKbSgmp0j88+wgot+HZfnbaKa9yo+s7VmOX/0vYZy1v6 +ERS/M5H4bsjRjr1ewZP+p7swXYM/6DPlikyBGFf0OfbUBAloZfy69MC6mOEfgmIV +OotgZpDQ/DsGRbmBlnl433SB5r7lRzTcv91SvQIDAQABAoIBAQC+WxnoFDXkx69X +MRoEKWlA4F7QzuEJyr1xh46hkqn+ML6nvQu8jaZuEKS5DYQgKPcD2EwWrzYk34V6 +9HbUWkTiLy556/Ybev3ExUatcWC89BL5bGhf2q1JXFTAMDyHUJzIgAqBoROPLTbj +4KGvu1JmIXmrPvQ8kggiGKAGtfOt5zQNbcmh79qlg/gJpNGaZP6km4OihNOeFazu +QjlX2Su7gNQRwmtTUB26zqh7PQNZsbyWIyEkUbAUV6b9omiSdOVKE+c5A6TPBdX6 +cDVH7q5JCDDbW6lyKYTEtqxuPlnXKeEaA4IA4QZTiZNWfkMWQkSttEOC/JSboN9u +hWGogxP1AoGBAP2PD6itv1qsF+wLm5lpmaLM0LejQqROsLWQwg+k60EK2+ocVycE +2qWL2rWuMEWFpTzd/gNgb0DB7nscTi3SG3Ow7W4bAk0Mhia/ghjaByHw7y1r0CdP +v1JvL/CSVcs7UJ5L6KA7uhKc/dpHQ5kBXqWFy2pFIsWZrZ2i0nVX+A73AoGBANs9 +AepcZWJKVpYcSZj/dmzXv2DK0NRutTyPt+cpsXmwc5my1zM3eVuE9uGy1ZFG9hWu ++KowGxb1UpuTswdsBr6fDcAPQ9Nvz+xzc5dL5oDLGOY7FXm3Iek75rSylghll0bR +WREE/S5O02yhIVwAk/ja9X+omapnqb2acabGUZrrAoGBAKRFejSjFFUPJ5Ry1MUo +iDPUE+jVach/fray6Tcvdoa2HVHoOIJ7/5e/KtwH1RhFhI8Rl47ifFFtEy9JYFEJ +TWW/m6N49h2q3oLeEdA9+N8BdENuFPZTETW7+UgX1mOLsyhLL5QX/e4vLbur7dyD +6ai9LNNVpTh13uhA467IZlpzAoGBAJ+PEiLQJhVYBxZu9f19B2KFzsoSMdQ50nuG +GZEK3hiznB6jc03aNN+vsMJolliKLbVwR5gNNNoLp5iE8UoYi/wV5DsAoRt0B/6N +s/XMDrFznuaI38NazjpiFzExvFHWJjgrSshBOkaGpgcsagv8e7UlHoFPYq8LjRTq +H4cCVQgLAoGBAOPmexZDWTmpsHY4gO0igpI0GcQfNfbgnz0QHgflt3y3yG01wGeR +be6hzw8JJE+AGgdMxcXbhwZj4ho9EpkCmgUqJF/CevsukK6bOsncVA/zJIFhgIIT +2+bGCF3OsSsaH5OkHMpm+E/efenbn+IynwIMByh1AUljmkgaKmmCfrLE +-----END RSA PRIVATE KEY----- diff --git a/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_key.pem b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_key.pem new file mode 100644 index 0000000000..2cdecd7d78 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtnrOsTHWLQadpo/2NeBswmYCOy1wGNbHG85uBOrZtdAifqJ7 +faoeIylur9wmEebekuzf5LHp0/for+uZYS/3tELsqB9xzsctOlxI8LSB8+EdSXLl +4WzI/wnzZM4yi0W9VllUbnR/5ChI06T/woNX3m8Z1qpXdio09s9HXrEYXiv3SwXk +C+QRXyC40UhurVSMR+ztEzTLPdX9CC1cB8iYu1yAruridN3gy8Oh3Wr96bfD+Ez/ +eocp9Xvr7m31Laq01SXfNgHRRbsmYIgg9a/tviwoul7f8KDYsDA3W49PwUpPZLkd +3stXGX7GQdZcVpEfYjw2NSXJNGJLRujqNuX8SwIDAQABAoIBAHO8+qHaqSxPuOgv +AQt096ZpCts725B1kT3qtU87IbC2fVpydf7jSlAk9EZoTGEoXF02Dl/AFD3UTGpf +9R12ThawJ+gOYaG0JNSSOdUpuA7V5jxyuqk61VUlm5GBUv+Q0SEWcX3JOGCeyGos +ied2ZOH495t22Rhidstf9rVxf7IiAC3CamwYqm40N0Danvb8BAf1lO75/M4LQkEx ++YtPWkMzhj0pQgruN11eIIlFiXcfIzAMtYFtais9MWbQYfqRYbxseAb0JO6Mt7LW +5F3wUplq4RQKWA72djp79WUgwqZiUuhkmLpu36KcVCtpfHBiFMhbT/HrkwfsQqCe +8jPB1QkCgYEA4tZ8uQ/e6xT02isORTI+bAGverSwf0us7UIIafbzOIqp737ulVFf +psWkMyVjeIlfGV2CgDehHTmq4+SDICKZTNAw7ErLmgtIFUBVvy6wCwHOjI/hljDB +e/Wx1zOKgahZ/hbWbpbckcZf9rVJdKOeG3DoqIHPt0JorbBqFOM4Ym8CgYEAzfBw +jnaaXC3vjfBPtNY8XAB9Fekv8R3kpgnA4uH6fz7L0kZdAri2HYYcSslPcbz+niHP +PGUna8b/oW2V3IhVmZ97+4OHSjxtuDcC4nDBLJY1mY+zDAsUaZBJZiE39Gdh9n7Y +I+PmHHe/HiLTWX0RyiDCpiY2OtFR1gVkZq/kgeUCgYAm1pySPxJm47asqBSQanLf +oUY/VzKlCPr5wIWaRwsL0koYVH5bGIytDEf57dvjJnoe6LDQbTXrwBTvYg/Fb8cZ +rMfDnWbQ4D+eS96ilkbUC6Im1PfF9GEcbUve0ddULdQCujxKwQ/Q8cs6fX2vN9h5 +UFK1j0xWT0uG+Z6gJutfhwKBgQCYUYNey7hl/4/Ugu7hSSfBwJbEEwJjq4GHkWU6 +KnxVi0PD1klLDwWcFqpsYjNBY9FLcqRN/l9G5xwB8QTL5YaypnRUVjdFoCZi/0VB +6LXoXX1thAyhFrzVS2QOhvlU6vZZE/6XpSN44NTZ3FZINSpoPwzZSSAJJvMJgll9 +XhF8RQKBgQCXt/FjJmWuYalTwHZXMk6vJi3GP/nH7WdXSv3OOiMMJWoyXXwfvNoi +1myKP/j959NB5jDk/VXDU+HTdX7FfKf6mfwKT7CtPlR9Xwb+XCWhaXnsr5BcyZLb +m+yFxMhsbNuY9BIc0HckGm2jMl7NeePwCfuCCFpOc2yRgpazUCC+3A== +-----END RSA PRIVATE KEY----- diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.cfg b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.cfg new file mode 100644 index 0000000000..b5daa7d2ca --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.cfg @@ -0,0 +1,35 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Cert +commonName_default = Test Cert +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyCertSign +extendedKeyUsage = clientAuth, serverAuth +subjectKeyIdentifier = hash +1.2.3.4.5.6.7.8 = ASN1:UTF8String:Something +1.2.3.4.5.6.7.9 = DER:30:03:01:01:FF + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyCertSign +extendedKeyUsage = clientAuth, serverAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +1.2.3.4.5.6.7.8 = ASN1:UTF8String:Something +1.2.3.4.5.6.7.9 = DER:30:03:01:01:FF \ No newline at end of file diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.pem b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.pem new file mode 100644 index 0000000000..8f6a5adf8f --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEJDCCAwygAwIBAgIUGf0AE7012IPTiBn6CB+c3SVvbcgwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjEwMjAzMTExNjM4WhcNMjMw +MjAzMTExNjM4WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzESMBAGA1UEAwwJVGVzdCBDZXJ0MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlePKCulIVDnvv7pvlaDtAfR+PJ504y6Ncxgd +nTyNnRCKHBnwlq56WWYkilc60FKdE4DehXWY9akyIlrA8nna3ckZ1PSM9GdHJRCn +JWUh1RirWS8xdhT7zbFH54BnGjj+7KT6JzsMKrP3vYa1Y5Ff/N6q9KFY5hQUWJ8X +gaWqxySi+yxFGhevjp3Gq0FTX3thoeMYOHt5AfwlFnXaKlc+xupWaAFl5VQAM8VS +G8IG+YOsxZmI8wNdk9BIJCUIubvNykYMrwveVkgnCYB1K3wYwNNjJt9Q5bFKtRfl +MKQ4KzugNlWU/PowIbDlla/NwSO866iYkROIZeW9aty5L+1KhQIDAQABo4GnMIGk +MAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgLkMB0GA1UdJQQWMBQGCCsGAQUFBwMC +BggrBgEFBQcDATAdBgNVHQ4EFgQUkGcFiogsv4LFO6e7juYw1Z/PsvUwHwYDVR0j +BBgwFoAU08ELI+PDqPIyvl9UwT94OBNVA9kwFgYHKgMEBQYHCAQLDAlTb21ldGhp +bmcwEAYHKgMEBQYHCQQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADvpQnMwqH09 +TetXraNmhvXUmhnTCaoStVQpWtEvOnnqHZ760Lc2XG1dKL6v40AovGK4PLlf7kds +Uz5oj65Wb7Z7pxz5Aa4P02VocMoASgYFMQrIJe4e0SIwfKX3UUhvmSG3e4mxu3nV +JHm4A2NZ9weuOIx6+6Hb6S8PfMLUbmmf3/ZCvNVMNlwllfsOwpMsDffT29GrbiZO +3UIQ5nq+aKuGzOCjg1tjB0fETdEE2/MqRUMdB8lOcT49ZhsJq/MLGw29NGyCB+ky +59xUk8U9c/C1NwxtaFFJzhMhDlOxjXXxpGt06QbOoH+B2yIW0r4uVymWhAUq/ewx +9RYva+yssd0= +-----END CERTIFICATE----- diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert_info.h b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert_info.h new file mode 100644 index 0000000000..2d1971cf25 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert_info.h @@ -0,0 +1,8 @@ +// NOLINT(namespace-envoy) +constexpr char TEST_KEYUSAGE_CERT_SIGN_CERT_256_HASH[] = + "182894ffefcf77ca5be0907d37cf20142bedfa9f65979ca1b33668e2884732a0"; +constexpr char TEST_KEYUSAGE_CERT_SIGN_CERT_1_HASH[] = "448a4b76b671cc6ebd7db9740958d2e150fc7c5f"; +constexpr char TEST_KEYUSAGE_CERT_SIGN_CERT_SPKI[] = "U4oa9edhO4+vrb95H+HrAM4+laJ/eoQOAtToNDoiciE="; +constexpr char TEST_KEYUSAGE_CERT_SIGN_CERT_SERIAL[] = "19fd0013bd35d883d38819fa081f9cdd256f6dc8"; +constexpr char TEST_KEYUSAGE_CERT_SIGN_CERT_NOT_BEFORE[] = "Feb 3 11:16:38 2021 GMT"; +constexpr char TEST_KEYUSAGE_CERT_SIGN_CERT_NOT_AFTER[] = "Feb 3 11:16:38 2023 GMT"; diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_key.pem b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_key.pem new file mode 100644 index 0000000000..d6df1edef0 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAlePKCulIVDnvv7pvlaDtAfR+PJ504y6NcxgdnTyNnRCKHBnw +lq56WWYkilc60FKdE4DehXWY9akyIlrA8nna3ckZ1PSM9GdHJRCnJWUh1RirWS8x +dhT7zbFH54BnGjj+7KT6JzsMKrP3vYa1Y5Ff/N6q9KFY5hQUWJ8XgaWqxySi+yxF +Ghevjp3Gq0FTX3thoeMYOHt5AfwlFnXaKlc+xupWaAFl5VQAM8VSG8IG+YOsxZmI +8wNdk9BIJCUIubvNykYMrwveVkgnCYB1K3wYwNNjJt9Q5bFKtRflMKQ4KzugNlWU +/PowIbDlla/NwSO866iYkROIZeW9aty5L+1KhQIDAQABAoIBAFQ4YdYvrgxlYWkB +gKE6gvGOR0AYaOUdyyzYaAtpcsjF+lQ/3wdLkkOZOP7idJGJWekTh/TFVuTx5NGY +3MFh5rCnxnP51RmezkLtUH2ajaAG9IBwHAKVV8cDzbsuUsBRNiwRpt1UOEnmRVWg +01rW3HBhTP2XizP8JFKHUdXvGD48Y5PLoFxDuP4Nth8WlcoYXdT8v0RyN/XGBS7E +w1gX3f4EtWFvFZGBeUrcaRZkdj6GLNr046GAxeY+DIdRFIxevIep06X0xAOTiuwC +tKBFj9KJqAFhCbjen2pUvrOZN3tXKlnlCDpwT/OAk/zoYHwIgJRvnYCNY7TA3AnG +NBYNIgECgYEAxT4kJEwzO8LQERcsVI4j0AXJkjV9v+U3l3xy9mjZ46AiEh146iGy +R/VzVw5f7w9xSICwk76uN4N9p+p/u1RC0HmTP30oLr/JoNmCAh5V00P/GDPVIEUc +S6Kab9NDtUU/fInNOseuu8uo0utLjWBmcWB16zaQI4qA/zhSYsZ++FUCgYEAwop7 +gJNbJ944r21lcAwHl/fnVA9wURxWPwnlnvsV3kC8c9sMPpUkuzdwmPthcwxM7oW+ ++kbSdJmqpYbprtpo90Oj35rx6Ozsp3LqlxHclGfoS/wnT6XOuUSD9VWlntdsRu1U +VjUfx4LMdMoeMY2rk8Ek3vY3uguxJ1lKATv7+XECgYBjCQKInyISXYyvKB2ADyZ4 +Ko+9M9KB6YtyKnBmvNq6agrxYY72sBid/OX+zh7pH63Xo5YFePZstT8AcsPTwUkS ++BgxBpyIbI/Gja+zdJvPShLpigz2+PxuFaTJhSA4Ah8QXviHDP/1FxsbXD1BLSgC +wVYz1d+lmMOQYi0rn1LdSQKBgFp2YOWyIAJTAJL60N+giGtvWL+rCjR9c9GOfZtG +8K1P9xH8ux3i5pi0OAS7aF5CSwfjY6IoCrczubmNGd84KvVIG8zf1TvV6FoZQuMK +6EKOauPiljkgRhe6t43+zKwnSm9U7xHDVErHFOH+Fro+QZnMh6OyZMl7pF5C0/ns +9cfRAoGAWeCaaX/IbVtCrp6IEcyc3vVI+vLxWEh6wW/8dCrxO06S+gy0NlPa8+JZ +mTgz3U5il3f5X33oeQ2zVSDqzICrvpvBdf38pi66NEJnkgVeIijPkVKFnwHcW91t +AQNkEbeiDPqcZoKKm4n8SDfTbk+i9mC/CzUMufwhbPDB71DtvZA= +-----END RSA PRIVATE KEY----- diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.cfg b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.cfg new file mode 100644 index 0000000000..3a3e3d04cb --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.cfg @@ -0,0 +1,35 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Cert +commonName_default = Test Cert +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, cRLSign +extendedKeyUsage = clientAuth, serverAuth +subjectKeyIdentifier = hash +1.2.3.4.5.6.7.8 = ASN1:UTF8String:Something +1.2.3.4.5.6.7.9 = DER:30:03:01:01:FF + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, cRLSign +extendedKeyUsage = clientAuth, serverAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +1.2.3.4.5.6.7.8 = ASN1:UTF8String:Something +1.2.3.4.5.6.7.9 = DER:30:03:01:01:FF \ No newline at end of file diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.pem b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.pem new file mode 100644 index 0000000000..fadd8b8505 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEJDCCAwygAwIBAgIUGf0AE7012IPTiBn6CB+c3SVvbckwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjEwMjAzMTExNjM4WhcNMjMw +MjAzMTExNjM4WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzESMBAGA1UEAwwJVGVzdCBDZXJ0MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6e0liMSEiPQFXUiM+Z6qzajpxGrFZP19v2Ds +1V2W5j/L5RIBfUWT4Cwsq44/r3GHjfCq++uJe828/F7Ukg9pMksRidEs0TUD/lpP +Heil6RGKRGZpzCiXYv0LZPKPDHJZyXLTXnlTAO3jE/jAujCwHNZroRs803g/5RaZ +5P3bhMdRgQDbB15v86aP5uaaXYUzcy1mGq3bSa0tAkhbYngsbSqhpLvtiAN11cgn +sh+TMs6TbcDGJXD5jXnNdHZfbrynrd/sLnvT0FoRdxEo4uO1+LG0khx4v4lAZ66n +GGMhYDBbBHA/9nSrwlZ6nmp3piQD6BKOn1xxqADdKl5Cs1OrrQIDAQABo4GnMIGk +MAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgHiMB0GA1UdJQQWMBQGCCsGAQUFBwMC +BggrBgEFBQcDATAdBgNVHQ4EFgQUnDiVycGw/XZydkQvsH23x3J8h1gwHwYDVR0j +BBgwFoAU08ELI+PDqPIyvl9UwT94OBNVA9kwFgYHKgMEBQYHCAQLDAlTb21ldGhp +bmcwEAYHKgMEBQYHCQQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABC0KwNn/rk0 +vY8YEbMkTErBHt1vM13Lg8bb0Qu/yA9Id4VqW7zzvaapUGNKvvx4t3H6ZBMMNNug +CPi2zW71HYKUEkhJZfpJtsrsRG9ezENjheW2uKDO977Cq78EBMrfWSQwfHP3iYyc +tBCHOb+v7oEZGohw3NgT4HoG3a66i9jpCenVUdWOgRvI6VbVc16C3FYrnDLdRx+h +9AyDOTe68s/tcBgn7DJyiUvXX+DFHcBfeddPfbVQ7luiT5ITPBfkzVkRePCyEySc +UONdq0bGTGEj/8YQLBvTMQ8JAxAHoRJJhKLHDJtsUR+RGTsp9IEqA7me/rvGKSla +jI9kdKxjF7I= +-----END CERTIFICATE----- diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert_info.h b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert_info.h new file mode 100644 index 0000000000..67ba22d31c --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert_info.h @@ -0,0 +1,8 @@ +// NOLINT(namespace-envoy) +constexpr char TEST_KEYUSAGE_CRL_SIGN_CERT_256_HASH[] = + "11a37b30d2fa15b30decc49bae67dfcf2b30d2fafa63ee52ebaad4503631c733"; +constexpr char TEST_KEYUSAGE_CRL_SIGN_CERT_1_HASH[] = "1e5a16317bc06aea66f0264ca4da234ba7db1758"; +constexpr char TEST_KEYUSAGE_CRL_SIGN_CERT_SPKI[] = "w5R7NEmc1JYbaSAYDJYRZZKzuI/mNbN0hoODeP2iM98="; +constexpr char TEST_KEYUSAGE_CRL_SIGN_CERT_SERIAL[] = "19fd0013bd35d883d38819fa081f9cdd256f6dc9"; +constexpr char TEST_KEYUSAGE_CRL_SIGN_CERT_NOT_BEFORE[] = "Feb 3 11:16:38 2021 GMT"; +constexpr char TEST_KEYUSAGE_CRL_SIGN_CERT_NOT_AFTER[] = "Feb 3 11:16:38 2023 GMT"; diff --git a/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_key.pem b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_key.pem new file mode 100644 index 0000000000..43380d2e19 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA6e0liMSEiPQFXUiM+Z6qzajpxGrFZP19v2Ds1V2W5j/L5RIB +fUWT4Cwsq44/r3GHjfCq++uJe828/F7Ukg9pMksRidEs0TUD/lpPHeil6RGKRGZp +zCiXYv0LZPKPDHJZyXLTXnlTAO3jE/jAujCwHNZroRs803g/5RaZ5P3bhMdRgQDb +B15v86aP5uaaXYUzcy1mGq3bSa0tAkhbYngsbSqhpLvtiAN11cgnsh+TMs6TbcDG +JXD5jXnNdHZfbrynrd/sLnvT0FoRdxEo4uO1+LG0khx4v4lAZ66nGGMhYDBbBHA/ +9nSrwlZ6nmp3piQD6BKOn1xxqADdKl5Cs1OrrQIDAQABAoIBAF214MlvYGC00MlT +3RXKmEYXGr7Svwz797oJDBdVjLPkbrvvgKU8kEbHq4V2UNDpvBICjZyp+MOd4c1/ +98wjXFMHe5koMLoGcPkeGH+0yXIa0rcgB9X/lNXU5RGlkeS8knd/BmncVIIUylkf +16U/B+4lf6xkivN0QrR1X2U6xQvlQPtiX5W4ykaogABHSxWzJP32XPmE4ojjfLd5 +DEqeyoxha3it4e+zmFpRIELStN9EboxqFPdqM7MDYjEJz8egBE5utC8Kw3jqEMat +Z3fXzuoIa8+OaEn8ILQus+BKADU46hmNrG/lOKlw+zXadDrg12TPX/tTXW8kUlYX +chKc4wkCgYEA/DGnSTBt8VL1A6ZfdQvptkDC0p6fwqSBQ5BX2dIRaVP1fDWk16+O +BfzXA609nvuSWf0DFlNcIstaJK/fxtlbCRSR3rfdB5iGbL8LmZZATkTZrD/z5K95 +xpq/1G+S7nCkX7Q8fOYvQVVYETav0BCEw0p8Nd/HAIUv9YKumdcdt5MCgYEA7XTq +p09l+TOrxNay1gPLAhdBsPP0FJJQJmr1yxvKJr2+mKZJWt3OikDzOMT8Ps90GUsO +rwujqL3hl8Ici7zB5olcsULpbRJtQxXry+YV34rlh34JEKLnpXtD453GxlZ8XgvZ +wuRqOsvBqEVUy8RMhe2ndiZ2SkiS6e7Ftuugl78CgYEAq0iSBJx232Nnc34o8RcR +Oa5MY75GZW1TOe8sK42IM9BJN347oh3iyOBLrHyaEINuh93WnfAp8JvKcoZc5vIy +6TzmQa0A2qrWCb/LghnRPRd3+4xH+rbPb3sk9IR+96DbkwCX4IB58dakBLTuvdKq +SPUq3XBJ+Wl8BDQon+XBki8CgYATSzWpxITHm9AwHTXIt+Qt1k/rHddOOJk0lepE +x4xEW5R5+MDrFiyrBR3+FdtdCyQmzfdyd6KjmlITL518KSkkHzMd4A7xYtbn5YcU +OSy7ziBaQv5fkKz7wClC/FXjVbGjPplCAac0AcxJbOC38co585Zwvi1MWds+EL2V +4E1bJwKBgQDO7fqUhSjDYNrOWoLQdShv9Ru/7hkhnWM4/Itdy+a70QXg0/uUqh3M +iPu0CcKYR1qU78dUnNJow4Ph9cITBcO1X0VAbysnKl+F3g6Yp9ilxNVtDUteP7mI +AN2Pu8bbYVMeYxdIH8FpVZf7qiESGIkAMnDmZmfVA7UWmRjAABRbzA== +-----END RSA PRIVATE KEY----- diff --git a/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.cfg b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.cfg new file mode 100644 index 0000000000..d9284904cd --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.cfg @@ -0,0 +1,36 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Server +commonName_default = Test Server +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[alt_names] +URI.1 = test.com diff --git a/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem new file mode 100644 index 0000000000..4e93ff690c --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEETCCAvmgAwIBAgIUasNGF+BDin79dyPNS4BoEOLI5VEwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjEwMjAzMTQ1MTIzWhcNMjMw +MjAzMTQ1MTIzWjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5efz3MfhC5kYFKCmVDKDAQolqKIxsuNEj +P59VdfUgr+u0pArp8lLix2gfMerYN1YkQudW7UYjz3qtSN5miQPCinNhBiqwLear +ZwNJ4MuRIFlYyx96cN+vIsxdhf8EU0l9HV44u3afV1D7OIJWPO6M8W1idyr7lF2r +8IpGeuVjB099PcVbahXRw+9jhXUyYJSpiuOAPA8ONigpMO26itkjU6ByKXf51+Ln +E8hMKLjUL6Wgn12Sab1y/C6nfnVnJ2EdB1H/mVYuuAp9SRYIosOmLCa7logWOcUY +iT4DJBC5XnV5R1U8eozHKkn6CzEr2ZJfqukMj0zJs7YboAC0xw8HAgMBAAGjgZIw +gY8wDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMBMGA1UdEQQMMAqGCHRlc3QuY29tMB0GA1UdDgQWBBS7M5f/ +9VlsFlRvdGM2KBw21tKOFjAfBgNVHSMEGDAWgBTTwQsj48Oo8jK+X1TBP3g4E1UD +2TANBgkqhkiG9w0BAQsFAAOCAQEAeGAL4on8MvnywhQa8lrjtZ+a6efWBnqHVSY6 +uHX9+WmCv7alwjksD9S5kwwOg1dGYG0lR6280gYnnjiCVRH+H8UukdJFWQ9Zb1Sg +cBITUVvOsWj9Ri7IV1Gi0I9QNcNUmpfy5ikTnbdIKbHrVJxWDa2l7f5whz0KXHgT +oNbdkxmlaJ3dFad4nZg+Fo/AbF7/BO97hfYLacH+gvEWhADMwuNuVfvQmt6+LP9I +YfyCBbdDoI7jOYniAr0eYoy0T+6GPeRGeCfPCT7jlRI+6nCBfQoF+MQtaK27l85C +VnLj4pVPdZFaiFfW8D7V3+QtX8ncAr/sbsvK2Ea6XE55pDbXuA== +-----END CERTIFICATE----- diff --git a/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert_info.h b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert_info.h new file mode 100644 index 0000000000..db4f8d2ac3 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert_info.h @@ -0,0 +1,8 @@ +// NOLINT(namespace-envoy) +constexpr char TEST_NON_SPIFFE_SAN_CERT_256_HASH[] = + "f47a053ad90243ec948df601d156911b0aaf75c9474c989fddd4f6bca44cf692"; +constexpr char TEST_NON_SPIFFE_SAN_CERT_1_HASH[] = "846c80075e0bf5261709e34d9e429a55fb6fd2c3"; +constexpr char TEST_NON_SPIFFE_SAN_CERT_SPKI[] = "Xv64COw0ddwG6Ht4kW+hqYcW8m+xnd4GlPeN8TphvcU="; +constexpr char TEST_NON_SPIFFE_SAN_CERT_SERIAL[] = "6ac34617e0438a7efd7723cd4b806810e2c8e551"; +constexpr char TEST_NON_SPIFFE_SAN_CERT_NOT_BEFORE[] = "Feb 3 14:51:23 2021 GMT"; +constexpr char TEST_NON_SPIFFE_SAN_CERT_NOT_AFTER[] = "Feb 3 14:51:23 2023 GMT"; diff --git a/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_key.pem b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_key.pem new file mode 100644 index 0000000000..83e920bfc1 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuXn89zH4QuZGBSgplQygwEKJaiiMbLjRIz+fVXX1IK/rtKQK +6fJS4sdoHzHq2DdWJELnVu1GI896rUjeZokDwopzYQYqsC3mq2cDSeDLkSBZWMsf +enDfryLMXYX/BFNJfR1eOLt2n1dQ+ziCVjzujPFtYncq+5Rdq/CKRnrlYwdPfT3F +W2oV0cPvY4V1MmCUqYrjgDwPDjYoKTDtuorZI1Ogcil3+dfi5xPITCi41C+loJ9d +kmm9cvwup351ZydhHQdR/5lWLrgKfUkWCKLDpiwmu5aIFjnFGIk+AyQQuV51eUdV +PHqMxypJ+gsxK9mSX6rpDI9MybO2G6AAtMcPBwIDAQABAoIBAQCBL41ZY62mcxtM +Fjg4P45ruyxZC5sbUvMgGP1SmhE9TirfK+8KGaVPnVJRgAQxywEtyoe1TRiwcp/g +uENnqYE77BEHADOVeLMUqXBp8a/4Ck8RAJGRR7MVGii770u7aINkKKNq4m9x9nBK +OobVqCUDeFkW3yfKCQHhc23sP0csXEgIj3LCRTDPOhX7AF+a4ECfz8eDcjLP8TKM +wqfTXu9TwiZMibGmJgrM2CIeW9vR0xKrEuhFGoL7I1wX0F+Be7kkbqY0NLJxREwY +QjeABNb3vfF92EyJ7QrYrxLDYdIVnrRyMqcoT3KGR85Bc/aQwkDej107apHyhofm +PIFDcIkxAoGBAOfZtCIxzi4Zw7Xu3XiUKQEifYnm4GV8vlhFOuzF7CEweOOXJ9Ko +7yxVn+aNdv0UqYOmsMd0GTXyUhvJSPLxEUCJC3N7eOuzWNLhiNtOMfvCY2iLhI4a +mHI2UavmWgNx7ZA7Q/vK5MDkEHlCAw8AMZ47hOLNPqqL+IjkODjdvm+ZAoGBAMzL +uUQ3Y4ZhKcqqBEommATE4YIpjEMWepua3g8N9wzdbaD5FtJM/ZfNSwl/wjFPZhK7 +c+pFLOZLl245uj52RS1QQ+6EbKai4jC8NEEb8Zo9rerWFkJRIs705Jxmq0yYAqdV +UJhycJeaVmX1bJJU4iQzCvF8nrLYvZpFLr9a4RefAoGAf1UgSjtiSg1aYBv8xFFS +p83ido83JGW7QE1dTFZzFdNCQXRtqZOgL5AjDoMZG2tyodw1cIVBp1AbailFCC// +Upsxj837Hi/Uk5TMDe3HI8ahw/QD6+uNWASfHDKZsxSp7TGvZ6UJtypKJd5sQZvQ +pF953vnr9cyDxeLZQdn+0dkCgYEAvhyogaEBbO+pwg8OKF+nY1X5GcHECUtGykh7 +t3H5UyIC8RoKi3MZPuA+tjS5atkQIneNZX6N7cNicdp5AB7+nNAUH8kiq5Ytb5xm +zcJJCCwV1RikVS/IpmJEDsRoZJQAcqIKTVp/Ft0ZM1EfVsAhpgUUNZTAJbp6WEm8 +2bpdlnUCgYEApAqC2nnimtbbN4XSyeLqiU9V2BCJoIbbtpM8Vy0gJUGFDUHgJKFz +ag01gHoh9wTSbsEyGi4qpeDLA5m9oRVlpcX6qOt7XCaGyl4mJbTzls8BFc+yh4Ty +2dAoFYZxlVpslZ/khFeVlmYGjU1Nr1gMSluWVrjou02jte2CX/x8ez4= +-----END RSA PRIVATE KEY----- diff --git a/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.cfg b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.cfg new file mode 100644 index 0000000000..30dc157d94 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.cfg @@ -0,0 +1,38 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = US +countryName_default = US +stateOrProvinceName = California +stateOrProvinceName_default = California +localityName = San Francisco +localityName_default = San Francisco +organizationName = Lyft +organizationName_default = Lyft +organizationalUnitName = Lyft Engineering +organizationalUnitName_default = Lyft Engineering +commonName = Test Server +commonName_default = Test Server +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[v3_ca] +basicConstraints = critical, CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[alt_names] +DNS.1 = envoy.com +URI.1 = spiffe://example.com/workload +email.1 = envoy@example.com diff --git a/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem new file mode 100644 index 0000000000..0df00683d6 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIERDCCAyygAwIBAgIULXaFFIBpExYK/oCHQmTNjJI1SxwwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjEwMzIyMDMwOTIxWhcNMjMw +MzIyMDMwOTIxWjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwEp4BaoX0h8fAro5TKV+nQmi7KaZwpRNR +9K2aXgUG6gFbqW+fUx6f9bLcvHxNR9N8WfDvRIlQHDm6YZaqgR8zo2vaW+Mszp4X +qJYFn0BM38dJ838ATiLuEVmJqOZdOsXicE5aDcvCXLt9CIz9w+ycQACTemPhY7b3 +95MUK1yrwmJTfIXoviev3n03u+tBPTrItp4SAd25enyVaVqvUPe9fouNOo6JPBVB +5I2QFhJ+4TIYImutHWxUp/BMEGKuGQTjpoezPMFCtLFq6RifJI4fwBYlBCrCE0Lu +b1Zoe1S5Q3QyVf91/H8nt9W2LSdBWGKdD0WeSNtwtyNHeV8faG7JAgMBAAGjgcUw +gcIwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMEYGA1UdEQQ/MD2CCWVudm95LmNvbYYdc3BpZmZlOi8vZXhh +bXBsZS5jb20vd29ya2xvYWSBEWVudm95QGV4YW1wbGUuY29tMB0GA1UdDgQWBBR5 +R8gqQYBGL9w9hTDtC2XvTgGaYTAfBgNVHSMEGDAWgBTTwQsj48Oo8jK+X1TBP3g4 +E1UD2TANBgkqhkiG9w0BAQsFAAOCAQEAIuNsPA2j0unE9OLz/y0fcnzTx5xRCYyv +Xxv/Ckxwbwh5sLwSN/qwLOVWajcwjwC5X0j3+XH0er1PdcTg1LlrLANDtzClZe2P +JVQl765b3TkSkD07Qpe2K549plwYJP0oTHRuhVt4zOUYKh9K7g4GuSYMdcynKLs6 +fbL9CntPy00krJIaqTfECKXVHTEEPpxoHVYa80D3117nf34PmSF+P2uXUHq3LHIu +vKLRZHRSfGj6+ZmnjAOf/njTFXSpdwOiIfSxUUzaQfx2wVN7KpFTlW5MRUy+qxWX +zltfig9EaXC6532BZZIIIx11nG4b/vTN0JVQ3ud/ADwvQ9E/hRZBlg== +-----END CERTIFICATE----- diff --git a/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert_info.h b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert_info.h new file mode 100644 index 0000000000..abfcc89292 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert_info.h @@ -0,0 +1,8 @@ +// NOLINT(namespace-envoy) +constexpr char TEST_SPIFFE_SAN_CERT_256_HASH[] = + "a47deda8919638a962a988a602116229ffe5a7bbc65cfb38a418e1e07753e8f9"; +constexpr char TEST_SPIFFE_SAN_CERT_1_HASH[] = "1ca4cde36e36dd032766680b5b81e7019b7c9fd5"; +constexpr char TEST_SPIFFE_SAN_CERT_SPKI[] = "x9Klc+szHFuefJihflHvoXnIQfHcpHT6yQ5cDRIJOI8="; +constexpr char TEST_SPIFFE_SAN_CERT_SERIAL[] = "2d768514806913160afe80874264cd8c92354b1c"; +constexpr char TEST_SPIFFE_SAN_CERT_NOT_BEFORE[] = "Mar 22 03:09:21 2021 GMT"; +constexpr char TEST_SPIFFE_SAN_CERT_NOT_AFTER[] = "Mar 22 03:09:21 2023 GMT"; diff --git a/test/extensions/transport_sockets/tls/test_data/spiffe_san_key.pem b/test/extensions/transport_sockets/tls/test_data/spiffe_san_key.pem new file mode 100644 index 0000000000..cfd2222a7f --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsBKeAWqF9IfHwK6OUylfp0JouymmcKUTUfStml4FBuoBW6lv +n1Men/Wy3Lx8TUfTfFnw70SJUBw5umGWqoEfM6Nr2lvjLM6eF6iWBZ9ATN/HSfN/ +AE4i7hFZiajmXTrF4nBOWg3Lwly7fQiM/cPsnEAAk3pj4WO29/eTFCtcq8JiU3yF +6L4nr959N7vrQT06yLaeEgHduXp8lWlar1D3vX6LjTqOiTwVQeSNkBYSfuEyGCJr +rR1sVKfwTBBirhkE46aHszzBQrSxaukYnySOH8AWJQQqwhNC7m9WaHtUuUN0MlX/ +dfx/J7fVti0nQVhinQ9FnkjbcLcjR3lfH2huyQIDAQABAoIBAH3fkV3pzYIXX9J7 +9/uz5FIqw7yp9fcpzDoW9dUZyfY7bGUfKFF6lrY8bHYpuaN16ddIZVpoYNIIm6yG +/7M9RBUii4Q7lJj/zT2UpEu4obtTb6GKlgydz5LqjFxBhw63aaiMKTdwDW8R5Gdq +qYDxhEHf74l/QdBr/O4g6+DLbyDKUjQQlJbQA3OODIOM4CGDJ2/ke3XKLOFF2jAs +nXjR/EikFiHnVz+ZVl4dUVO1ED4GTSBMxg6cg0vAwSh/Hxe5+VP+3cB5OrM8VEuK +6gn3UNUFQtGFGzUlYM7mY5brG6itw0bYMMILkCKGCzJUkNxdzG1RWLtGPEFsrPUs +L21d4FECgYEA4/6bE2IEHW/a7MchcvMwgvoJqr7We+kxzpCkLucJRqKGslK4wtCA +SC0f6jn7UlG21ZbH1EpX4fELGsi76FxUoa70iJrE+sFOM7gXlDqIqxwDD4Mzbrcu +bfgxLQuXI50fONBQpnhRV6zOjisOPwj8LNGIpfJ5edNHZd/9qMNIW08CgYEAxbNO +MxTadklykh0xbnTkTQuyuVmm2q49NVGDa0QvXCiJ61YaaQwce0biwjw2v6KP8/Qc +EfU8pUrfDYuL3QF76DYHB2H0GODfd4OR6ErPtsxHM2ZjoAha+Zzr51Qcw1mXzpob +P+NO9BXLEKOUNoZ7J2snH5dbw+zmZ72AoG4ErmcCgYEAhpnSrc/JBCP8SJuLWNTQ +CBNbz60Y7VCP8hach4AojsX25aJGp+T2RyY02FBg2omZemGpGeDGH4Lx/l8R6K1R +3Z5viZJtxmiHJauCaCVieDpyaB8aJzwIMi47wg88rG7fdugtJQqVY0CHNZGfuIEy +d7dAiFAtfUnJthBWLeMUfasCgYAq0iqG7+Zdpoz1TvNs/E6SzYzZjqyQQxso2E4B +xtQHPxSYb57qz9+/Z9wnvAkTuu85JM+LqDWJi80CTw5tQSwIYONm2vWXCeX9KfdJ +fCK/ckGdxXigqg6icDW8dkCAo4A5FLIARYjNX4cLRJMOuFQmdpQTyKHsrvw8zhP1 +K79B0wKBgGzX2ypMSfBaKf/wpX9SIW4E2vZT0MdLiRrjm0Yi1DkTYw25vC/Mj/Vs +9rsyAYnUa28GSizJK3/tOI0jcRXZ15QcAvnHwWTpMkXydmEN31VfRV5R4Xc1mSoR ++vyWUcy5sfGawNxpiKekmMUIBmabNyMcqKacUTtt8RX3qg5TRIlj +-----END RSA PRIVATE KEY----- diff --git a/test/extensions/transport_sockets/tls/utility_test.cc b/test/extensions/transport_sockets/tls/utility_test.cc index bcf3958cdc..9a9207734d 100644 --- a/test/extensions/transport_sockets/tls/utility_test.cc +++ b/test/extensions/transport_sockets/tls/utility_test.cc @@ -43,6 +43,14 @@ TEST(UtilityTest, TestGetSubjectAlternateNamesWithUri) { EXPECT_EQ(1, subject_alt_names.size()); } +TEST(UtilityTest, TestGetSubjectAlternateNamesWithEmail) { + bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem")); + const auto& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_EMAIL); + EXPECT_EQ(1, subject_alt_names.size()); + EXPECT_EQ("envoy@example.com", subject_alt_names.front()); +} + TEST(UtilityTest, TestGetSubjectAlternateNamesWithNoSAN) { bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem")); diff --git a/test/integration/ssl_utility.cc b/test/integration/ssl_utility.cc index aab3dd5d4a..4a9ca3ab3e 100644 --- a/test/integration/ssl_utility.cc +++ b/test/integration/ssl_utility.cc @@ -39,6 +39,14 @@ createClientSslTransportSocketFactory(const ClientSslTransportOptions& options, filename: "{{ test_rundir }}/test/config/integration/certs/client_ecdsacert.pem" private_key: filename: "{{ test_rundir }}/test/config/integration/certs/client_ecdsakey.pem" +)EOF"; + } else if (options.use_expired_spiffe_cert_) { + yaml_plain += R"EOF( + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/expired_spiffe_san_key.pem" )EOF"; } else { yaml_plain += R"EOF( diff --git a/test/integration/ssl_utility.h b/test/integration/ssl_utility.h index afaf57eae0..1be73ef324 100644 --- a/test/integration/ssl_utility.h +++ b/test/integration/ssl_utility.h @@ -36,25 +36,31 @@ struct ClientSslTransportOptions { return *this; } + ClientSslTransportOptions& setSni(absl::string_view sni) { + sni_ = std::string(sni); + return *this; + } + ClientSslTransportOptions& setTlsVersion( envoy::extensions::transport_sockets::tls::v3::TlsParameters::TlsProtocol tls_version) { tls_version_ = tls_version; return *this; } - ClientSslTransportOptions& setSni(absl::string_view sni) { - sni_ = std::string(sni); + ClientSslTransportOptions& setUseExpiredSpiffeCer(bool use_expired) { + use_expired_spiffe_cert_ = use_expired; return *this; } bool alpn_{}; - bool san_{}; - bool client_ecdsa_cert_{}; + bool client_ecdsa_cert_{false}; std::vector cipher_suites_{}; + bool san_{}; std::string sigalgs_; std::string sni_; envoy::extensions::transport_sockets::tls::v3::TlsParameters::TlsProtocol tls_version_{ envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLS_AUTO}; + bool use_expired_spiffe_cert_{}; }; Network::TransportSocketFactoryPtr diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 3a13a80bd3..11dea55c47 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -3,6 +3,7 @@ #include #include +#include "envoy/api/api.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "envoy/ssl/certificate_validation_context_config.h" #include "envoy/ssl/connection.h" @@ -152,6 +153,9 @@ class MockCertificateValidationContextConfig : public CertificateValidationConte MOCK_METHOD(const std::vector&, verifyCertificateHashList, (), (const)); MOCK_METHOD(const std::vector&, verifyCertificateSpkiList, (), (const)); MOCK_METHOD(bool, allowExpiredCertificate, (), (const)); + MOCK_METHOD(const absl::optional&, + customValidatorConfig, (), (const)); + MOCK_METHOD(Api::Api&, api, (), (const)); MOCK_METHOD(envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: TrustChainVerification, trustChainVerification, (), (const)); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index c59099857c..7eb7bdb9f8 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -3525,8 +3525,8 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidIntermediate )EOF"), Network::Address::IpVersion::v4); - EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), - EnvoyException, "Failed to load certificate chain from "); + EXPECT_THROW_WITH_REGEX(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), + EnvoyException, "^Failed to load certificate chain from "); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) { @@ -3545,8 +3545,8 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) )EOF", Network::Address::IpVersion::v4); - EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), - EnvoyException, "Failed to load private key from "); + EXPECT_THROW_WITH_REGEX(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), + EnvoyException, "^Failed to load private key from "); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 0a28f00119..e780251762 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -41,6 +41,7 @@ CHMOD CHLOS CHLOs CIDR +CITT CLA CLI CMSG @@ -318,6 +319,7 @@ STL STRLEN STS SVG +SVID Symbolizer TBD TCLAP @@ -488,7 +490,7 @@ ci ciphersuite ciphersuites circllhist -CITT +clientcert cloneable cloneability cmd @@ -521,6 +523,7 @@ coverity cplusplus cpuset creds +cRLSign crypto cryptographic cryptographically