From 9b6832f6511cb90c0d8f7b45415dc154f0b65300 Mon Sep 17 00:00:00 2001 From: Daniel Grimm Date: Thu, 29 Apr 2021 16:55:32 +0200 Subject: [PATCH] MAISTRA-2242 Cherry-pick https://github.com/envoyproxy/envoy/pull/14884 --- .../transport_sockets/tls/v3/common.proto | 15 +- .../tls/v3/tls_spiffe_validator_config.proto | 54 ++ .../tls/v4alpha/common.proto | 15 +- .../v4alpha/tls_spiffe_validator_config.proto | 60 +++ bazel/envoy_library.bzl | 31 ++ docs/root/version_history/current.rst | 29 + .../transport_sockets/tls/v3/common.proto | 15 +- .../tls/v3/tls_spiffe_validator_config.proto | 54 ++ .../tls/v4alpha/common.proto | 15 +- .../v4alpha/tls_spiffe_validator_config.proto | 60 +++ include/envoy/ssl/BUILD | 4 +- .../certificate_validation_context_config.h | 14 + source/common/ssl/BUILD | 1 + ...tificate_validation_context_config_impl.cc | 8 +- ...rtificate_validation_context_config_impl.h | 9 + source/extensions/extensions_build_config.bzl | 1 - .../quiche/envoy_quic_proof_verifier.cc | 1 + .../tls/cert_validator/BUILD | 41 ++ .../tls/cert_validator/cert_validator.h | 86 +++ .../tls/cert_validator/default_validator.cc | 494 ++++++++++++++++++ .../tls/cert_validator/factory.cc | 21 + .../tls/cert_validator/factory.h | 36 ++ .../tls/cert_validator/spiffe/BUILD | 45 ++ .../cert_validator/spiffe/spiffe_validator.cc | 264 ++++++++++ .../cert_validator/spiffe/spiffe_validator.h | 77 +++ .../tls/cert_validator/well_known_names.h | 29 + .../transport_sockets/tls/context_impl.cc | 15 + test.yaml | 13 + test/config/utility.cc | 12 +- test/config/utility.h | 7 + .../quiche/envoy_quic_proof_source_test.cc | 2 + .../quiche/envoy_quic_proof_verifier_test.cc | 4 + .../tls/cert_validator/BUILD | 49 ++ .../tls/cert_validator/factory_test.cc | 28 + .../tls/cert_validator/spiffe/BUILD | 28 + .../spiffe/spiffe_validator_test.cc | 403 ++++++++++++++ .../tls/cert_validator/util.h | 83 +++ .../transport_sockets/tls/integration/BUILD | 3 + .../tls/integration/ssl_integration_test.cc | 95 +++- .../tls/integration/ssl_integration_test.h | 1 + .../transport_sockets/tls/test_data/certs.sh | 12 + .../tls/test_data/keyusage_cert_sign_cert.cfg | 35 ++ .../tls/test_data/keyusage_cert_sign_cert.pem | 25 + .../test_data/keyusage_cert_sign_cert_info.h | 8 + .../tls/test_data/keyusage_cert_sign_key.pem | 27 + .../tls/test_data/keyusage_crl_sign_cert.cfg | 35 ++ .../tls/test_data/keyusage_crl_sign_cert.pem | 25 + .../test_data/keyusage_crl_sign_cert_info.h | 8 + .../tls/test_data/keyusage_crl_sign_key.pem | 27 + .../tls/test_data/non_spiffe_san_cert.cfg | 36 ++ .../tls/test_data/non_spiffe_san_cert.pem | 24 + .../tls/test_data/non_spiffe_san_cert_info.h | 8 + .../tls/test_data/non_spiffe_san_key.pem | 27 + .../tls/test_data/spiffe_san_cert.cfg | 37 ++ .../tls/test_data/spiffe_san_cert.pem | 25 + .../tls/test_data/spiffe_san_cert_info.h | 8 + .../tls/test_data/spiffe_san_key.pem | 27 + test/mocks/ssl/mocks.h | 4 + tools/spelling/spelling_dictionary.txt | 5 +- 59 files changed, 2610 insertions(+), 15 deletions(-) create mode 100644 api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto create mode 100644 api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto create mode 100644 generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto create mode 100644 generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto create mode 100644 source/extensions/transport_sockets/tls/cert_validator/BUILD create mode 100644 source/extensions/transport_sockets/tls/cert_validator/cert_validator.h create mode 100644 source/extensions/transport_sockets/tls/cert_validator/default_validator.cc create mode 100644 source/extensions/transport_sockets/tls/cert_validator/factory.cc create mode 100644 source/extensions/transport_sockets/tls/cert_validator/factory.h create mode 100644 source/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD create mode 100644 source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc create mode 100644 source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h create mode 100644 source/extensions/transport_sockets/tls/cert_validator/well_known_names.h create mode 100644 test.yaml create mode 100644 test/extensions/transport_sockets/tls/cert_validator/BUILD create mode 100644 test/extensions/transport_sockets/tls/cert_validator/factory_test.cc create mode 100644 test/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD create mode 100644 test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc create mode 100644 test/extensions/transport_sockets/tls/cert_validator/util.h create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.cfg create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_cert_info.h create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_cert_sign_key.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.cfg create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_cert_info.h create mode 100644 test/extensions/transport_sockets/tls/test_data/keyusage_crl_sign_key.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.cfg create mode 100644 test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert_info.h create mode 100644 test/extensions/transport_sockets/tls/test_data/non_spiffe_san_key.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.cfg create mode 100644 test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem create mode 100644 test/extensions/transport_sockets/tls/test_data/spiffe_san_cert_info.h create mode 100644 test/extensions/transport_sockets/tls/test_data/spiffe_san_key.pem 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/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index 5eb90df500..e0bef06ab2 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -62,6 +62,37 @@ EXTENSION_SECURITY_POSTURES = [ "data_plane_agnostic", ] +# Extension categories as defined by factories +EXTENSION_CATEGORIES = [ + "envoy.access_loggers", + "envoy.bootstrap", + "envoy.clusters", + "envoy.compression.compressor", + "envoy.compression.decompressor", + "envoy.filters.http", + "envoy.filters.listener", + "envoy.filters.network", + "envoy.filters.udp_listener", + "envoy.grpc_credentials", + "envoy.guarddog_actions", + "envoy.health_checkers", + "envoy.internal_redirect_predicates", + "envoy.io_socket", + "envoy.rate_limit_descriptors", + "envoy.resource_monitors", + "envoy.retry_host_predicates", + "envoy.retry_priorities", + "envoy.stats_sinks", + "envoy.thrift_proxy.filters", + "envoy.tracers", + "envoy.transport_sockets.downstream", + "envoy.transport_sockets.upstream", + "envoy.tls.cert_validator", + "envoy.upstreams", + "envoy.wasm.runtime", + "DELIBERATELY_OMITTED", +] + EXTENSION_STATUS_VALUES = [ # This extension is stable and is expected to be production usable. "stable", diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f50abcf6e4..cb8b0cbddf 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -108,5 +108,34 @@ 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. +* access log: added the :ref:`formatters ` extension point for custom formatters (command operators). +* access log: support command operator: %FILTER_CHAIN_NAME% for the downstream tcp and http request. +* access log: support command operator: %REQUEST_HEADERS_BYTES%, %RESPONSE_HEADERS_BYTES%, and %RESPONSE_TRAILERS_BYTES%. +* compression: add brotli :ref:`compressor ` and :ref:`decompressor `. +* config: add `envoy.features.fail_on_any_deprecated_feature` runtime key, which matches the behaviour of compile-time flag `ENVOY_DISABLE_DEPRECATED_FEATURES`, i.e. use of deprecated fields will cause a crash. +* config: the ``Node`` :ref:`dynamic context parameters ` are populated in discovery requests when set on the server instance. +* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. +* ext_authz: added :ref:`response_headers_to_add ` to support sending response headers to downstream clients on OK authorization checks via gRPC. +* ext_authz: added :ref:`allowed_client_headers_on_success ` to support sending response headers to downstream clients on OK external authorization checks via HTTP. +* grpc_json_transcoder: added :ref:`request_validation_options ` to reject invalid requests early. +* grpc_json_transcoder: filter can now be configured on per-route/per-vhost level as well. Leaving empty list of services in the filter configuration disables transcoding on the specific route. +* http: added support for `Envoy::ScopeTrackedObject` for HTTP/1 and HTTP/2 dispatching. Crashes while inside the dispatching loop should dump debug information. +* http: added support for :ref:`preconnecting `. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1. +* http: added new runtime config `envoy.reloadable_features.check_unsupported_typed_per_filter_config`, the default value is true. When the value is true, envoy will reject virtual host-specific typed per filter config when the filter doesn't support it. +* http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to false. +* json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature `envoy.reloadable_features.remove_legacy_json`. +* log: added a new custom flag ``%j`` to the log pattern to print the actual message to log as JSON escaped string. +* original_dst: added support for :ref:`Original Destination ` on Windows. This enables the use of Envoy as a sidecar proxy on Windows. +* overload: add support for scaling :ref:`transport connection timeouts`. This can be used to reduce the TLS handshake timeout in response to overload. +* postgres: added ability to :ref:`terminate SSL`. +* route config: added :ref:`allow_post field ` for allowing POST payload as raw TCP. +* route config: added :ref:`max_direct_response_body_size_bytes ` to set maximum :ref:`direct response body ` size in bytes. If not specified the default remains 4096 bytes. +* server: added *fips_mode* to :ref:`server compilation settings ` related statistic. +* tcp_proxy: add support for converting raw TCP streams into HTTP/1.1 CONNECT requests. See :ref:`upgrade documentation ` for details. +* tcp_proxy: added a :ref:`use_post field ` for using HTTP POST to proxy TCP streams. +* tcp_proxy: added a :ref:`headers_to_add field ` for setting additional headers to the HTTP requests for TCP proxing. +* thrift_proxy: added a :ref:`max_requests_per_connection field ` for setting maximum requests for per downstream connection. +* 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..a8a50fa014 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -32,7 +32,13 @@ 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()) { + 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()) { if (!certificate_revocation_list_.empty()) { throw EnvoyException(fmt::format("Failed to load CRL from {} without trusted CA", 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..b2fe87ebf1 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -217,7 +217,6 @@ EXTENSIONS = { # "envoy.watchdog.profile_action": "//source/extensions/watchdog/profile_action:config", "envoy.watchdog.abort_action": "//source/extensions/watchdog/abort_action: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/cert_validator/BUILD b/source/extensions/transport_sockets/tls/cert_validator/BUILD new file mode 100644 index 0000000000..9c9340cf56 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/BUILD @@ -0,0 +1,41 @@ +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", + ], + hdrs = [ + "cert_validator.h", + "default_validator.h", + "factory.h", + "well_known_names.h", + ], + external_deps = [ + "ssl", + "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/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..c9177d3903 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc @@ -0,0 +1,494 @@ +#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/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/x509v3.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +DefaultCertValidator::DefaultCertValidator( + const Envoy::Ssl::CertificateValidationContextConfig* config, SslStats& stats, + TimeSource& time_source) + : config_(config), stats_(stats), time_source_(time_source) { + if (config_ != nullptr) { + allow_untrusted_certificate_ = config_->trustChainVerification() == + envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED; + } +}; + +int DefaultCertValidator::initializeSslContexts(std::vector contexts, + bool provides_certificates) { + + int verify_mode = SSL_VERIFY_NONE; + int verify_mode_validation_context = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + if (config_ != nullptr) { + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext:: + TrustChainVerification verification = config_->trustChainVerification(); + if (verification == envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED) { + verify_mode = SSL_VERIFY_PEER; // Ensure client-certs will be requested even if we have + // nothing to verify against + verify_mode_validation_context = SSL_VERIFY_PEER; + } + } + + if (config_ != nullptr && !config_->caCert().empty() && !provides_certificates) { + ca_file_path_ = config_->caCertPath(); + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->caCert().data()), config_->caCert().size())); + RELEASE_ASSERT(bio != nullptr, ""); + // Based on BoringSSL's X509_load_cert_crl_file(). + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificates from ", config_->caCertPath())); + } + + for (auto& ctx : contexts) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + bool has_crl = false; + for (const X509_INFO* item : list.get()) { + if (item->x509) { + X509_STORE_add_cert(store, item->x509); + if (ca_cert_ == nullptr) { + X509_up_ref(item->x509); + ca_cert_.reset(item->x509); + } + } + if (item->crl) { + X509_STORE_add_crl(store, item->crl); + has_crl = true; + } + } + if (ca_cert_ == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load trusted CA certificates from ", config_->caCertPath())); + } + if (has_crl) { + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + verify_mode = SSL_VERIFY_PEER; + verify_trusted_ca_ = true; + + // NOTE: We're using SSL_CTX_set_cert_verify_callback() instead of X509_verify_cert() + // directly. However, our new callback is still calling X509_verify_cert() under + // the hood. Therefore, to ignore cert expiration, we need to set the callback + // for X509_verify_cert to ignore that error. + if (config_->allowExpiredCertificate()) { + X509_STORE_set_verify_cb(store, DefaultCertValidator::ignoreCertificateExpirationCallback); + } + } + } + + if (config_ != nullptr && !config_->certificateRevocationList().empty()) { + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(config_->certificateRevocationList().data()), + config_->certificateRevocationList().size())); + RELEASE_ASSERT(bio != nullptr, ""); + + // Based on BoringSSL's X509_load_cert_crl_file(). + bssl::UniquePtr list( + PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); + if (list == nullptr) { + throw EnvoyException( + absl::StrCat("Failed to load CRL from ", config_->certificateRevocationListPath())); + } + + for (auto& ctx : contexts) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + for (const X509_INFO* item : list.get()) { + if (item->crl) { + X509_STORE_add_crl(store, item->crl); + } + } + + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + } + + const Envoy::Ssl::CertificateValidationContextConfig* cert_validation_config = config_; + if (cert_validation_config != nullptr) { + if (!cert_validation_config->verifySubjectAltNameList().empty()) { + verify_subject_alt_name_list_ = cert_validation_config->verifySubjectAltNameList(); + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->subjectAltNameMatchers().empty()) { + for (const envoy::type::matcher::v3::StringMatcher& matcher : + cert_validation_config->subjectAltNameMatchers()) { + subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher)); + } + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->verifyCertificateHashList().empty()) { + for (auto hash : cert_validation_config->verifyCertificateHashList()) { + // Remove colons from the 95 chars long colon-separated "fingerprint" + // in order to get the hex-encoded string. + if (hash.size() == 95) { + hash.erase(std::remove(hash.begin(), hash.end(), ':'), hash.end()); + } + const auto& decoded = Hex::decode(hash); + if (decoded.size() != SHA256_DIGEST_LENGTH) { + throw EnvoyException(absl::StrCat("Invalid hex-encoded SHA-256 ", hash)); + } + verify_certificate_hash_list_.push_back(decoded); + } + verify_mode = verify_mode_validation_context; + } + + if (!cert_validation_config->verifyCertificateSpkiList().empty()) { + for (const auto& hash : cert_validation_config->verifyCertificateSpkiList()) { + const auto decoded = Base64::decode(hash); + if (decoded.size() != SHA256_DIGEST_LENGTH) { + throw EnvoyException(absl::StrCat("Invalid base64-encoded SHA-256 ", hash)); + } + verify_certificate_spki_list_.emplace_back(decoded.begin(), decoded.end()); + } + verify_mode = verify_mode_validation_context; + } + } + + return verify_mode; +} + +int DefaultCertValidator::doVerifyCertChain( + X509_STORE_CTX* store_ctx, Ssl::SslExtendedSocketInfo* ssl_extended_info, X509& leaf_cert, + const Network::TransportSocketOptions* transport_socket_options) { + if (verify_trusted_ca_) { + int ret = X509_verify_cert(store_ctx); + if (ssl_extended_info) { + ssl_extended_info->setCertificateValidationStatus( + ret == 1 ? Envoy::Ssl::ClientValidationStatus::Validated + : Envoy::Ssl::ClientValidationStatus::Failed); + } + + if (ret <= 0) { + stats_.fail_verify_error_.inc(); + return allow_untrusted_certificate_ ? 1 : ret; + } + } + + Envoy::Ssl::ClientValidationStatus validated = verifyCertificate( + &leaf_cert, + transport_socket_options && + !transport_socket_options->verifySubjectAltNameListOverride().empty() + ? transport_socket_options->verifySubjectAltNameListOverride() + : verify_subject_alt_name_list_, + subject_alt_name_matchers_); + + if (ssl_extended_info) { + if (ssl_extended_info->certificateValidationStatus() == + Envoy::Ssl::ClientValidationStatus::NotValidated) { + ssl_extended_info->setCertificateValidationStatus(validated); + } else if (validated != Envoy::Ssl::ClientValidationStatus::NotValidated) { + ssl_extended_info->setCertificateValidationStatus(validated); + } + } + + return allow_untrusted_certificate_ ? 1 + : (validated != Envoy::Ssl::ClientValidationStatus::Failed); +} + +int DefaultCertValidator::ignoreCertificateExpirationCallback(int ok, X509_STORE_CTX* store_ctx) { + if (!ok) { + int err = X509_STORE_CTX_get_error(store_ctx); + if (err == X509_V_ERR_CERT_HAS_EXPIRED || err == X509_V_ERR_CERT_NOT_YET_VALID) { + return 1; + } + } + + return ok; +} + +Envoy::Ssl::ClientValidationStatus DefaultCertValidator::verifyCertificate( + X509* cert, const std::vector& verify_san_list, + const std::vector& subject_alt_name_matchers) { + Envoy::Ssl::ClientValidationStatus validated = Envoy::Ssl::ClientValidationStatus::NotValidated; + + if (!verify_san_list.empty()) { + if (!verifySubjectAltName(cert, verify_san_list)) { + stats_.fail_verify_san_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + validated = Envoy::Ssl::ClientValidationStatus::Validated; + } + + if (!subject_alt_name_matchers.empty() && !matchSubjectAltName(cert, subject_alt_name_matchers)) { + stats_.fail_verify_san_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + + if (!verify_certificate_hash_list_.empty() || !verify_certificate_spki_list_.empty()) { + const bool valid_certificate_hash = + !verify_certificate_hash_list_.empty() && + verifyCertificateHashList(cert, verify_certificate_hash_list_); + const bool valid_certificate_spki = + !verify_certificate_spki_list_.empty() && + verifyCertificateSpkiList(cert, verify_certificate_spki_list_); + + if (!valid_certificate_hash && !valid_certificate_spki) { + stats_.fail_verify_cert_hash_.inc(); + return Envoy::Ssl::ClientValidationStatus::Failed; + } + + validated = Envoy::Ssl::ClientValidationStatus::Validated; + } + + return validated; +} + +bool DefaultCertValidator::verifySubjectAltName(X509* cert, + const std::vector& subject_alt_names) { + bssl::UniquePtr san_names( + static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + if (san_names == nullptr) { + return false; + } + for (const GENERAL_NAME* general_name : san_names.get()) { + const std::string san = Utility::generalNameAsString(general_name); + for (auto& config_san : subject_alt_names) { + if (general_name->type == GEN_DNS ? dnsNameMatch(config_san, san.c_str()) + : config_san == san) { + return true; + } + } + } + return false; +} + +bool DefaultCertValidator::dnsNameMatch(const absl::string_view dns_name, + const absl::string_view pattern) { + const std::string lower_case_dns_name = absl::AsciiStrToLower(dns_name); + const std::string lower_case_pattern = absl::AsciiStrToLower(pattern); + if (lower_case_dns_name == lower_case_pattern) { + return true; + } + + size_t pattern_len = lower_case_pattern.length(); + if (pattern_len > 1 && lower_case_pattern[0] == '*' && lower_case_pattern[1] == '.') { + if (lower_case_dns_name.length() > pattern_len - 1) { + const size_t off = lower_case_dns_name.length() - pattern_len + 1; + return lower_case_dns_name.substr(0, off).find('.') == std::string::npos && + lower_case_dns_name.substr(off, pattern_len - 1) == + lower_case_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** a, const X509_NAME** 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())); + } + 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/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..d6f74254f3 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD @@ -0,0 +1,45 @@ +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", + ], + category = "envoy.tls.cert_validator", + 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..72a680ead5 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc @@ -0,0 +1,264 @@ +#include "extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "envoy/common/pure.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.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/common/regex.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/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); + + SPIFFEConfig message; + Config::Utility::translateOpaqueConfig(config->customValidatorConfig().value().typed_config(), + ProtobufWkt::Struct(), + ProtobufMessage::getStrictValidationVisitor(), message); + + 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** a, const X509_NAME** 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. + 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 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. + store_ctx->ctx = trust_bundle; + auto 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) { + stats_.fail_verify_error_.inc(); + } + return ret; +} + +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 + auto ext = X509_get_extension_flags(leaf_cert); + if (ext & EXFLAG_CA) { + return false; + } + + auto us = X509_get_key_usage(leaf_cert); + return (us & (KU_CRL_SIGN | KU_KEY_CERT_SIGN)) == 0; +} + +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..487c6a3fa7 --- /dev/null +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h @@ -0,0 +1,77 @@ +#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_; + }; + +private: + std::vector> ca_certs_; + std::string ca_file_name_; + 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/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..f5577774ae 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -23,6 +23,8 @@ #include "common/stats/utility.h" #include "extensions/transport_sockets/tls/openssl_impl.h" +#include "extensions/transport_sockets/tls/cert_validator/factory.h" +#include "extensions/transport_sockets/tls/stats.h" #include "extensions/transport_sockets/tls/utility.h" #include "absl/container/node_hash_set.h" @@ -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())); diff --git a/test.yaml b/test.yaml new file mode 100644 index 0000000000..ef9e5f7e61 --- /dev/null +++ b/test.yaml @@ -0,0 +1,13 @@ +apiVersion: maistra.io/v1alpha1 +kind: ServiceMeshExtension +metadata: + name: header-append + namespace: dp1 +spec: + workloadSelector: + labels: + app: httpbin + config: test + image: quay.io/maistra-dev/header-append-filter:latest + phase: PostAuthZ + priority: 100 diff --git a/test/config/utility.cc b/test/config/utility.cc index b248c80d56..aa3b88c784 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -955,10 +955,14 @@ 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); + } // We'll negotiate up to TLSv1.3 for the tests that care, but it really // depends on what the client sets. diff --git a/test/config/utility.h b/test/config/utility.h index af87144d03..6921884537 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -66,6 +66,13 @@ class ConfigHelper { return *this; } + ServerSslOptions& setCustomValidatorConfig( + envoy::config::core::v3::TypedExtensionConfig* custom_validator_config) { + custom_validator_config_ = custom_validator_config; + return *this; + } + + envoy::config::core::v3::TypedExtensionConfig* custom_validator_config_; bool rsa_cert_{true}; bool rsa_cert_ocsp_staple_{true}; bool ecdsa_cert_{false}; 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..796f127d77 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/BUILD @@ -0,0 +1,49 @@ +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 = "factory_test", + srcs = [ + "factory_test.cc", + ], + deps = [ + ":util", + "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib", + ], +) + +envoy_cc_test_library( + name = "util", + hdrs = ["util.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", + ], +) + +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:util", + "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", + ], +) 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..844ca55ee9 --- /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/util.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..a3f4decb56 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/BUILD @@ -0,0 +1,28 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "spiffe_validator_test", + srcs = [ + "spiffe_validator_test.cc", + ], + data = [ + "//test/extensions/transport_sockets/tls/test_data:certs", + ], + 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:util", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_runtime_lib", + "//test/test_common:utility_lib", + ], +) 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..c16ed75e27 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc @@ -0,0 +1,403 @@ +#include +#include +#include +#include + +#include "envoy/common/exception.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/util.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 X509StoreContextPtr = 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); + 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); + validator_ = + std::make_unique(config_.get(), stats_, config_->api().timeSource()); + }; + + void initializeWithNullptr() { + validator_ = std::make_unique(nullptr, stats_, time_system_); + } + + void initialize() { validator_ = std::make_unique(stats_, time_system_); } + + SPIFFEValidator& validator() { return *validator_; } + SslStats& stats() { return stats_; } + +private: + TestCertificateValidationContextConfigPtr config_; + SPIFFEValidatorPtr validator_; + Stats::TestUtil::TestStore store_; + SslStats stats_; + Event::TestRealTimeSystem time_system_; +}; + +TEST_F(TestSPIFFEValidator, InvalidCA) { + 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"); +} + +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"); + + 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); + + 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 match 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 match 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)); + + // 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(1, stats().fail_verify_error_.value()); +} + +TEST_F(TestSPIFFEValidator, TestGetCaCertInformation) { + initialize(); + EXPECT_FALSE(validator().getCaCertInformation()); // should be nullptr + + 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/util.h b/test/extensions/transport_sockets/tls/cert_validator/util.h new file mode 100644 index 0000000000..7691bf50e4 --- /dev/null +++ b/test/extensions/transport_sockets/tls/cert_validator/util.h @@ -0,0 +1,83 @@ +#include + +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/ssl_socket_extended_info.h" + +#include "common/common/macros.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) + : api_(Api::createApiForTest()), custom_validator_config_(config){}; + 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 { + CONSTRUCT_ON_FIRST_USE(std::vector, {}); + } + 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 false; } + 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: + Api::ApiPtr api_; + const absl::optional custom_validator_config_; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/integration/BUILD b/test/extensions/transport_sockets/tls/integration/BUILD index d6cb3ad636..884f16890d 100644 --- a/test/extensions/transport_sockets/tls/integration/BUILD +++ b/test/extensions/transport_sockets/tls/integration/BUILD @@ -16,6 +16,7 @@ envoy_cc_test( ], data = [ "//test/config/integration/certs", + "//test/extensions/transport_sockets/tls/test_data:certs", ], deps = [ "//source/common/event:dispatcher_includes", @@ -25,6 +26,8 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:config", "//source/extensions/transport_sockets/tls:context_config_lib", "//source/extensions/transport_sockets/tls:context_lib", + "//source/extensions/transport_sockets/tls/cert_validator/spiffe:config", + "//test/extensions/common/tap:common", "//test/integration:http_integration_lib", "//test/mocks/secret:secret_mocks", "//test/test_common:utility_lib", 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..6b15fa3805 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -41,7 +41,8 @@ void SslIntegrationTestBase::initialize() { .setEcdsaCertOcspStaple(server_ecdsa_cert_ocsp_staple_) .setOcspStapleRequired(ocsp_staple_required_) .setTlsV13(server_tlsv1_3_) - .setExpectClientEcdsaCert(client_ecdsa_cert_)); + .setExpectClientEcdsaCert(client_ecdsa_cert_) + .setCustomValidatorConfig(custom_validator_config_)); HttpIntegrationTest::initialize(); context_manager_ = @@ -383,6 +384,98 @@ TEST_P(SslCertficateIntegrationTest, ServerRsa) { checkStats(); } +// Server configured on SPIFFE certificate validation for mTLS +// clientcert.pem's san is "spiffe://lyft.com/frontend-team" so it should be accepted. +TEST_P(SslCertficateIntegrationTest, 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; + server_rsa_cert_ = true; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection({}); + }; + testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator); + checkStats(); + Stats::CounterSharedPtr counter = + test_server_->counter(listenerStatPrefix("ssl.fail_verify_error")); + EXPECT_EQ(0, counter->value()); + counter->reset(); +} +// Server configured on SPIFFE certificate validation for mTLS +// clientcert.pem's san is "spiffe://lyft.com/frontend-team" so it should be rejected. +TEST_P(SslCertficateIntegrationTest, 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; + server_rsa_cert_ = true; + 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 { + makeHttpConnection(std::move(conn))->close(); + } + + Stats::CounterSharedPtr counter = + test_server_->counter(listenerStatPrefix("ssl.fail_verify_error")); + EXPECT_EQ(1, counter->value()); + counter->reset(); +} + +// Server configured on SPIFFE certificate validation for mTLS +// 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(SslCertficateIntegrationTest, 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; + server_rsa_cert_ = true; + 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 { + makeHttpConnection(std::move(conn))->close(); + } + Stats::CounterSharedPtr counter = + test_server_->counter(listenerStatPrefix("ssl.fail_verify_error")); + EXPECT_EQ(1, counter->value()); + counter->reset(); +} + // Server with an ECDSA certificate and a client with RSA/ECDSA cipher suites works. TEST_P(SslCertficateIntegrationTest, ServerEcdsa) { server_rsa_cert_ = false; diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h index e7f615c544..909f72dc08 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h @@ -29,6 +29,7 @@ class SslIntegrationTestBase : public HttpIntegrationTest { void checkStats(); protected: + envoy::config::core::v3::TypedExtensionConfig* custom_validator_config_{nullptr}; bool server_tlsv1_3_{false}; bool server_rsa_cert_{true}; bool server_rsa_cert_ocsp_staple_{false}; diff --git a/test/extensions/transport_sockets/tls/test_data/certs.sh b/test/extensions/transport_sockets/tls/test_data/certs.sh index b1155f18d9..da9ce8f7d9 100755 --- a/test/extensions/transport_sockets/tls/test_data/certs.sh +++ b/test/extensions/transport_sockets/tls/test_data/certs.sh @@ -260,3 +260,15 @@ 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 \ No newline at end of file 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..f4578f4f15 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.cfg @@ -0,0 +1,37 @@ +[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 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..cdd919911b --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIULfVpq633gV4mwbFj7nwmn5GYaFgwDQYJKoZIhvcNAQEL +BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n +aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjEwMzAxMDA1NTU5WhcNMjMw +MzAxMDA1NTU5WjB6MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW +MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwETHlmdDEZMBcGA1UECwwQ +THlmdCBFbmdpbmVlcmluZzEUMBIGA1UEAwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3YjGzHUr6FNKh1PfMPFE7NmAYSeLStc5+ +44lTku418R5ZbJ9+1OhXPbHjrqXM90jLZPLHOOginVVvNtZRk+LJrhUidzNmS69M +N2N9xosif0/zafI1/sToBwC8g/lP/TAmBwIzsbX3D526R1iVvIkyhxAK3AUTXG2x +Ilg2+sn4iIGmmPZNDtmoqLabDRWBToH0ijx+wxpF1IB5Tj2Ymeb7svG17e+MsG7G +bXo6+qT00n5hjpY1Cfj0I2mpUIw4aEqdk7+5MPFnnOmv2LuH73p/7goIs8Mw2ucX +xpF2f8lqFLt5+ZZT9BEdUT/sR23f7vQCaAjz7JATxrr9X2wqwHArAgMBAAGjgbIw +ga8wDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMBMDMGA1UdEQQsMCqGHXNwaWZmZTovL2V4YW1wbGUuY29tL3dv +cmtsb2FkggllbnZveS5jb20wHQYDVR0OBBYEFEaJC8Z4FIvUx44gnSSHh4uaeWTf +MB8GA1UdIwQYMBaAFNPBCyPjw6jyMr5fVME/eDgTVQPZMA0GCSqGSIb3DQEBCwUA +A4IBAQCEDoSAAvBuwAnrWuXuPgt++fqG6OQVnxRcJXL2GS14KUt2g2IniSTPm8Hi +mZj3hXS/RSpuyz3ObvtfKZ0gsf1QCBAigkQmSPFHTe/rhyWN+Vx+WkfEkrTqTmlN +UMAUlcO0SwbM9UABYPpMQI8Dn/XSiWgZaKHwGbzs2mQ00DosjcIGnav6+aPMCWfj +BTzw+GFzKcDqyhMRjEKBydTrzoTB2SK/hpE2ZDFn4xysfR9lf26MYsKHSRfLgK6E +ou/Tpl9twMoYSu40vdIO16gOK/KviUnCZ2nfcmhzDIcFQSNQQMNBPKPkpjji4xkl +udMoBC5KNZsvpC4gtGbY2X+DLI1P +-----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..e368ebf284 --- /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[] = + "52c784aa74dc6aadd581d6a9aad0a379a8cf8fb0b175e30874e19acf924dfe10"; +constexpr char TEST_SPIFFE_SAN_CERT_1_HASH[] = "3c2978c764c322a776bfb8aa04bb9a69e0f8bff2"; +constexpr char TEST_SPIFFE_SAN_CERT_SPKI[] = "7drIzHFClgoBw1tG6lbnubSOw9d9cKhKyUTWT77hpX0="; +constexpr char TEST_SPIFFE_SAN_CERT_SERIAL[] = "2df569abadf7815e26c1b163ee7c269f91986858"; +constexpr char TEST_SPIFFE_SAN_CERT_NOT_BEFORE[] = "Mar 1 00:55:59 2021 GMT"; +constexpr char TEST_SPIFFE_SAN_CERT_NOT_AFTER[] = "Mar 1 00:55:59 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..0a89f805d0 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_data/spiffe_san_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAt2Ixsx1K+hTSodT3zDxROzZgGEni0rXOfuOJU5LuNfEeWWyf +ftToVz2x466lzPdIy2TyxzjoIp1VbzbWUZPiya4VInczZkuvTDdjfcaLIn9P82ny +Nf7E6AcAvIP5T/0wJgcCM7G19w+dukdYlbyJMocQCtwFE1xtsSJYNvrJ+IiBppj2 +TQ7ZqKi2mw0VgU6B9Io8fsMaRdSAeU49mJnm+7Lxte3vjLBuxm16Ovqk9NJ+YY6W +NQn49CNpqVCMOGhKnZO/uTDxZ5zpr9i7h+96f+4KCLPDMNrnF8aRdn/JahS7efmW +U/QRHVE/7Edt3+70AmgI8+yQE8a6/V9sKsBwKwIDAQABAoIBAF5MXAoisf9O3dDh +1lprWcn8+AUFWWHIo1qUXnVfRKbwSg7p0EpD6QWTb/oIQLHZJtGQI1dWZ+gEx33c +0PA5/5B9t9h1OzULDiU/BiYTBlDC7rXYcPha/Z3im/pUUstTAoNLb1Jtu4hDu3Oi +ZGb7AAG/efxbjzCZgr5nTr1W0Ky/hWLpP1V1dLvWItlr/ieOPiNDg7dQqytIbSAV +cT1XpimTGSrS1UPEYVnHM9qNBMRJ9CmMigVdipaAPYV5u4SGuHU3oyClE5j4jWeE +vwugRkTap9L8xbG+JDYE5sjtPXxRtPb4DOEY6KElbPAt9S/zGkEu0N/9Qe1BYeeV +Q7uuduECgYEA7Cax4Ptc4C7lYTvGh+5gyylKQdUdfa7d4ixU48aG/X/3EKicSKvW ++ap6zd3olGy9tDPV29VF4f+9UrYuy04UxEpLeo8XDy5iauDAlU8lAPIrr0tkVm6w +ODJEiLgkljLFaxIvqiXE+661o3cBn3KcRcADAuQAzvvB8dGheyhzCY8CgYEAxswX +E2Je+W94iKLkiCNo6ljdiXwQu6TxocqJ8lYAiEzmXjJEnZ8NQbXgw4ESMPk/W1XL +ikJLjLrEEpdu7QSvIVqaw89l6DGpS034Ct8wQLGEJk/D253yumlX4gxyjGLrZV3Q +hHcQ1jbN0q86CWQstk81k20BtWgXJSvuOsdGyaUCgYEAgfuWg1i4OWl2tnt5fo6W +Vp0mk2/jqK9c0EZIf4th+By8eD3msBVt4cSVjcUsZK4qCQtTFoqgyZHDusgun5cd +1SFzxEUIk0GbyGpndoe2vXuO0hD0bKLGelgo4vxAny/Y/GNpOwVJFKOItS4nBYXH +QJk8zxWC4GswyJLziF+uWj8CgYEArePr82rCxNE6z9ocqPDAXuzoq9A4GssHCYzO +6YlM4ezSPWcfGfj8cZQUTS7jqK79Onlrlz6yMyFTTSflQbItNrG4WrtZ2qdF/Lbw +1yGvZYdhntl66unYXjKzSum0cRQ97+cF9DjqI1bA5x+bVoenjLjwlkptii7IwB0T +P5r8UnkCgYBPniQR7jINhnkGdR01GqsstvSgwoGG4+IeO9EuhBePoktI7W48HEs2 +Ng23QoXJRwWo+KM2GqpOD9y3lxaJnawki2PsiUcRE84wHmclOxOadEn6tun7e0Qw +rHu+7hAxcFl9xvK5zE7h1BjZCNR5hNpyMXyAmNpl8mX/mgZPc9HhHg== +-----END RSA PRIVATE KEY----- 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/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