CVE-2022-21656: Access of Resource Using Incompatible Type ('Type Confusion') (CWE-843)
value: Envoy is an open source edge and service proxy, designed for cloud-native applications. The default_validator.cc implementation used to implement the default certificate validation routines has a "type confusion" bug when processing subjectAltNames. This processing allows, for example, an rfc822Name or uniformResourceIndicator to be authenticated as a domain name. This confusion allows for the bypassing of nameConstraints, as processed by the underlying OpenSSL/BoringSSL implementation, exposing the possibility of impersonation of arbitrary servers. As a result Envoy will trust upstream certificates that should not be trusted.
CWE description: The product allocates or initializes a resource such as a pointer, object, or variable using one type, but it later accesses that resource using a type that is incompatible with the original type.
CVSS v3 base score: 5.9
Published: 2022-02-22T23:15Z
Repository: https://github.com/envoyproxy/envoy
Language: C++
Files changed: api/envoy/extensions/transport_sockets/tls/v3/common.proto, api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto, configs/envoy_double_proxy.template.yaml, configs/envoy_service_to_service.template.yaml, docs/root/intro/arch_overview/security/_include/ssl.yaml, docs/root/intro/arch_overview/security/ssl.rst, docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml, docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml, docs/root/start/quick-start/securing.rst, docs/root/version_history/current.rst, envoy/ssl/certificate_validation_context_config.h, examples/double-proxy/envoy-backend.yaml, examples/double-proxy/envoy-frontend.yaml, source/common/ssl/BUILD, source/common/ssl/certificate_validation_context_config_impl.cc, source/common/ssl/certificate_validation_context_config_impl.h, source/extensions/transport_sockets/tls/cert_validator/BUILD, source/extensions/transport_sockets/tls/cert_validator/default_validator.cc, source/extensions/transport_sockets/tls/cert_validator/default_validator.h, source/extensions/transport_sockets/tls/cert_validator/san_matcher.cc, source/extensions/transport_sockets/tls/cert_validator/san_matcher.h, source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc, source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h, test/common/quic/envoy_quic_proof_source_test.cc, test/common/quic/envoy_quic_proof_verifier_test.cc, test/common/secret/sds_api_test.cc, test/common/secret/secret_manager_impl_test.cc, test/common/upstream/upstream_impl_test.cc, test/config/utility.cc, test/config/utility.h, test/extensions/transport_sockets/tls/cert_validator/BUILD, test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc, test/extensions/transport_sockets/tls/cert_validator/san_matcher_test.cc, test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc, test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h, test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc, test/extensions/transport_sockets/tls/cert_validator/test_common.h, test/extensions/transport_sockets/tls/context_impl_test.cc, test/extensions/transport_sockets/tls/ssl_socket_test.cc, test/integration/ads_integration.cc, test/integration/ssl_utility.cc, test/integration/xfcc_integration_test.cc, test/mocks/ssl/mocks.h, test/per_file_coverage.sh, test/server/listener_manager_impl_quic_only_test.cc
Commit date: 2021-11-24 09:28:02 -0500
Commit message: Specify type for matching Subject Alternative Name. (#18628)


Signed-off-by: Pradeep Rao <pcrao@google.com>

Diff: diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto
index 369ee4d657e..c638578392a 100644
--- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto
+++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto
@@ -7,10 +7,11 @@ import "envoy/config/core/v3/extension.proto";
 import "envoy/type/matcher/v3/string.proto";
 
 import "google/protobuf/any.proto";
 import "google/protobuf/wrappers.proto";
 
+import "envoy/annotations/deprecation.proto";
 import "udpa/annotations/migrate.proto";
 import "udpa/annotations/sensitive.proto";
 import "udpa/annotations/status.proto";
 import "udpa/annotations/versioning.proto";
 import "validate/validate.proto";
@@ -266,11 +267,30 @@ message CertificateProviderPluginInstance {
   // particular domain. Not all provider instances will actually use this field, so the value
   // defaults to the empty string.
   string certificate_name = 2;
 }
 
-// [#next-free-field: 15]
+// Matcher for subject alternative names, to match both type and value of the SAN.
+message SubjectAltNameMatcher {
+  // Indicates the choice of GeneralName as defined in section 4.2.1.5 of RFC 5280 to match
+  // against.
+  enum SanType {
+    SAN_TYPE_UNSPECIFIED = 0;
+    EMAIL = 1;
+    DNS = 2;
+    URI = 3;
+    IP_ADDRESS = 4;
+  }
+
+  // Specification of type of SAN. Note that the default enum value is an invalid choice.
+  SanType san_type = 1 [(validate.rules).enum = {defined_only: true not_in: 0}];
+
+  // Matcher for SAN value.
+  type.matcher.v3.StringMatcher matcher = 2 [(validate.rules).message = {required: true}];
+}
+
+// [#next-free-field: 16]
 message CertificateValidationContext {
   option (udpa.annotations.versioning).previous_message_type =
       "envoy.api.v2.auth.CertificateValidationContext";
 
   // Peer certificate verification mode.
@@ -296,12 +316,12 @@ message CertificateValidationContext {
   // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.DownstreamTlsContext.require_client_certificate>`,
   // :ref:`verify_certificate_spki
   // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_spki>`,
   // :ref:`verify_certificate_hash
   // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.verify_certificate_hash>`, or
-  // :ref:`match_subject_alt_names
-  // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>`) is also
+  // :ref:`match_typed_subject_alt_names
+  // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`) is also
   // specified.
   //
   // It can optionally contain certificate revocation lists, in which case Envoy will verify
   // that the presented peer certificate has not been revoked by one of the included CRLs. Note
   // that if a CRL is provided for any certificate authority in a trust chain, a CRL must be
@@ -404,27 +424,36 @@ message CertificateValidationContext {
   repeated string verify_certificate_hash = 2
       [(validate.rules).repeated = {items {string {min_len: 64 max_bytes: 95}}}];
 
   // An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the
   // Subject Alternative Name of the presented certificate matches one of the specified matchers.
+  // The matching uses "any" semantics, that is to say, the SAN is verified if at least one matcher is
+  // matched.
   //
   // When a certificate has wildcard DNS SAN entries, to match a specific client, it should be
   // configured with exact match type in the :ref:`string matcher <envoy_v3_api_msg_type.matcher.v3.StringMatcher>`.
   // For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com",
   // it should be configured as shown below.
   //
   // .. code-block:: yaml
   //
-  //  match_subject_alt_names:
-  //    exact: "api.example.com"
+  //  match_typed_subject_alt_names:
+  //  - san_type: DNS
+  //    matcher:
+  //      exact: "api.example.com"
   //
   // .. attention::
   //
   //   Subject Alternative Names are easily spoofable and verifying only them is insecure,
   //   therefore this option must be used together with :ref:`trusted_ca
   //   <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.trusted_ca>`.
-  repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9;
+  repeated SubjectAltNameMatcher match_typed_subject_alt_names = 15;
+
+  // This field is deprecated in favor of ref:`match_typed_subject_alt_names
+  // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`
+  repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9
+      [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];
 
   // [#not-implemented-hide:] Must present signed certificate time-stamp.
   google.protobuf.BoolValue require_signed_certificate_timestamp = 6;
 
   // An optional `certificate revocation list
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
index cfb5e5c07e9..382fe985daf 100644
--- 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
@@ -40,11 +40,11 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
 // SAN would be rejected since Envoy selects the trust bundle according to the presented SAN before validate the certificate.
 //
 // Note that SPIFFE validator inherits and uses the following options from :ref:`CertificateValidationContext <envoy_v3_api_msg_extensions.transport_sockets.tls.v3.CertificateValidationContext>`.
 //
 // - :ref:`allow_expired_certificate <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.allow_expired_certificate>` to allow expired certificates.
-// - :ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types.
+// - :ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types.
 //
 message SPIFFECertValidatorConfig {
   message TrustDomain {
     // Name of the trust domain, `example.com`, `foo.bar.gov` for example.
     // Note that this must *not* have "spiffe://" prefix.
diff --git a/configs/envoy_double_proxy.template.yaml b/configs/envoy_double_proxy.template.yaml
index b574d6a518c..56f4d17ad20 100644
--- a/configs/envoy_double_proxy.template.yaml
+++ b/configs/envoy_double_proxy.template.yaml
@@ -147,12 +147,14 @@ static_resources:
             private_key:
               filename: certs/clientkey.pem
           validation_context:
             trusted_ca:
               filename: certs/cacert.pem
-            match_subject_alt_names:
-              exact: "front-proxy.yourcompany.net"
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: "front-proxy.yourcompany.net"
     typed_extension_protocol_options:
       envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
         "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
         explicit_http_config:
           http2_protocol_options: {}
@@ -186,12 +188,14 @@ static_resources:
         "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
         common_tls_context:
           validation_context:
             trusted_ca:
               filename: certs/cacert.pem
-            match_subject_alt_names:
-              exact: "collector-grpc.lightstep.com"
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: "collector-grpc.lightstep.com"
 flags_path: "/etc/envoy/flags"
 stats_sinks:
 - name: envoy.stat_sinks.statsd
   typed_config:
     "@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink
diff --git a/configs/envoy_service_to_service.template.yaml b/configs/envoy_service_to_service.template.yaml
index 6ec2f0bde90..7bdd6a4d0fa 100644
--- a/configs/envoy_service_to_service.template.yaml
+++ b/configs/envoy_service_to_service.template.yaml
@@ -348,12 +348,14 @@ static_resources:
         common_tls_context:
           validation_context:
             trusted_ca:
               filename: certs/cacert.pem
             {% if host.get('verify_subject_alt_name', False) %}
-            match_subject_alt_names:
-              exact: "{{host['verify_subject_alt_name'] }}"
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: "{{host['verify_subject_alt_name'] }}"
             {% endif %}
         {% if host.get('sni', False) %}
         sni: "{{ host['sni'] }}"
         {% endif %}
     connect_timeout: 1s
@@ -518,12 +520,14 @@ static_resources:
         "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
         common_tls_context:
           validation_context:
             trusted_ca:
               filename: certs/cacert.pem
-            match_subject_alt_names:
-              exact: "collector-grpc.lightstep.com"
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: "collector-grpc.lightstep.com"
   - name: cds_cluster
     connect_timeout: 0.25s
     type: STRICT_DNS
     lb_policy: ROUND_ROBIN
     load_assignment:
diff --git a/docs/root/intro/arch_overview/security/_include/ssl.yaml b/docs/root/intro/arch_overview/security/_include/ssl.yaml
index 6f666e4d92a..00bd6083239 100644
--- a/docs/root/intro/arch_overview/security/_include/ssl.yaml
+++ b/docs/root/intro/arch_overview/security/_include/ssl.yaml
@@ -48,9 +48,11 @@ static_resources:
           tls_certificates:
           - certificate_chain: {"filename": "certs/servercert.pem"}
             private_key: {"filename": "certs/serverkey.pem"}
             ocsp_staple: {"filename": "certs/server_ocsp_resp.der"}
           validation_context:
-            match_subject_alt_names:
-            - exact: "foo"
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: "foo"
             trusted_ca:
               filename: /etc/ssl/certs/ca-certificates.crt
diff --git a/docs/root/intro/arch_overview/security/ssl.rst b/docs/root/intro/arch_overview/security/ssl.rst
index e1192833232..8878c1faf41 100644
--- a/docs/root/intro/arch_overview/security/ssl.rst
+++ b/docs/root/intro/arch_overview/security/ssl.rst
@@ -74,11 +74,11 @@ Example configuration
 .. literalinclude:: _include/ssl.yaml
     :language: yaml
 
 */etc/ssl/certs/ca-certificates.crt* is the default path for the system CA bundle on Debian systems.
 :ref:`trusted_ca <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.trusted_ca>` along with
-:ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>`
+:ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`
 makes Envoy verify the server identity of *127.0.0.1:1234* as "foo" in the same way as e.g. cURL
 does on standard Debian installations. Common paths for system CA bundles on Linux and BSD are:
 
 * /etc/ssl/certs/ca-certificates.crt (Debian/Ubuntu/Gentoo etc.)
 * /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem (CentOS/RHEL 7)
diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml
index 84367c637f6..dd48870fbda 100644
--- a/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml
+++ b/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml
@@ -32,12 +32,14 @@ static_resources:
           require_client_certificate: true
           common_tls_context:
             validation_context:
               trusted_ca:
                 filename: certs/cacert.pem
-              match_subject_alt_names:
-              - exact: proxy-postgres-frontend.example.com
+              match_typed_subject_alt_names:
+              - san_type: DNS
+                matcher:
+                  exact: proxy-postgres-frontend.example.com
             tls_certificates:
             - certificate_chain:
                 filename: certs/servercert.pem
               private_key:
                 filename: certs/serverkey.pem
diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml
index b9ad6cc0635..054f79e55f3 100644
--- a/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml
+++ b/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml
@@ -46,7 +46,9 @@ static_resources:
         "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
         common_tls_context:
           validation_context:
             trusted_ca:
               filename: certs/cacert.pem
-            match_subject_alt_names:
-            - exact: proxy-postgres-backend.example.com
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: proxy-postgres-backend.example.com
diff --git a/docs/root/start/quick-start/securing.rst b/docs/root/start/quick-start/securing.rst
index ccfd6bd0ce0..35ff830450a 100644
--- a/docs/root/start/quick-start/securing.rst
+++ b/docs/root/start/quick-start/securing.rst
@@ -98,11 +98,11 @@ certificate is valid for.
    :caption: :download:`envoy-demo-tls-validation.yaml <_include/envoy-demo-tls-validation.yaml>`
 
 .. note::
 
    If the "Subject Alternative Names" for a certificate are for a wildcard domain, eg ``*.example.com``,
-   this is what you should use when matching with ``match_subject_alt_names``.
+   this is what you should use when matching with ``match_typed_subject_alt_names``.
 
 .. note::
 
    See :ref:`here <envoy_v3_api_msg_extensions.transport_sockets.tls.v3.CertificateValidationContext>` to view all
    of the possible configurations for certificate validation.
@@ -120,24 +120,24 @@ and specify a mutually trusted certificate authority:
 
 .. literalinclude:: _include/envoy-demo-tls-client-auth.yaml
    :language: yaml
    :linenos:
    :lineno-start: 27
-   :lines: 27-39
+   :lines: 27-41
    :emphasize-lines: 6, 8-10
    :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>`
 
 You can further restrict the authentication of connecting clients by specifying the allowed
 "Subject Alternative Names" in
-:ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>`,
+:ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`,
 similar to validating upstream certificates :ref:`described above <start_quick_start_securing_validation>`.
 
 .. literalinclude:: _include/envoy-demo-tls-client-auth.yaml
    :language: yaml
    :linenos:
    :lineno-start: 27
-   :lines: 27-39
+   :lines: 27-41
    :emphasize-lines: 7, 11-12
    :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>`
 
 .. note::
 
@@ -152,12 +152,12 @@ Use mutual ``TLS`` (``mTLS``) to connect with client certificates
 When connecting to an upstream with client certificates you can set them as follows:
 
 .. literalinclude:: _include/envoy-demo-tls-client-auth.yaml
    :language: yaml
    :linenos:
-   :lineno-start: 44
-   :lines: 44-68
+   :lineno-start: 46
+   :lines: 46-70
    :emphasize-lines: 20-25
    :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>`
 
 .. _start_quick_start_securing_sni:
 
diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst
index 5297fe4e80a..1937c5298b4 100644
--- a/docs/root/version_history/current.rst
+++ b/docs/root/version_history/current.rst
@@ -78,10 +78,11 @@ New Features
 * thrift_proxy: add upstream response zone metrics in the form ``cluster.cluster_name.zone.local_zone.upstream_zone.thrift.upstream_resp_success``.
 * thrift_proxy: add upstream metrics to show decoding errors and whether exception is from local or remote, e.g. ``cluster.cluster_name.thrift.upstream_resp_exception_remote``.
 * thrift_proxy: add host level success/error metrics where success is a reply of type success and error is any other response to a call.
 * thrift_proxy: support header flags.
 * thrift_proxy: support subset lb when using request or route metadata.
+* tls: added support for :ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>` for subject alternative names to enforce specifying the subject alternative name type for the matcher to prevent matching against an unintended type in the certificate.
 * tls: added support for only verifying the leaf CRL in the certificate chain with :ref:`only_verify_leaf_cert_crl <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.only_verify_leaf_cert_crl>`.
 * tls: support loading certificate chain and private key via :ref:`pkcs12 <envoy_v3_api_field_extensions.transport_sockets.tls.v3.TlsCertificate.pkcs12>`.
 * tls_inspector filter: added :ref:`enable_ja3_fingerprinting <envoy_v3_api_field_extensions.filters.listener.tls_inspector.v3.TlsInspector.enable_ja3_fingerprinting>` to create JA3 fingerprint hash from Client Hello message.
 * transport_socket: added :ref:`envoy.transport_sockets.tcp_stats <envoy_v3_api_msg_extensions.transport_sockets.tcp_stats.v3.Config>` which generates additional statistics gathered from the OS TCP stack.
 * udp: add support for multiple listener filters.
@@ -93,6 +94,7 @@ New Features
 Deprecated
 ----------
 * bootstrap: :ref:`dns_resolution_config <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.dns_resolution_config>` is deprecated in favor of :ref:`typed_dns_resolver_config <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.typed_dns_resolver_config>`.
 * cluster: :ref:`dns_resolution_config <envoy_v3_api_field_config.cluster.v3.Cluster.dns_resolution_config>` is deprecated in favor of :ref:`typed_dns_resolver_config <envoy_v3_api_field_config.cluster.v3.Cluster.typed_dns_resolver_config>`.
 * dns_cache: :ref:`dns_resolution_config <envoy_v3_api_field_extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_resolution_config>` is deprecated in favor of :ref:`typed_dns_resolver_config <envoy_v3_api_field_extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.typed_dns_resolver_config>`.
+* tls: :ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>` has been deprecated in favor of the :ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`.
 * dns_filter: :ref:`dns_resolution_config <envoy_v3_api_field_extensions.filters.udp.dns_filter.v3.DnsFilterConfig.ClientContextConfig.dns_resolution_config>` is deprecated in favor of :ref:`typed_dns_resolver_config <envoy_v3_api_field_extensions.filters.udp.dns_filter.v3.DnsFilterConfig.ClientContextConfig.typed_dns_resolver_config>`.
diff --git a/envoy/ssl/certificate_validation_context_config.h b/envoy/ssl/certificate_validation_context_config.h
index d8a1bf6b0af..d331c45ce25 100644
--- a/envoy/ssl/certificate_validation_context_config.h
+++ b/envoy/ssl/certificate_validation_context_config.h
@@ -5,10 +5,11 @@
 #include <vector>
 
 #include "envoy/api/api.h"
 #include "envoy/common/pure.h"
 #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
 #include "envoy/type/matcher/v3/string.pb.h"
 
 #include "absl/types/optional.h"
 
 namespace Envoy {
@@ -41,11 +42,11 @@ public:
   virtual const std::string& certificateRevocationListPath() const PURE;
 
   /**
    * @return The subject alt name matchers to be verified, if enabled.
    */
-  virtual const std::vector<envoy::type::matcher::v3::StringMatcher>&
+  virtual const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
   subjectAltNameMatchers() const PURE;
 
   /**
    * @return A list of a hex-encoded SHA-256 certificate hashes to be verified.
    */
diff --git a/examples/double-proxy/envoy-backend.yaml b/examples/double-proxy/envoy-backend.yaml
index 07cc1a7905f..0636354c377 100644
--- a/examples/double-proxy/envoy-backend.yaml
+++ b/examples/double-proxy/envoy-backend.yaml
@@ -24,12 +24,14 @@ static_resources:
             - certificate_chain:
                 filename: certs/servercert.pem
               private_key:
                 filename: certs/serverkey.pem
             validation_context:
-              match_subject_alt_names:
-              - exact: proxy-postgres-frontend.example.com
+              match_typed_subject_alt_names:
+              - san_type: DNS
+                matcher:
+                  exact: proxy-postgres-frontend.example.com
               trusted_ca:
                 filename: certs/cacert.pem
 
   clusters:
   - name: postgres_cluster
diff --git a/examples/double-proxy/envoy-frontend.yaml b/examples/double-proxy/envoy-frontend.yaml
index 37acbf33412..21fa643e62e 100644
--- a/examples/double-proxy/envoy-frontend.yaml
+++ b/examples/double-proxy/envoy-frontend.yaml
@@ -34,9 +34,11 @@ static_resources:
           - certificate_chain:
               filename: certs/clientcert.pem
             private_key:
               filename: certs/clientkey.pem
           validation_context:
-            match_subject_alt_names:
-            - exact: proxy-postgres-backend.example.com
+            match_typed_subject_alt_names:
+            - san_type: DNS
+              matcher:
+                exact: proxy-postgres-backend.example.com
             trusted_ca:
               filename: certs/cacert.pem
diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD
index 714c426b930..4526fe428f7 100644
--- a/source/common/ssl/BUILD
+++ b/source/common/ssl/BUILD
@@ -30,9 +30,10 @@ envoy_cc_library(
     deps = [
         "//envoy/api:api_interface",
         "//envoy/ssl:certificate_validation_context_config_interface",
         "//source/common/common:empty_string",
         "//source/common/config:datasource_lib",
+        "@envoy_api//envoy/config/core/v3:pkg_cc_proto",
         "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
         "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto",
     ],
 )
diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc
index 564325c3fb6..376f2b65a08 100644
--- a/source/common/ssl/certificate_validation_context_config_impl.cc
+++ b/source/common/ssl/certificate_validation_context_config_impl.cc
@@ -1,12 +1,15 @@
 #include "source/common/ssl/certificate_validation_context_config_impl.h"
 
 #include "envoy/common/exception.h"
+#include "envoy/config/core/v3/extension.pb.h"
 #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
 
 #include "source/common/common/empty_string.h"
 #include "source/common/common/fmt.h"
+#include "source/common/common/logger.h"
 #include "source/common/config/datasource.h"
 
 namespace Envoy {
 namespace Ssl {
 
@@ -20,12 +23,11 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl(
                         .value_or(ca_cert_.empty() ? EMPTY_STRING : INLINE_STRING)),
       certificate_revocation_list_(Config::DataSource::read(config.crl(), true, api)),
       certificate_revocation_list_path_(
           Config::DataSource::getPath(config.crl())
               .value_or(certificate_revocation_list_.empty() ? EMPTY_STRING : INLINE_STRING)),
-      subject_alt_name_matchers_(config.match_subject_alt_names().begin(),
-                                 config.match_subject_alt_names().end()),
+      subject_alt_name_matchers_(getSubjectAltNameMatchers(config)),
       verify_certificate_hash_list_(config.verify_certificate_hash().begin(),
                                     config.verify_certificate_hash().end()),
       verify_certificate_spki_list_(config.verify_certificate_spki().begin(),
                                     config.verify_certificate_spki().end()),
       allow_expired_certificate_(config.allow_expired_certificate()),
@@ -49,7 +51,36 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl(
       throw EnvoyException("Certificate validity period is always ignored without trusted CA");
     }
   }
 }
 
+std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+CertificateValidationContextConfigImpl::getSubjectAltNameMatchers(
+    const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config) {
+  if (!config.match_typed_subject_alt_names().empty() &&
+      !config.match_subject_alt_names().empty()) {
+    throw EnvoyException("SAN-based verification using both match_typed_subject_alt_names and "
+                         "the deprecated match_subject_alt_names is not allowed");
+  }
+  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+      subject_alt_name_matchers(config.match_typed_subject_alt_names().begin(),
+                                config.match_typed_subject_alt_names().end());
+  // Handle deprecated string type san matchers without san type specified, by
+  // creating a matcher for each supported type.
+  for (const envoy::type::matcher::v3::StringMatcher& matcher : config.match_subject_alt_names()) {
+    static constexpr std::array<
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType, 4>
+        san_types{envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI,
+                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL,
+                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS};
+    for (const auto san_type : san_types) {
+      subject_alt_name_matchers.emplace_back();
+      subject_alt_name_matchers.back().set_san_type(san_type);
+      *subject_alt_name_matchers.back().mutable_matcher() = matcher;
+    }
+  }
+  return subject_alt_name_matchers;
+}
+
 } // namespace Ssl
 } // namespace Envoy
diff --git a/source/common/ssl/certificate_validation_context_config_impl.h b/source/common/ssl/certificate_validation_context_config_impl.h
index 038abe08b1e..6e00605ff5c 100644
--- a/source/common/ssl/certificate_validation_context_config_impl.h
+++ b/source/common/ssl/certificate_validation_context_config_impl.h
@@ -2,10 +2,11 @@
 
 #include <string>
 
 #include "envoy/api/api.h"
 #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
 #include "envoy/ssl/certificate_validation_context_config.h"
 #include "envoy/type/matcher/v3/string.pb.h"
 
 namespace Envoy {
 namespace Ssl {
@@ -22,11 +23,11 @@ public:
     return certificate_revocation_list_;
   }
   const std::string& certificateRevocationListPath() const final {
     return certificate_revocation_list_path_;
   }
-  const std::vector<envoy::type::matcher::v3::StringMatcher>&
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
   subjectAltNameMatchers() const override {
     return subject_alt_name_matchers_;
   }
   const std::vector<std::string>& verifyCertificateHashList() const override {
     return verify_certificate_hash_list_;
@@ -49,15 +50,19 @@ public:
   Api::Api& api() const override { return api_; }
 
   bool onlyVerifyLeafCertificateCrl() const override { return only_verify_leaf_cert_crl_; }
 
 private:
+  static std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+  getSubjectAltNameMatchers(
+      const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config);
   const std::string ca_cert_;
   const std::string ca_cert_path_;
   const std::string certificate_revocation_list_;
   const std::string certificate_revocation_list_path_;
-  const std::vector<envoy::type::matcher::v3::StringMatcher> subject_alt_name_matchers_;
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+      subject_alt_name_matchers_;
   const std::vector<std::string> verify_certificate_hash_list_;
   const std::vector<std::string> verify_certificate_spki_list_;
   const bool allow_expired_certificate_;
   const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext::
       TrustChainVerification trust_chain_verification_;
diff --git a/source/extensions/transport_sockets/tls/cert_validator/BUILD b/source/extensions/transport_sockets/tls/cert_validator/BUILD
index ce92df41e80..93f745f8faa 100644
--- a/source/extensions/transport_sockets/tls/cert_validator/BUILD
+++ b/source/extensions/transport_sockets/tls/cert_validator/BUILD
@@ -11,16 +11,18 @@ envoy_extension_package()
 envoy_cc_library(
     name = "cert_validator_lib",
     srcs = [
         "default_validator.cc",
         "factory.cc",
+        "san_matcher.cc",
         "utility.cc",
     ],
     hdrs = [
         "cert_validator.h",
         "default_validator.h",
         "factory.h",
+        "san_matcher.h",
         "utility.h",
     ],
     external_deps = [
         "ssl",
         "abseil_base",
@@ -33,11 +35,15 @@ envoy_cc_library(
         "//source/common/common:assert_lib",
         "//source/common/common:base64_lib",
         "//source/common/common:hex_lib",
         "//source/common/common:minimal_logger_lib",
         "//source/common/common:utility_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",
+        "@envoy_api//envoy/config/core/v3:pkg_cc_proto",
+        "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
+        "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto",
     ],
 )
diff --git a/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc
index e638b98b428..93bbe8b17a0 100644
--- a/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc
+++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.cc
@@ -16,10 +16,11 @@
 #include "source/common/common/base64.h"
 #include "source/common/common/fmt.h"
 #include "source/common/common/hex.h"
 #include "source/common/common/matchers.h"
 #include "source/common/common/utility.h"
+#include "source/common/config/utility.h"
 #include "source/common/network/address_impl.h"
 #include "source/common/protobuf/utility.h"
 #include "source/common/runtime/runtime_features.h"
 #include "source/common/stats/symbol_table_impl.h"
 #include "source/common/stats/utility.h"
@@ -145,13 +146,13 @@ int DefaultCertValidator::initializeSslContexts(std::vector<SSL_CTX*> contexts,
   }
 
   const Envoy::Ssl::CertificateValidationContextConfig* cert_validation_config = config_;
   if (cert_validation_config != nullptr) {
     if (!cert_validation_config->subjectAltNameMatchers().empty()) {
-      for (const envoy::type::matcher::v3::StringMatcher& matcher :
+      for (const envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher& matcher :
            cert_validation_config->subjectAltNameMatchers()) {
-        subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher));
+        subject_alt_name_matchers_.emplace_back(createStringSanMatcher(matcher));
       }
       verify_mode = verify_mode_validation_context;
     }
 
     if (!cert_validation_config->verifyCertificateHashList().empty()) {
@@ -219,23 +220,22 @@ int DefaultCertValidator::doVerifyCertChain(
     }
   }
 
   // If `trusted_ca` exists, it is already verified in the code above. Thus, we just need to make
   // sure the verification for other validation context configurations doesn't fail (i.e. either
-  // `NotValidated` or `Validated`). If `trusted_ca` doesn't exist, we will need to make sure other
-  // configurations are verified and the verification succeed.
+  // `NotValidated` or `Validated`). If `trusted_ca` doesn't exist, we will need to make sure
+  // other configurations are verified and the verification succeed.
   int validation_status = verify_trusted_ca_
                               ? validated != Envoy::Ssl::ClientValidationStatus::Failed
                               : validated == Envoy::Ssl::ClientValidationStatus::Validated;
 
   return allow_untrusted_certificate_ ? 1 : validation_status;
 }
 
 Envoy::Ssl::ClientValidationStatus DefaultCertValidator::verifyCertificate(
     X509* cert, const std::vector<std::string>& verify_san_list,
-    const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
-        subject_alt_name_matchers) {
+    const std::vector<SanMatcherPtr>& 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();
@@ -289,27 +289,19 @@ bool DefaultCertValidator::verifySubjectAltName(X509* cert,
   }
   return false;
 }
 
 bool DefaultCertValidator::matchSubjectAltName(
-    X509* cert,
-    const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
-        subject_alt_name_matchers) {
+    X509* cert, const std::vector<SanMatcherPtr>& subject_alt_name_matchers) {
   bssl::UniquePtr<GENERAL_NAMES> san_names(
       static_cast<GENERAL_NAMES*>(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
-              ? Utility::dnsNameMatch(config_san_matcher.matcher().exact(), absl::string_view(san))
-              : config_san_matcher.match(san)) {
+  for (const auto& config_san_matcher : subject_alt_name_matchers) {
+    for (const GENERAL_NAME* general_name : san_names.get()) {
+      if (config_san_matcher->match(general_name)) {
         return true;
       }
     }
   }
   return false;
diff --git a/source/extensions/transport_sockets/tls/cert_validator/default_validator.h b/source/extensions/transport_sockets/tls/cert_validator/default_validator.h
index 0cfe2766d13..64f52bc3872 100644
--- a/source/extensions/transport_sockets/tls/cert_validator/default_validator.h
+++ b/source/extensions/transport_sockets/tls/cert_validator/default_validator.h
@@ -2,10 +2,11 @@
 
 #include <array>
 #include <deque>
 #include <functional>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "envoy/common/pure.h"
 #include "envoy/network/transport_socket.h"
 #include "envoy/registry/registry.h"
@@ -16,10 +17,11 @@
 
 #include "source/common/common/logger.h"
 #include "source/common/common/matchers.h"
 #include "source/common/stats/symbol_table_impl.h"
 #include "source/extensions/transport_sockets/tls/cert_validator/cert_validator.h"
+#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
 #include "source/extensions/transport_sockets/tls/stats.h"
 
 #include "absl/synchronization/mutex.h"
 #include "openssl/ssl.h"
 #include "openssl/x509v3.h"
@@ -51,14 +53,13 @@ public:
   size_t daysUntilFirstCertExpires() const override;
   std::string getCaFileName() const override { return ca_file_path_; };
   Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const override;
 
   // Utility functions.
-  Envoy::Ssl::ClientValidationStatus verifyCertificate(
-      X509* cert, const std::vector<std::string>& verify_san_list,
-      const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
-          subject_alt_name_matchers);
+  Envoy::Ssl::ClientValidationStatus
+  verifyCertificate(X509* cert, const std::vector<std::string>& verify_san_list,
+                    const std::vector<SanMatcherPtr>& subject_alt_name_matchers);
 
   /**
    * Verifies certificate hash for pinning. The hash is a hex-encoded SHA-256 of the DER-encoded
    * certificate.
    *
@@ -92,25 +93,22 @@ public:
    * Performs subjectAltName matching with the provided matchers.
    * @param ssl the certificate to verify
    * @param subject_alt_name_matchers the configured matchers to match
    * @return true if the verification succeeds
    */
-  static bool matchSubjectAltName(
-      X509* cert,
-      const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
-          subject_alt_name_matchers);
+  static bool matchSubjectAltName(X509* cert,
+                                  const std::vector<SanMatcherPtr>& subject_alt_name_matchers);
 
 private:
   const Envoy::Ssl::CertificateValidationContextConfig* config_;
   SslStats& stats_;
   TimeSource& time_source_;
 
   bool allow_untrusted_certificate_{false};
   bssl::UniquePtr<X509> ca_cert_;
   std::string ca_file_path_;
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers_;
+  std::vector<SanMatcherPtr> subject_alt_name_matchers_;
   std::vector<std::vector<uint8_t>> verify_certificate_hash_list_;
   std::vector<std::vector<uint8_t>> verify_certificate_spki_list_;
   bool verify_trusted_ca_{false};
 };
 
diff --git a/source/extensions/transport_sockets/tls/cert_validator/san_matcher.cc b/source/extensions/transport_sockets/tls/cert_validator/san_matcher.cc
new file mode 100644
index 00000000000..4be1c64ec09
--- /dev/null
+++ b/source/extensions/transport_sockets/tls/cert_validator/san_matcher.cc
@@ -0,0 +1,53 @@
+#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
+
+#include <memory>
+
+#include "envoy/config/core/v3/extension.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
+#include "envoy/registry/registry.h"
+#include "envoy/ssl/certificate_validation_context_config.h"
+
+#include "source/extensions/transport_sockets/tls/utility.h"
+
+namespace Envoy {
+namespace Extensions {
+namespace TransportSockets {
+namespace Tls {
+
+bool StringSanMatcher::match(const GENERAL_NAME* general_name) const {
+  if (general_name->type != general_name_type_) {
+    return false;
+  }
+  // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics.
+  const std::string san = Utility::generalNameAsString(general_name);
+  return general_name->type == GEN_DNS &&
+                 matcher_.matcher().match_pattern_case() ==
+                     envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact
+             ? Utility::dnsNameMatch(matcher_.matcher().exact(), absl::string_view(san))
+             : matcher_.match(san);
+}
+
+SanMatcherPtr createStringSanMatcher(
+    envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher const& matcher) {
+  // Verify that a new san type has not been added.
+  static_assert(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MAX ==
+                4);
+
+  switch (matcher.san_type()) {
+  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS:
+    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher.matcher())};
+  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL:
+    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_EMAIL, matcher.matcher())};
+  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI:
+    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher.matcher())};
+  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS:
+    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_IPADD, matcher.matcher())};
+  default:
+    NOT_REACHED_GCOVR_EXCL_LINE;
+  }
+}
+
+} // namespace Tls
+} // namespace TransportSockets
+} // namespace Extensions
+} // namespace Envoy
diff --git a/source/extensions/transport_sockets/tls/cert_validator/san_matcher.h b/source/extensions/transport_sockets/tls/cert_validator/san_matcher.h
new file mode 100644
index 00000000000..5c2141f4b57
--- /dev/null
+++ b/source/extensions/transport_sockets/tls/cert_validator/san_matcher.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <memory>
+
+#include "envoy/config/core/v3/extension.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
+#include "envoy/ssl/certificate_validation_context_config.h"
+#include "envoy/type/matcher/v3/string.pb.h"
+
+#include "source/common/common/hash.h"
+#include "source/common/common/matchers.h"
+#include "source/common/protobuf/protobuf.h"
+#include "source/extensions/transport_sockets/tls/utility.h"
+
+#include "openssl/x509v3.h"
+
+namespace Envoy {
+namespace Extensions {
+namespace TransportSockets {
+namespace Tls {
+
+/** Interface to verify if there is a match in a list of subject alternative
+ * names.
+ */
+class SanMatcher {
+public:
+  virtual bool match(GENERAL_NAME const*) const PURE;
+  virtual ~SanMatcher() = default;
+};
+
+using SanMatcherPtr = std::unique_ptr<SanMatcher>;
+
+class StringSanMatcher : public SanMatcher {
+public:
+  bool match(const GENERAL_NAME* general_name) const override;
+  ~StringSanMatcher() override = default;
+  StringSanMatcher(int general_name_type, envoy::type::matcher::v3::StringMatcher matcher)
+      : general_name_type_(general_name_type), matcher_(matcher) {}
+
+private:
+  const int general_name_type_;
+  const Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher> matcher_;
+};
+
+SanMatcherPtr createStringSanMatcher(
+    const envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher& matcher);
+
+} // namespace Tls
+} // namespace TransportSockets
+} // namespace Extensions
+} // namespace Envoy
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
index 63df8297c76..c3b8d338fca 100644
--- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc
+++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc
@@ -1,7 +1,8 @@
 #include "source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h"
 
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
 #include "envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.pb.h"
 #include "envoy/network/transport_socket.h"
 #include "envoy/registry/registry.h"
 #include "envoy/ssl/context_config.h"
 #include "envoy/ssl/ssl_socket_extended_info.h"
@@ -35,11 +36,18 @@ SPIFFEValidator::SPIFFEValidator(const Envoy::Ssl::CertificateValidationContextC
   Config::Utility::translateOpaqueConfig(config->customValidatorConfig().value().typed_config(),
                                          ProtobufMessage::getStrictValidationVisitor(), message);
 
   if (!config->subjectAltNameMatchers().empty()) {
     for (const auto& matcher : config->subjectAltNameMatchers()) {
-      subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher));
+      if (matcher.san_type() ==
+          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI) {
+        // Only match against URI SAN since SPIFFE specification does not restrict values in other
+        // SAN types. See the discussion: https://github.com/envoyproxy/envoy/issues/15392
+        // TODO(pradeepcrao): Throw an exception when a non-URI matcher is encountered after the
+        // deprecated field match_subject_alt_names is removed
+        subject_alt_name_matchers_.emplace_back(createStringSanMatcher(matcher));
+      }
     }
   }
 
   const auto size = message.trust_domains().size();
   trust_bundle_stores_.reserve(size);
@@ -222,19 +230,14 @@ bool SPIFFEValidator::matchSubjectAltName(X509& leaf_cert) {
   // SPIFFE cert validation algorithm succeeded, which requires exactly one URI SAN in the leaf
   // cert.
   ASSERT(san_names != nullptr,
          "san_names should have at least one name after SPIFFE cert validation");
 
-  // Only match against URI SAN since SPIFFE specification does not restrict values in other SAN
-  // types. See the discussion: https://github.com/envoyproxy/envoy/issues/15392
   for (const GENERAL_NAME* general_name : san_names.get()) {
-    if (general_name->type == GEN_URI) {
-      const std::string san = Utility::generalNameAsString(general_name);
-      for (const auto& config_san_matcher : subject_alt_name_matchers_) {
-        if (config_san_matcher.match(san)) {
-          return true;
-        }
+    for (const auto& config_san_matcher : subject_alt_name_matchers_) {
+      if (config_san_matcher->match(general_name)) {
+        return true;
       }
     }
   }
   return false;
 }
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
index b4cc068e908..669d77aec7d 100644
--- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h
+++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h
@@ -15,10 +15,11 @@
 
 #include "source/common/common/c_smart_ptr.h"
 #include "source/common/common/matchers.h"
 #include "source/common/stats/symbol_table_impl.h"
 #include "source/extensions/transport_sockets/tls/cert_validator/cert_validator.h"
+#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
 #include "source/extensions/transport_sockets/tls/stats.h"
 
 #include "openssl/ssl.h"
 #include "openssl/x509v3.h"
 
@@ -65,12 +66,11 @@ public:
 
 private:
   bool allow_expired_certificate_{false};
   std::vector<bssl::UniquePtr<X509>> ca_certs_;
   std::string ca_file_name_;
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers_{};
+  std::vector<SanMatcherPtr> subject_alt_name_matchers_{};
   absl::flat_hash_map<std::string, X509StorePtr> trust_bundle_stores_;
 
   SslStats& stats_;
   TimeSource& time_source_;
 };
diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc
index b481768eb91..3c49ad23f35 100644
--- a/test/common/quic/envoy_quic_proof_source_test.cc
+++ b/test/common/quic/envoy_quic_proof_source_test.cc
@@ -56,11 +56,12 @@ public:
     ON_CALL(cert_validation_ctx_config_, certificateRevocationList())
         .WillByDefault(ReturnRef(crl_list));
     ON_CALL(cert_validation_ctx_config_, certificateRevocationListPath())
         .WillByDefault(ReturnRef(path_string));
     const std::vector<std::string> empty_string_list;
-    const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers;
+    const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+        san_matchers;
     ON_CALL(cert_validation_ctx_config_, subjectAltNameMatchers())
         .WillByDefault(ReturnRef(san_matchers));
     ON_CALL(cert_validation_ctx_config_, verifyCertificateHashList())
         .WillByDefault(ReturnRef(empty_string_list));
     ON_CALL(cert_validation_ctx_config_, verifyCertificateSpkiList())
diff --git a/test/common/quic/envoy_quic_proof_verifier_test.cc b/test/common/quic/envoy_quic_proof_verifier_test.cc
index dea9daa038f..06b3405b7d2 100644
--- a/test/common/quic/envoy_quic_proof_verifier_test.cc
+++ b/test/common/quic/envoy_quic_proof_verifier_test.cc
@@ -73,11 +73,12 @@ public:
 
 protected:
   const std::string path_string_{"some_path"};
   const std::string alpn_{"h2,http/1.1"};
   const std::string sig_algs_{"rsa_pss_rsae_sha256"};
-  const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_;
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+      san_matchers_;
   const std::string empty_string_;
   const std::vector<std::string> empty_string_list_;
   const std::string cert_chain_{quic::test::kTestCertificateChainPem};
   std::string root_ca_cert_;
   const std::string leaf_cert_;
diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc
index a0cea30a2f8..f91734a588b 100644
--- a/test/common/secret/sds_api_test.cc
+++ b/test/common/secret/sds_api_test.cc
@@ -663,11 +663,14 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) {
   typed_secret.set_name("abc.com");
   auto* dynamic_cvc = typed_secret.mutable_validation_context();
   dynamic_cvc->set_allow_expired_certificate(false);
   dynamic_cvc->mutable_trusted_ca()->set_filename(TestEnvironment::substitute(
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"));
-  dynamic_cvc->add_match_subject_alt_names()->set_exact("second san");
+  auto* san_matcher = dynamic_cvc->add_match_typed_subject_alt_names();
+  san_matcher->mutable_matcher()->set_exact("second san");
+  san_matcher->set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
   const std::string dynamic_verify_certificate_spki =
       "QGJRPdmx/r5EGOFLb2MTiZp2isyC0Whht7iazhzXaCM=";
   dynamic_cvc->add_verify_certificate_spki(dynamic_verify_certificate_spki);
   EXPECT_CALL(secret_callback, onAddOrUpdateSecret());
   EXPECT_CALL(validation_callback, validateCvc(_));
@@ -679,11 +682,14 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) {
   const std::string default_verify_certificate_hash =
       "0000000000000000000000000000000000000000000000000000000000000000";
   envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext default_cvc;
   default_cvc.set_allow_expired_certificate(true);
   default_cvc.mutable_trusted_ca()->set_inline_bytes("fake trusted ca");
-  default_cvc.add_match_subject_alt_names()->set_exact("first san");
+  san_matcher = default_cvc.add_match_typed_subject_alt_names();
+  san_matcher->mutable_matcher()->set_exact("first san");
+  san_matcher->set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
   default_cvc.add_verify_certificate_hash(default_verify_certificate_hash);
   envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext merged_cvc =
       default_cvc;
   merged_cvc.MergeFrom(*sds_api.secret());
   Ssl::CertificateValidationContextConfigImpl cvc_config(merged_cvc, *api_);
@@ -695,12 +701,16 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) {
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem";
   EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(ca_cert)),
             cvc_config.caCert());
   // Verify that repeated fields are concatenated.
   EXPECT_EQ(2, cvc_config.subjectAltNameMatchers().size());
-  EXPECT_EQ("first san", cvc_config.subjectAltNameMatchers()[0].exact());
-  EXPECT_EQ("second san", cvc_config.subjectAltNameMatchers()[1].exact());
+  EXPECT_EQ("first san", cvc_config.subjectAltNameMatchers()[0].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+            cvc_config.subjectAltNameMatchers()[0].san_type());
+  EXPECT_EQ("second san", cvc_config.subjectAltNameMatchers()[1].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+            cvc_config.subjectAltNameMatchers()[1].san_type());
   // Verify that if dynamic CertificateValidationContext does not set certificate hash list, the new
   // secret contains hash list from default CertificateValidationContext.
   EXPECT_EQ(1, cvc_config.verifyCertificateHashList().size());
   EXPECT_EQ(default_verify_certificate_hash, cvc_config.verifyCertificateHashList()[0]);
   // Verify that if default CertificateValidationContext does not set certificate SPKI list, the new
diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc
index a73f143ba86..1109b762125 100644
--- a/test/common/secret/secret_manager_impl_test.cc
+++ b/test/common/secret/secret_manager_impl_test.cc
@@ -1126,8 +1126,45 @@ tls_certificate:
   EXPECT_THROW_WITH_MESSAGE(
       Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), ctx, *api_),
       EnvoyException, "Failed to load private key provider: test");
 }
 
+// Verify that using the match_subject_alt_names will result in a typed matcher, one for each of
+// DNS, URI, EMAIL and IP_ADDRESS.
+// TODO(pradeepcrao): Delete this test once the deprecated field is removed.
+TEST_F(SecretManagerImplTest, DeprecatedSanMatcher) {
+  envoy::extensions::transport_sockets::tls::v3::Secret secret_config;
+  const std::string yaml =
+      R"EOF(
+      name: "abc.com"
+      validation_context:
+        trusted_ca: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" }
+        allow_expired_certificate: true
+        match_subject_alt_names:
+          exact: "example.foo"
+      )EOF";
+  TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), secret_config);
+  std::unique_ptr<SecretManager> secret_manager(new SecretManagerImpl(config_tracker_));
+  secret_manager->addStaticSecret(secret_config);
+
+  ASSERT_EQ(secret_manager->findStaticCertificateValidationContextProvider("undefined"), nullptr);
+  ASSERT_NE(secret_manager->findStaticCertificateValidationContextProvider("abc.com"), nullptr);
+  Ssl::CertificateValidationContextConfigImpl cvc_config(
+      *secret_manager->findStaticCertificateValidationContextProvider("abc.com")->secret(), *api_);
+  EXPECT_EQ(cvc_config.subjectAltNameMatchers().size(), 4);
+  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[0].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+            cvc_config.subjectAltNameMatchers()[0].san_type());
+  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[1].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI,
+            cvc_config.subjectAltNameMatchers()[1].san_type());
+  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[2].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL,
+            cvc_config.subjectAltNameMatchers()[2].san_type());
+  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[3].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS,
+            cvc_config.subjectAltNameMatchers()[3].san_type());
+}
+
 } // namespace
 } // namespace Secret
 } // namespace Envoy
diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc
index b177712ca4c..f413d4cfd8a 100644
--- a/test/common/upstream/upstream_impl_test.cc
+++ b/test/common/upstream/upstream_impl_test.cc
@@ -3552,13 +3552,17 @@ TEST_F(ClusterInfoImplTest, Http3) {
               private_key:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
             validation_context:
               trusted_ca:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-              match_subject_alt_names:
-              - exact: localhost
-              - exact: 127.0.0.1
+              match_typed_subject_alt_names:
+              - matcher:
+                  exact: localhost
+                san_type: URI
+              - matcher:
+                  exact: 127.0.0.1
+                san_type: IP_ADDRESS
   )EOF",
                                                        Network::Address::IpVersion::v4);
   auto cluster1 = makeCluster(yaml);
   ASSERT_TRUE(cluster1->info()->idleTimeout().has_value());
   EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value());
@@ -3625,13 +3629,17 @@ TEST_F(ClusterInfoImplTest, Http3BadConfig) {
               private_key:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
             validation_context:
               trusted_ca:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-              match_subject_alt_names:
-              - exact: localhost
-              - exact: 127.0.0.1
+              match_typed_subject_alt_names:
+              - matcher:
+                  exact: localhost
+                san_type: URI
+              - matcher:
+                  exact: 127.0.0.1
+                san_type: IP_ADDRESS
     typed_extension_protocol_options:
       envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
         "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
         use_downstream_protocol_config:
           http3_protocol_options: {}
@@ -3670,13 +3678,17 @@ TEST_F(ClusterInfoImplTest, Http3Auto) {
               private_key:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
             validation_context:
               trusted_ca:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-              match_subject_alt_names:
-              - exact: localhost
-              - exact: 127.0.0.1
+              match_typed_subject_alt_names:
+              - matcher:
+                  exact: localhost
+                san_type: URI
+              - matcher:
+                  exact: 127.0.0.1
+                san_type: IP_ADDRESS
   )EOF",
                                                        Network::Address::IpVersion::v4);
 
   auto cluster1 = makeCluster(yaml);
   ASSERT_TRUE(cluster1->info()->idleTimeout().has_value());
@@ -3729,13 +3741,17 @@ TEST_F(ClusterInfoImplTest, UseDownstreamHttpProtocolWithoutDowngrade) {
               private_key:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
             validation_context:
               trusted_ca:
                 filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-              match_subject_alt_names:
-              - exact: localhost
-              - exact: 127.0.0.1
+              match_typed_subject_alt_names:
+              - matcher:
+                  exact: localhost
+                san_type: URI
+              - matcher:
+                  exact: 127.0.0.1
+                san_type: IP_ADDRESS
     typed_extension_protocol_options:
       envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
         "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
         use_downstream_protocol_config:
           http3_protocol_options: {}
diff --git a/test/config/utility.cc b/test/config/utility.cc
index 320fa3957c7..cfd6fa10e61 100644
--- a/test/config/utility.cc
+++ b/test/config/utility.cc
@@ -1213,12 +1213,12 @@ void ConfigHelper::initializeTls(
       tls_certificate->mutable_ocsp_staple()->set_filename(TestEnvironment::runfilesPath(
           "test/config/integration/certs/server_ecdsa_ocsp_resp.der"));
     }
   }
   if (!options.san_matchers_.empty()) {
-    *validation_context->mutable_match_subject_alt_names() = {options.san_matchers_.begin(),
-                                                              options.san_matchers_.end()};
+    *validation_context->mutable_match_typed_subject_alt_names() = {options.san_matchers_.begin(),
+                                                                    options.san_matchers_.end()};
   }
 }
 
 void ConfigHelper::renameListener(const std::string& name) {
   auto* static_resources = bootstrap_.mutable_static_resources();
diff --git a/test/config/utility.h b/test/config/utility.h
index 5ebdc148401..33fa29e13b6 100644
--- a/test/config/utility.h
+++ b/test/config/utility.h
@@ -12,10 +12,11 @@
 #include "envoy/config/endpoint/v3/endpoint.pb.h"
 #include "envoy/config/listener/v3/listener_components.pb.h"
 #include "envoy/config/route/v3/route_components.pb.h"
 #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h"
 #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
 #include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.h"
 #include "envoy/http/codes.h"
 
 #include "source/common/config/api_version.h"
 #include "source/common/network/address_impl.h"
@@ -78,11 +79,12 @@ public:
       custom_validator_config_ = custom_validator_config;
       return *this;
     }
 
     ServerSslOptions&
-    setSanMatchers(std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers) {
+    setSanMatchers(std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+                       san_matchers) {
       san_matchers_ = san_matchers;
       return *this;
     }
 
     bool allow_expired_certificate_{};
@@ -92,11 +94,12 @@ public:
     bool ecdsa_cert_{false};
     bool ecdsa_cert_ocsp_staple_{false};
     bool ocsp_staple_required_{false};
     bool tlsv1_3_{false};
     bool expect_client_ecdsa_cert_{false};
-    std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
+    std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+        san_matchers_{};
   };
 
   // Set up basic config, using the specified IpVersion for all connections: listeners, upstream,
   // and admin connections.
   //
diff --git a/test/extensions/transport_sockets/tls/cert_validator/BUILD b/test/extensions/transport_sockets/tls/cert_validator/BUILD
index 7fd2c97084a..9d880d51fa0 100644
--- a/test/extensions/transport_sockets/tls/cert_validator/BUILD
+++ b/test/extensions/transport_sockets/tls/cert_validator/BUILD
@@ -55,5 +55,18 @@ envoy_cc_test_library(
         "//envoy/ssl:ssl_socket_extended_info_interface",
         "//source/common/common:macros",
         "//test/test_common:utility_lib",
     ],
 )
+
+envoy_cc_test(
+    name = "san_matcher_test",
+    srcs = [
+        "san_matcher_test.cc",
+    ],
+    deps = [
+        "//source/common/protobuf:utility_lib",
+        "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib",
+        "//test/test_common:utility_lib",
+        "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
+    ],
+)
diff --git a/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc
index 036c3679fa1..d25eb20c0a0 100644
--- a/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc
+++ b/test/extensions/transport_sockets/tls/cert_validator/default_validator_test.cc
@@ -1,9 +1,10 @@
 #include <string>
 #include <vector>
 
 #include "source/extensions/transport_sockets/tls/cert_validator/default_validator.h"
+#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
 
 #include "test/extensions/transport_sockets/tls/ssl_test_utility.h"
 #include "test/test_common/environment.h"
 #include "test/test_common/test_runtime.h"
 #include "test/test_common/utility.h"
@@ -26,39 +27,50 @@ TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameDNSMatched) {
 
 TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameDNSMatched) {
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
   envoy::type::matcher::v3::StringMatcher matcher;
-  matcher.MergeFrom(TestUtility::createRegexMatcher(".*.example.com"));
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers;
-  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw"));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
   EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
 }
 
+TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameIncorrectTypeMatched) {
+  bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
+      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
+  envoy::type::matcher::v3::StringMatcher matcher;
+  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw"));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher)});
+  EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
+}
+
 TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameWildcardDNSMatched) {
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
       "{{ test_rundir "
       "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem"));
   envoy::type::matcher::v3::StringMatcher matcher;
   matcher.set_exact("api.example.com");
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers;
-  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
   EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
 }
 
 TEST(DefaultCertValidatorTest, TestMultiLevelMatch) {
   // san_multiple_dns_cert matches *.example.com
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
       "{{ test_rundir "
       "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem"));
   envoy::type::matcher::v3::StringMatcher matcher;
   matcher.set_exact("foo.api.example.com");
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers;
-  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
   EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
 }
 
 TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameURIMatched) {
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
@@ -79,14 +91,14 @@ TEST(DefaultCertValidatorTest, TestVerifySubjectAltMultiDomain) {
 
 TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameURIMatched) {
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"));
   envoy::type::matcher::v3::StringMatcher matcher;
-  matcher.MergeFrom(TestUtility::createRegexMatcher("spiffe://lyft.com/.*-team"));
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers;
-  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw(spiffe://lyft.com/[^/]*-team)raw"));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher)});
   EXPECT_TRUE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
 }
 
 TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameNotMatched) {
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
@@ -98,14 +110,20 @@ TEST(DefaultCertValidatorTest, TestVerifySubjectAltNameNotMatched) {
 
 TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameNotMatched) {
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
   envoy::type::matcher::v3::StringMatcher matcher;
-  matcher.MergeFrom(TestUtility::createRegexMatcher(".*.foo.com"));
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      subject_alt_name_matchers;
-  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example\.net)raw"));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_IPADD, matcher)});
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher)});
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_EMAIL, matcher)});
   EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
 }
 
 TEST(DefaultCertValidatorTest, TestCertificateVerificationWithSANMatcher) {
   Stats::TestUtil::TestStore test_store;
@@ -117,22 +135,22 @@ TEST(DefaultCertValidatorTest, TestCertificateVerificationWithSANMatcher) {
           Event::GlobalTimeSystem().timeSystem());
 
   bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
   envoy::type::matcher::v3::StringMatcher matcher;
-  matcher.MergeFrom(TestUtility::createRegexMatcher(".*.example.com"));
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>> san_matchers;
-  san_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw"));
+  std::vector<SanMatcherPtr> san_matchers;
+  san_matchers.push_back(SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
   // Verify the certificate with correct SAN regex matcher.
   EXPECT_EQ(default_validator->verifyCertificate(cert.get(), /*verify_san_list=*/{}, san_matchers),
             Envoy::Ssl::ClientValidationStatus::Validated);
   EXPECT_EQ(stats.fail_verify_san_.value(), 0);
 
   matcher.MergeFrom(TestUtility::createExactMatcher("hello.example.com"));
-  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
-      invalid_san_matchers;
-  invalid_san_matchers.push_back(Matchers::StringMatcherImpl(matcher));
+  std::vector<SanMatcherPtr> invalid_san_matchers;
+  invalid_san_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
   // Verify the certificate with incorrect SAN exact matcher.
   EXPECT_EQ(default_validator->verifyCertificate(cert.get(), /*verify_san_list=*/{},
                                                  invalid_san_matchers),
             Envoy::Ssl::ClientValidationStatus::Failed);
   EXPECT_EQ(stats.fail_verify_san_.value(), 1);
@@ -156,9 +174,20 @@ TEST(DefaultCertValidatorTest, TestCertificateVerificationWithNoValidationContex
                                                  /*leaf_cert=*/*cert,
                                                  /*transport_socket_options=*/nullptr),
             0);
 }
 
+TEST(DefaultCertValidatorTest, NoSanInCert) {
+  bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
+      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/fake_ca_cert.pem"));
+  envoy::type::matcher::v3::StringMatcher matcher;
+  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example\.net)raw"));
+  std::vector<SanMatcherPtr> subject_alt_name_matchers;
+  subject_alt_name_matchers.push_back(
+      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
+  EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
+}
+
 } // namespace Tls
 } // namespace TransportSockets
 } // namespace Extensions
 } // namespace Envoy
diff --git a/test/extensions/transport_sockets/tls/cert_validator/san_matcher_test.cc b/test/extensions/transport_sockets/tls/cert_validator/san_matcher_test.cc
new file mode 100644
index 00000000000..b7729de8d41
--- /dev/null
+++ b/test/extensions/transport_sockets/tls/cert_validator/san_matcher_test.cc
@@ -0,0 +1,57 @@
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.validate.h"
+
+#include "source/common/protobuf/message_validator_impl.h"
+#include "source/common/protobuf/utility.h"
+#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
+
+#include "test/test_common/utility.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace Envoy {
+namespace Extensions {
+namespace TransportSockets {
+namespace Tls {
+
+// Verify that we get a valid string san matcher for all valid san types.
+TEST(SanMatcherConfigTest, TestValidSanType) {
+  // Iterate over all san type enums.
+  for (envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType san_type =
+           envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MIN;
+       san_type <=
+       envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MAX;
+       san_type = static_cast<
+           envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType>(
+           static_cast<int>(san_type + 1))) {
+    envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher;
+    san_matcher.mutable_matcher()->set_exact("foo.example");
+    san_matcher.set_san_type(san_type);
+    if (san_type == envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::
+                        SAN_TYPE_UNSPECIFIED) {
+      continue;
+    } else {
+      const SanMatcherPtr matcher = createStringSanMatcher(san_matcher);
+      EXPECT_NE(matcher.get(), nullptr);
+      // Verify that the message is valid.
+      TestUtility::validate(san_matcher);
+    }
+  }
+}
+
+TEST(SanMatcherConfigTest, UnspecifiedSanType) {
+  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher;
+  san_matcher.mutable_matcher()->set_exact("foo.example");
+  // Do not set san_type
+  EXPECT_THROW_WITH_REGEX(TestUtility::validate(san_matcher), EnvoyException,
+                          "Proto constraint validation failed");
+  san_matcher.set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SAN_TYPE_UNSPECIFIED);
+  EXPECT_THROW_WITH_REGEX(TestUtility::validate(san_matcher), EnvoyException,
+                          "Proto constraint validation failed");
+}
+
+} // namespace Tls
+} // namespace TransportSockets
+} // namespace Extensions
+} // namespace Envoy
diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc
index 47acf98c4f5..fe47ef14f6e 100644
--- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc
+++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc
@@ -51,10 +51,30 @@ void SslSPIFFECertValidatorIntegrationTest::checkVerifyErrorCouter(uint64_t valu
       test_server_->counter(listenerStatPrefix("ssl.fail_verify_error"));
   EXPECT_EQ(value, counter->value());
   counter->reset();
 }
 
+void SslSPIFFECertValidatorIntegrationTest::addStringMatcher(
+    const envoy::type::matcher::v3::StringMatcher& matcher) {
+  san_matchers_.emplace_back();
+  *san_matchers_.back().mutable_matcher() = matcher;
+  san_matchers_.back().set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
+  san_matchers_.emplace_back();
+  *san_matchers_.back().mutable_matcher() = matcher;
+  san_matchers_.back().set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI);
+  san_matchers_.emplace_back();
+  *san_matchers_.back().mutable_matcher() = matcher;
+  san_matchers_.back().set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL);
+  san_matchers_.emplace_back();
+  *san_matchers_.back().mutable_matcher() = matcher;
+  san_matchers_.back().set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     IpVersionsClientVersions, SslSPIFFECertValidatorIntegrationTest,
     testing::Combine(
         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
         testing::Values(envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2,
@@ -122,11 +142,11 @@ typed_config:
                             *typed_conf);
   custom_validator_config_ = typed_conf;
 
   envoy::type::matcher::v3::StringMatcher matcher;
   matcher.set_prefix("spiffe://lyft.com/");
-  san_matchers_ = {matcher};
+  addStringMatcher(matcher);
 
   ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr {
     return makeSslClientConnection({});
   };
   testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator);
@@ -150,11 +170,11 @@ typed_config:
 
   envoy::type::matcher::v3::StringMatcher matcher;
   matcher.set_prefix("spiffe://example.com/");
   // The cert has "DNS.1 = lyft.com" but SPIFFE validator must ignore SAN types other than URI.
   matcher.set_prefix("www.lyft.com");
-  san_matchers_ = {matcher};
+  addStringMatcher(matcher);
   initialize();
   auto conn = makeSslClientConnection({});
   if (tls_version_ == envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_2) {
     auto codec = makeRawHttpConnection(std::move(conn), absl::nullopt);
     EXPECT_FALSE(codec->connected());
@@ -221,12 +241,12 @@ typed_config:
     codec->close();
   }
   checkVerifyErrorCouter(1);
 }
 
-// clientcert.pem's san is "spiffe://lyft.com/frontend-team" but the corresponding trust bundle does
-// not match with the client cert. So this should also be rejected.
+// clientcert.pem's san is "spiffe://lyft.com/frontend-team" but the corresponding trust bundle
+// does not match with the client cert. So this should also be rejected.
 TEST_P(SslSPIFFECertValidatorIntegrationTest, ServerRsaSPIFFEValidatorRejected2) {
   auto typed_conf = new envoy::config::core::v3::TypedExtensionConfig();
   TestUtility::loadFromYaml(TestEnvironment::substitute(R"EOF(
 name: envoy.tls.cert_validator.spiffe
 typed_config:
diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h
index b1e57169ea1..01d08f5a811 100644
--- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h
+++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.h
@@ -37,14 +37,15 @@ public:
                                                                                     : "IPv6",
                        std::get<1>(params.param) - 1);
   }
 
 protected:
+  void addStringMatcher(envoy::type::matcher::v3::StringMatcher const& matcher);
   bool allow_expired_cert_{};
   envoy::config::core::v3::TypedExtensionConfig* custom_validator_config_{nullptr};
   std::unique_ptr<ContextManager> context_manager_;
-  std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_;
+  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher> san_matchers_;
   const envoy::extensions::transport_sockets::tls::v3::TlsParameters::TlsProtocol tls_version_{
       std::get<1>(GetParam())};
 };
 
 } // namespace Ssl
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
index 07206e156a7..3be9706e76b 100644
--- 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
@@ -62,17 +62,38 @@ public:
   SslStats& stats() { return stats_; }
 
   // Setter.
   void setAllowExpiredCertificate(bool val) { allow_expired_certificate_ = val; }
   void setSanMatchers(std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers) {
-    san_matchers_ = san_matchers;
+    san_matchers_.clear();
+    for (auto& matcher : san_matchers) {
+      san_matchers_.emplace_back();
+      san_matchers_.back().set_san_type(
+          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
+      *san_matchers_.back().mutable_matcher() = matcher;
+
+      san_matchers_.emplace_back();
+      san_matchers_.back().set_san_type(
+          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI);
+      *san_matchers_.back().mutable_matcher() = matcher;
+
+      san_matchers_.emplace_back();
+      san_matchers_.back().set_san_type(
+          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL);
+      *san_matchers_.back().mutable_matcher() = matcher;
+
+      san_matchers_.emplace_back();
+      san_matchers_.back().set_san_type(
+          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS);
+      *san_matchers_.back().mutable_matcher() = matcher;
+    }
   };
 
 private:
   bool allow_expired_certificate_{false};
   TestCertificateValidationContextConfigPtr config_;
-  std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
+  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher> san_matchers_{};
   Stats::TestUtil::TestStore store_;
   SslStats stats_;
   Event::TestRealTimeSystem time_system_;
   SPIFFEValidatorPtr validator_;
 };
@@ -191,11 +212,12 @@ TEST_F(TestSPIFFEValidator, TestGetTrustBundleStore) {
       "{{ 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"));
+      "{{ 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"));
diff --git a/test/extensions/transport_sockets/tls/cert_validator/test_common.h b/test/extensions/transport_sockets/tls/cert_validator/test_common.h
index 11d4022cf04..0eb5066e1c7 100644
--- a/test/extensions/transport_sockets/tls/cert_validator/test_common.h
+++ b/test/extensions/transport_sockets/tls/cert_validator/test_common.h
@@ -31,11 +31,12 @@ private:
 class TestCertificateValidationContextConfig
     : public Envoy::Ssl::CertificateValidationContextConfig {
 public:
   TestCertificateValidationContextConfig(
       envoy::config::core::v3::TypedExtensionConfig config, bool allow_expired_certificate = false,
-      std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers = {})
+      std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+          san_matchers = {})
       : allow_expired_certificate_(allow_expired_certificate), api_(Api::createApiForTest()),
         custom_validator_config_(config), san_matchers_(san_matchers){};
   TestCertificateValidationContextConfig()
       : api_(Api::createApiForTest()), custom_validator_config_(absl::nullopt){};
 
@@ -45,11 +46,11 @@ public:
     CONSTRUCT_ON_FIRST_USE(std::string, "");
   }
   const std::string& certificateRevocationListPath() const final {
     CONSTRUCT_ON_FIRST_USE(std::string, "");
   }
-  const std::vector<envoy::type::matcher::v3::StringMatcher>&
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
   subjectAltNameMatchers() const override {
     return san_matchers_;
   }
   const std::vector<std::string>& verifyCertificateHashList() const override {
     CONSTRUCT_ON_FIRST_USE(std::vector<std::string>, {});
@@ -76,11 +77,12 @@ public:
 
 private:
   bool allow_expired_certificate_{false};
   Api::ApiPtr api_;
   const absl::optional<envoy::config::core::v3::TypedExtensionConfig> custom_validator_config_;
-  const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+      san_matchers_{};
 };
 
 } // namespace Tls
 } // namespace TransportSockets
 } // namespace Extensions
diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc
index 2e6c67a1da1..f4568860b22 100644
--- a/test/extensions/transport_sockets/tls/context_impl_test.cc
+++ b/test/extensions/transport_sockets/tls/context_impl_test.cc
@@ -921,12 +921,14 @@ TEST_F(SslServerContextImplTicketTest, VerifySanWithNoCA) {
             certificate_chain:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"
             private_key:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"
           validation_context:
-            match_subject_alt_names:
-              exact : "spiffe://lyft.com/testclient"
+            match_typed_subject_alt_names:
+            - san_type: URI
+              matcher:
+                exact: "spiffe://lyft.com/testclient"
 )EOF";
   EXPECT_THROW_WITH_MESSAGE(loadConfigYaml(yaml), EnvoyException,
                             "SAN-based verification of peer certificates without trusted CA "
                             "is insecure and not allowed");
 }
@@ -1905,10 +1907,39 @@ TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureBothKeyAndMethod)
   EXPECT_THROW_WITH_MESSAGE(
       ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException,
       "Certificate configuration can't have both private_key and private_key_provider");
 }
 
+// Test that we don't allow specification of both typed and untyped matchers for
+// sans.
+TEST_F(ServerContextConfigImplTest, DeprecatedSanMatcher) {
+  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
+  NiceMock<Ssl::MockContextManager> context_manager;
+  NiceMock<Ssl::MockPrivateKeyMethodManager> private_key_method_manager;
+  auto private_key_method_provider_ptr =
+      std::make_shared<NiceMock<Ssl::MockPrivateKeyMethodProvider>>();
+  const std::string yaml =
+      R"EOF(
+      common_tls_context:
+        validation_context:
+          trusted_ca: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" }
+          allow_expired_certificate: true
+          match_typed_subject_alt_names:
+          - san_type: DNS
+            matcher:
+              exact: "foo.example"
+          match_subject_alt_names:
+            exact: "foo.example"
+      )EOF";
+  TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), tls_context);
+
+  EXPECT_THROW_WITH_MESSAGE(
+      ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException,
+      "SAN-based verification using both match_typed_subject_alt_names and "
+      "the deprecated match_subject_alt_names is not allowed");
+}
+
 TEST_F(ServerContextConfigImplTest, Pkcs12LoadFailureBothPkcs12AndMethod) {
   envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
   NiceMock<Ssl::MockContextManager> context_manager;
   NiceMock<Ssl::MockPrivateKeyMethodManager> private_key_method_manager;
   auto private_key_method_provider_ptr =
diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc
index 751d9e5459b..af9288bbea3 100644
--- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc
+++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc
@@ -1087,12 +1087,14 @@ TEST_P(SslSocketTest, GetUriWithUriSan) {
       private_key:
         filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
     validation_context:
       trusted_ca:
         filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-      match_subject_alt_names:
-        exact: "spiffe://lyft.com/test-team"
+      match_typed_subject_alt_names:
+      - san_type: URI
+        matcher:
+          exact: "spiffe://lyft.com/test-team"
 )EOF";
 
   TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
   testUtil(test_options.setExpectedClientCertUri("spiffe://lyft.com/test-team")
                .setExpectedSerialNumber(TEST_SAN_URI_CERT_SERIAL));
@@ -1103,12 +1105,14 @@ TEST_P(SslSocketTest, Ipv4San) {
   const std::string client_ctx_yaml = R"EOF(
   common_tls_context:
     validation_context:
       trusted_ca:
         filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem"
-      match_subject_alt_names:
-        exact: "127.0.0.1"
+      match_typed_subject_alt_names:
+      - san_type: IP_ADDRESS
+        matcher:
+          exact: "127.0.0.1"
 )EOF";
 
   const std::string server_ctx_yaml = R"EOF(
   common_tls_context:
     tls_certificates:
@@ -1127,12 +1131,14 @@ TEST_P(SslSocketTest, Ipv6San) {
   const std::string client_ctx_yaml = R"EOF(
   common_tls_context:
     validation_context:
       trusted_ca:
         filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem"
-      match_subject_alt_names:
-        exact: "::1"
+      match_typed_subject_alt_names:
+      - san_type: IP_ADDRESS
+        matcher:
+          exact: "::1"
 )EOF";
 
   const std::string server_ctx_yaml = R"EOF(
   common_tls_context:
     tls_certificates:
@@ -1531,12 +1537,14 @@ TEST_P(SslSocketTest, FailedClientAuthSanVerificationNoClientCert) {
       private_key:
         filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
     validation_context:
       trusted_ca:
         filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-      match_subject_alt_names:
-        exact: "example.com"
+      match_typed_subject_alt_names:
+      - san_type: DNS
+        matcher:
+          exact: "example.com"
 )EOF";
 
   TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam());
   testUtil(test_options.setExpectedServerStats("ssl.fail_verify_no_cert"));
 }
@@ -1559,12 +1567,14 @@ TEST_P(SslSocketTest, FailedClientAuthSanVerification) {
       private_key:
         filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
     validation_context:
       trusted_ca:
         filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-      match_subject_alt_names:
-        exact: "example.com"
+      match_typed_subject_alt_names:
+      - san_type: DNS
+        matcher:
+          exact: "example.com"
 )EOF";
 
   TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam());
   testUtil(test_options.setExpectedServerStats("ssl.fail_verify_san"));
 }
diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc
index f97ad753fe5..a85242d72d1 100644
--- a/test/integration/ads_integration.cc
+++ b/test/integration/ads_integration.cc
@@ -143,11 +143,14 @@ void AdsIntegrationTest::initializeAds(const bool rate_limiting) {
     ads_cluster->set_name("ads_cluster");
     envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext context;
     auto* validation_context = context.mutable_common_tls_context()->mutable_validation_context();
     validation_context->mutable_trusted_ca()->set_filename(
         TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem"));
-    validation_context->add_match_subject_alt_names()->set_suffix("lyft.com");
+    auto* san_matcher = validation_context->add_match_typed_subject_alt_names();
+    san_matcher->mutable_matcher()->set_suffix("lyft.com");
+    san_matcher->set_san_type(
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
     if (clientType() == Grpc::ClientType::GoogleGrpc) {
       auto* google_grpc = grpc_service->mutable_google_grpc();
       auto* ssl_creds = google_grpc->mutable_channel_credentials()->mutable_ssl_credentials();
       ssl_creds->mutable_root_certs()->set_filename(
           TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem"));
diff --git a/test/integration/ssl_utility.cc b/test/integration/ssl_utility.cc
index 36b93b6f81b..c629d6f2e3c 100644
--- a/test/integration/ssl_utility.cc
+++ b/test/integration/ssl_utility.cc
@@ -64,12 +64,27 @@ void initializeUpstreamTlsContextConfig(
     common_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http2);
     common_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http11);
     common_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http3);
   }
   if (!options.san_.empty()) {
-    common_context->mutable_validation_context()->add_match_subject_alt_names()->set_exact(
-        options.san_);
+    envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher* matcher =
+        common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
+    matcher->mutable_matcher()->set_exact(options.san_);
+    matcher->set_san_type(
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
+    matcher = common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
+    matcher->mutable_matcher()->set_exact(options.san_);
+    matcher->set_san_type(
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI);
+    matcher = common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
+    matcher->mutable_matcher()->set_exact(options.san_);
+    matcher->set_san_type(
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL);
+    matcher = common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
+    matcher->mutable_matcher()->set_exact(options.san_);
+    matcher->set_san_type(
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS);
   }
   for (const std::string& cipher_suite : options.cipher_suites_) {
     common_context->mutable_tls_params()->add_cipher_suites(cipher_suite);
   }
   if (!options.sni_.empty()) {
diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc
index 19186660db2..0ce4d4a9421 100644
--- a/test/integration/xfcc_integration_test.cc
+++ b/test/integration/xfcc_integration_test.cc
@@ -4,10 +4,11 @@
 #include <regex>
 
 #include "envoy/config/bootstrap/v3/bootstrap.pb.h"
 #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h"
 #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
 #include "envoy/stats/scope.h"
 
 #include "source/common/event/dispatcher_impl.h"
 #include "source/common/http/header_map_impl.h"
 #include "source/common/network/utility.h"
@@ -43,25 +44,37 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createClientSslContext(b
   const std::string yaml_tls = R"EOF(
 common_tls_context:
   validation_context:
     trusted_ca:
       filename: {{ test_rundir }}/test/config/integration/certs/cacert.pem
-    match_subject_alt_names:
-      exact: "spiffe://lyft.com/backend-team"
-      exact: "lyft.com"
-      exact: "www.lyft.com"
+    match_typed_subject_alt_names:
+    - san_type: URI
+      matcher:
+        exact: "spiffe://lyft.com/backend-team"
+    - san_type: DNS
+      matcher:
+        exact: "lyft.com"
+    - san_type: DNS
+      matcher:
+        exact: "www.lyft.com"
 )EOF";
 
   const std::string yaml_mtls = R"EOF(
 common_tls_context:
   validation_context:
     trusted_ca:
       filename: {{ test_rundir }}/test/config/integration/certs/cacert.pem
-    match_subject_alt_names:
-      exact: "spiffe://lyft.com/backend-team"
-      exact: "lyft.com"
-      exact: "www.lyft.com"
+    match_typed_subject_alt_names:
+    - san_type: URI
+      matcher:
+        exact: "spiffe://lyft.com/backend-team"
+    - san_type: DNS
+      matcher:
+        exact: "lyft.com"
+    - san_type: DNS
+      matcher:
+       exact: "www.lyft.com"
   tls_certificates:
     certificate_chain:
       filename: {{ test_rundir }}/test/config/integration/certs/clientcert.pem
     private_key:
       filename: {{ test_rundir }}/test/config/integration/certs/clientkey.pem
@@ -133,11 +146,14 @@ void XfccIntegrationTest::initialize() {
         bootstrap.mutable_static_resources()->mutable_clusters(0)->mutable_transport_socket();
     envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext context;
     auto* validation_context = context.mutable_common_tls_context()->mutable_validation_context();
     validation_context->mutable_trusted_ca()->set_filename(
         TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem"));
-    validation_context->add_match_subject_alt_names()->set_suffix("lyft.com");
+    auto* san_matcher = validation_context->add_match_typed_subject_alt_names();
+    san_matcher->mutable_matcher()->set_suffix("lyft.com");
+    san_matcher->set_san_type(
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
     transport_socket->set_name("envoy.transport_sockets.tls");
     transport_socket->mutable_typed_config()->PackFrom(context);
   });
 
   if (tls_) {
diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h
index 130a78b83c6..104242ce0c9 100644
--- a/test/mocks/ssl/mocks.h
+++ b/test/mocks/ssl/mocks.h
@@ -157,12 +157,13 @@ class MockCertificateValidationContextConfig : public CertificateValidationConte
 public:
   MOCK_METHOD(const std::string&, caCert, (), (const));
   MOCK_METHOD(const std::string&, caCertPath, (), (const));
   MOCK_METHOD(const std::string&, certificateRevocationList, (), (const));
   MOCK_METHOD(const std::string&, certificateRevocationListPath, (), (const));
-  MOCK_METHOD(const std::vector<envoy::type::matcher::v3::StringMatcher>&, subjectAltNameMatchers,
-              (), (const));
+  MOCK_METHOD(
+      const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&,
+      subjectAltNameMatchers, (), (const));
   MOCK_METHOD(const std::vector<std::string>&, verifyCertificateHashList, (), (const));
   MOCK_METHOD(const std::vector<std::string>&, verifyCertificateSpkiList, (), (const));
   MOCK_METHOD(bool, allowExpiredCertificate, (), (const));
   MOCK_METHOD(const absl::optional<envoy::config::core::v3::TypedExtensionConfig>&,
               customValidatorConfig, (), (const));
diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh
index 184a5485da2..93ba06fd0c3 100755
--- a/test/per_file_coverage.sh
+++ b/test/per_file_coverage.sh
@@ -69,11 +69,11 @@ declare -a KNOWN_LOW_COVERAGE=(
 "source/extensions/tracers/opencensus:94.8"
 "source/extensions/tracers/xray:96.2"
 "source/extensions/tracers/zipkin:96.1"
 "source/extensions/transport_sockets:95.3"
 "source/extensions/transport_sockets/tls:94.5"
-"source/extensions/transport_sockets/tls/cert_validator:95.8"
+"source/extensions/transport_sockets/tls/cert_validator:95.7"
 "source/extensions/transport_sockets/tls/ocsp:96.5"
 "source/extensions/transport_sockets/tls/private_key:77.8"
 "source/extensions/wasm_runtime/wamr:0.0" # Not enabled in coverage build
 "source/extensions/wasm_runtime/wasmtime:0.0" # Not enabled in coverage build
 "source/extensions/wasm_runtime/wavm:0.0" # Not enabled in coverage build
diff --git a/test/server/listener_manager_impl_quic_only_test.cc b/test/server/listener_manager_impl_quic_only_test.cc
index 84e1a3a200c..f505b467c99 100644
--- a/test/server/listener_manager_impl_quic_only_test.cc
+++ b/test/server/listener_manager_impl_quic_only_test.cc
@@ -58,13 +58,17 @@ filter_chains:
             private_key:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
           validation_context:
             trusted_ca:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-            match_subject_alt_names:
-            - exact: localhost
-            - exact: 127.0.0.1
+            match_typed_subject_alt_names:
+            - matcher:
+                exact: localhost
+              san_type: URI
+            - matcher:
+                exact: 127.0.0.1
+              san_type: IP_ADDRESS
 udp_listener_config:
   quic_options: {}
   )EOF",
                                                        Network::Address::IpVersion::v4);
 
@@ -159,13 +163,17 @@ filter_chains:
           private_key:
             filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
         validation_context:
           trusted_ca:
             filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-          match_subject_alt_names:
-          - exact: localhost
-          - exact: 127.0.0.1
+          match_typed_subject_alt_names:
+          - matcher:
+              exact: localhost
+            san_type: URI
+          - matcher:
+              exact: 127.0.0.1
+            san_type: IP_ADDRESS
 udp_listener_config:
   quic_options: {}
   )EOF",
                                                        Network::Address::IpVersion::v4);
 
@@ -203,13 +211,17 @@ filter_chains:
             private_key:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
           validation_context:
             trusted_ca:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-            match_subject_alt_names:
-            - exact: localhost
-            - exact: 127.0.0.1
+            match_typed_subject_alt_names:
+            - matcher:
+                exact: localhost
+              san_type: URI
+            - matcher:
+                exact: 127.0.0.1
+              san_type: IP_ADDRESS
 udp_listener_config:
   quic_options: {}
   )EOF",
                                                        Network::Address::IpVersion::v4);
 
@@ -257,13 +269,17 @@ filter_chains:
             private_key:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
           validation_context:
             trusted_ca:
               filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
-            match_subject_alt_names:
-            - exact: localhost
-            - exact: 127.0.0.1
+            match_typed_subject_alt_names:
+            - matcher:
+                exact: localhost
+              san_type: URI
+            - matcher:
+                exact: 127.0.0.1
+              san_type: IP_ADDRESS
 udp_listener_config:
   quic_options: {}
 connection_balance_config:
   exact_balance: {}
   )EOF",

Vulnerable code: // [#next-free-field: 15]
  // :ref:`match_subject_alt_names
  // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>`) is also
  //  match_subject_alt_names:
  //    exact: "api.example.com"
  repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9;
// - :ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types.
            match_subject_alt_names:
              exact: "front-proxy.yourcompany.net"
            match_subject_alt_names:
              exact: "collector-grpc.lightstep.com"
            match_subject_alt_names:
              exact: "{{host['verify_subject_alt_name'] }}"
            match_subject_alt_names:
              exact: "collector-grpc.lightstep.com"
            match_subject_alt_names:
            - exact: "foo"
:ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>`
              match_subject_alt_names:
              - exact: proxy-postgres-frontend.example.com
            match_subject_alt_names:
            - exact: proxy-postgres-backend.example.com
   this is what you should use when matching with ``match_subject_alt_names``.
   :lines: 27-39
:ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>`,
   :lines: 27-39
   :lineno-start: 44
   :lines: 44-68
  virtual const std::vector<envoy::type::matcher::v3::StringMatcher>&
              match_subject_alt_names:
              - exact: proxy-postgres-frontend.example.com
            match_subject_alt_names:
            - exact: proxy-postgres-backend.example.com
      subject_alt_name_matchers_(config.match_subject_alt_names().begin(),
                                 config.match_subject_alt_names().end()),
  const std::vector<envoy::type::matcher::v3::StringMatcher>&
  const std::vector<envoy::type::matcher::v3::StringMatcher> subject_alt_name_matchers_;
      for (const envoy::type::matcher::v3::StringMatcher& matcher :
        subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher));
  // `NotValidated` or `Validated`). If `trusted_ca` doesn't exist, we will need to make sure other
  // configurations are verified and the verification succeed.
    const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
        subject_alt_name_matchers) {
    X509* cert,
    const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
        subject_alt_name_matchers) {
  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
              ? Utility::dnsNameMatch(config_san_matcher.matcher().exact(), absl::string_view(san))
              : config_san_matcher.match(san)) {
  Envoy::Ssl::ClientValidationStatus verifyCertificate(
      X509* cert, const std::vector<std::string>& verify_san_list,
      const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
          subject_alt_name_matchers);
  static bool matchSubjectAltName(
      X509* cert,
      const std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>&
          subject_alt_name_matchers);
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers_;
      subject_alt_name_matchers_.push_back(Matchers::StringMatcherImpl(matcher));
  // Only match against URI SAN since SPIFFE specification does not restrict values in other SAN
  // types. See the discussion: https://github.com/envoyproxy/envoy/issues/15392
    if (general_name->type == GEN_URI) {
      const std::string san = Utility::generalNameAsString(general_name);
      for (const auto& config_san_matcher : subject_alt_name_matchers_) {
        if (config_san_matcher.match(san)) {
          return true;
        }
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers_{};
    const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers;
  const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_;
  dynamic_cvc->add_match_subject_alt_names()->set_exact("second san");
  default_cvc.add_match_subject_alt_names()->set_exact("first san");
  EXPECT_EQ("first san", cvc_config.subjectAltNameMatchers()[0].exact());
  EXPECT_EQ("second san", cvc_config.subjectAltNameMatchers()[1].exact());
              match_subject_alt_names:
              - exact: localhost
              - exact: 127.0.0.1
              match_subject_alt_names:
              - exact: localhost
              - exact: 127.0.0.1
              match_subject_alt_names:
              - exact: localhost
              - exact: 127.0.0.1
              match_subject_alt_names:
              - exact: localhost
              - exact: 127.0.0.1
    *validation_context->mutable_match_subject_alt_names() = {options.san_matchers_.begin(),
                                                              options.san_matchers_.end()};
    setSanMatchers(std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers) {
    std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
  matcher.MergeFrom(TestUtility::createRegexMatcher(".*.example.com"));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  matcher.MergeFrom(TestUtility::createRegexMatcher("spiffe://lyft.com/.*-team"));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  matcher.MergeFrom(TestUtility::createRegexMatcher(".*.foo.com"));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  matcher.MergeFrom(TestUtility::createRegexMatcher(".*.example.com"));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>> san_matchers;
  san_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  std::vector<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>
      invalid_san_matchers;
  invalid_san_matchers.push_back(Matchers::StringMatcherImpl(matcher));
  san_matchers_ = {matcher};
  san_matchers_ = {matcher};
// 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.
  std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_;
    san_matchers_ = san_matchers;
  std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem"));
      std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers = {})
  const std::vector<envoy::type::matcher::v3::StringMatcher>&
  const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
            match_subject_alt_names:
              exact : "spiffe://lyft.com/testclient"
      match_subject_alt_names:
        exact: "spiffe://lyft.com/test-team"
      match_subject_alt_names:
        exact: "127.0.0.1"
      match_subject_alt_names:
        exact: "::1"
      match_subject_alt_names:
        exact: "example.com"
      match_subject_alt_names:
        exact: "example.com"
    validation_context->add_match_subject_alt_names()->set_suffix("lyft.com");
    common_context->mutable_validation_context()->add_match_subject_alt_names()->set_exact(
        options.san_);
    match_subject_alt_names:
      exact: "spiffe://lyft.com/backend-team"
      exact: "lyft.com"
      exact: "www.lyft.com"
    match_subject_alt_names:
      exact: "spiffe://lyft.com/backend-team"
      exact: "lyft.com"
      exact: "www.lyft.com"
    validation_context->add_match_subject_alt_names()->set_suffix("lyft.com");
  MOCK_METHOD(const std::vector<envoy::type::matcher::v3::StringMatcher>&, subjectAltNameMatchers,
              (), (const));
"source/extensions/transport_sockets/tls/cert_validator:95.8"
            match_subject_alt_names:
            - exact: localhost
            - exact: 127.0.0.1
          match_subject_alt_names:
          - exact: localhost
          - exact: 127.0.0.1
            match_subject_alt_names:
            - exact: localhost
            - exact: 127.0.0.1
            match_subject_alt_names:
            - exact: localhost
            - exact: 127.0.0.1
Fixed code: import "envoy/annotations/deprecation.proto";
// Matcher for subject alternative names, to match both type and value of the SAN.
message SubjectAltNameMatcher {
  // Indicates the choice of GeneralName as defined in section 4.2.1.5 of RFC 5280 to match
  // against.
  enum SanType {
    SAN_TYPE_UNSPECIFIED = 0;
    EMAIL = 1;
    DNS = 2;
    URI = 3;
    IP_ADDRESS = 4;
  }

  // Specification of type of SAN. Note that the default enum value is an invalid choice.
  SanType san_type = 1 [(validate.rules).enum = {defined_only: true not_in: 0}];

  // Matcher for SAN value.
  type.matcher.v3.StringMatcher matcher = 2 [(validate.rules).message = {required: true}];
}

// [#next-free-field: 16]
  // :ref:`match_typed_subject_alt_names
  // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`) is also
  // The matching uses "any" semantics, that is to say, the SAN is verified if at least one matcher is
  // matched.
  //  match_typed_subject_alt_names:
  //  - san_type: DNS
  //    matcher:
  //      exact: "api.example.com"
  repeated SubjectAltNameMatcher match_typed_subject_alt_names = 15;

  // This field is deprecated in favor of ref:`match_typed_subject_alt_names
  // <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`
  repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9
      [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];
// - :ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types.
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: "front-proxy.yourcompany.net"
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: "collector-grpc.lightstep.com"
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: "{{host['verify_subject_alt_name'] }}"
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: "collector-grpc.lightstep.com"
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: "foo"
:ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`
              match_typed_subject_alt_names:
              - san_type: DNS
                matcher:
                  exact: proxy-postgres-frontend.example.com
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: proxy-postgres-backend.example.com
   this is what you should use when matching with ``match_typed_subject_alt_names``.
   :lines: 27-41
:ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`,
   :lines: 27-41
   :lineno-start: 46
   :lines: 46-70
* tls: added support for :ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>` for subject alternative names to enforce specifying the subject alternative name type for the matcher to prevent matching against an unintended type in the certificate.
* tls: :ref:`match_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_subject_alt_names>` has been deprecated in favor of the :ref:`match_typed_subject_alt_names <envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`.
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
  virtual const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
              match_typed_subject_alt_names:
              - san_type: DNS
                matcher:
                  exact: proxy-postgres-frontend.example.com
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: proxy-postgres-backend.example.com
        "@envoy_api//envoy/config/core/v3:pkg_cc_proto",
#include "envoy/config/core/v3/extension.pb.h"
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
#include "source/common/common/logger.h"
      subject_alt_name_matchers_(getSubjectAltNameMatchers(config)),
std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
CertificateValidationContextConfigImpl::getSubjectAltNameMatchers(
    const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config) {
  if (!config.match_typed_subject_alt_names().empty() &&
      !config.match_subject_alt_names().empty()) {
    throw EnvoyException("SAN-based verification using both match_typed_subject_alt_names and "
                         "the deprecated match_subject_alt_names is not allowed");
  }
  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
      subject_alt_name_matchers(config.match_typed_subject_alt_names().begin(),
                                config.match_typed_subject_alt_names().end());
  // Handle deprecated string type san matchers without san type specified, by
  // creating a matcher for each supported type.
  for (const envoy::type::matcher::v3::StringMatcher& matcher : config.match_subject_alt_names()) {
    static constexpr std::array<
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType, 4>
        san_types{envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI,
                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL,
                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS};
    for (const auto san_type : san_types) {
      subject_alt_name_matchers.emplace_back();
      subject_alt_name_matchers.back().set_san_type(san_type);
      *subject_alt_name_matchers.back().mutable_matcher() = matcher;
    }
  }
  return subject_alt_name_matchers;
}

#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
  static std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
  getSubjectAltNameMatchers(
      const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config);
  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
      subject_alt_name_matchers_;
        "san_matcher.cc",
        "san_matcher.h",
        "//source/common/config:utility_lib",
        "@envoy_api//envoy/config/core/v3:pkg_cc_proto",
        "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
        "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto",
#include "source/common/config/utility.h"
      for (const envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher& matcher :
        subject_alt_name_matchers_.emplace_back(createStringSanMatcher(matcher));
  // `NotValidated` or `Validated`). If `trusted_ca` doesn't exist, we will need to make sure
  // other configurations are verified and the verification succeed.
    const std::vector<SanMatcherPtr>& subject_alt_name_matchers) {
    X509* cert, const std::vector<SanMatcherPtr>& subject_alt_name_matchers) {
  for (const auto& config_san_matcher : subject_alt_name_matchers) {
    for (const GENERAL_NAME* general_name : san_names.get()) {
      if (config_san_matcher->match(general_name)) {
#include <utility>
#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
  Envoy::Ssl::ClientValidationStatus
  verifyCertificate(X509* cert, const std::vector<std::string>& verify_san_list,
                    const std::vector<SanMatcherPtr>& subject_alt_name_matchers);
  static bool matchSubjectAltName(X509* cert,
                                  const std::vector<SanMatcherPtr>& subject_alt_name_matchers);
  std::vector<SanMatcherPtr> subject_alt_name_matchers_;
#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"

#include <memory>

#include "envoy/config/core/v3/extension.pb.h"
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
#include "envoy/registry/registry.h"
#include "envoy/ssl/certificate_validation_context_config.h"

#include "source/extensions/transport_sockets/tls/utility.h"

namespace Envoy {
namespace Extensions {
namespace TransportSockets {
namespace Tls {

bool StringSanMatcher::match(const GENERAL_NAME* general_name) const {
  if (general_name->type != general_name_type_) {
    return false;
  }
  // For DNS SAN, if the StringMatcher type is exact, we have to follow DNS matching semantics.
  const std::string san = Utility::generalNameAsString(general_name);
  return general_name->type == GEN_DNS &&
                 matcher_.matcher().match_pattern_case() ==
                     envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kExact
             ? Utility::dnsNameMatch(matcher_.matcher().exact(), absl::string_view(san))
             : matcher_.match(san);
}

SanMatcherPtr createStringSanMatcher(
    envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher const& matcher) {
  // Verify that a new san type has not been added.
  static_assert(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MAX ==
                4);

  switch (matcher.san_type()) {
  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS:
    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher.matcher())};
  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL:
    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_EMAIL, matcher.matcher())};
  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI:
    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher.matcher())};
  case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS:
    return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_IPADD, matcher.matcher())};
  default:
    NOT_REACHED_GCOVR_EXCL_LINE;
  }
}

} // namespace Tls
} // namespace TransportSockets
} // namespace Extensions
} // namespace Envoy
#pragma once

#include <memory>

#include "envoy/config/core/v3/extension.pb.h"
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
#include "envoy/ssl/certificate_validation_context_config.h"
#include "envoy/type/matcher/v3/string.pb.h"

#include "source/common/common/hash.h"
#include "source/common/common/matchers.h"
#include "source/common/protobuf/protobuf.h"
#include "source/extensions/transport_sockets/tls/utility.h"

#include "openssl/x509v3.h"

namespace Envoy {
namespace Extensions {
namespace TransportSockets {
namespace Tls {

/** Interface to verify if there is a match in a list of subject alternative
 * names.
 */
class SanMatcher {
public:
  virtual bool match(GENERAL_NAME const*) const PURE;
  virtual ~SanMatcher() = default;
};

using SanMatcherPtr = std::unique_ptr<SanMatcher>;

class StringSanMatcher : public SanMatcher {
public:
  bool match(const GENERAL_NAME* general_name) const override;
  ~StringSanMatcher() override = default;
  StringSanMatcher(int general_name_type, envoy::type::matcher::v3::StringMatcher matcher)
      : general_name_type_(general_name_type), matcher_(matcher) {}

private:
  const int general_name_type_;
  const Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher> matcher_;
};

SanMatcherPtr createStringSanMatcher(
    const envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher& matcher);

} // namespace Tls
} // namespace TransportSockets
} // namespace Extensions
} // namespace Envoy
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
      if (matcher.san_type() ==
          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI) {
        // Only match against URI SAN since SPIFFE specification does not restrict values in other
        // SAN types. See the discussion: https://github.com/envoyproxy/envoy/issues/15392
        // TODO(pradeepcrao): Throw an exception when a non-URI matcher is encountered after the
        // deprecated field match_subject_alt_names is removed
        subject_alt_name_matchers_.emplace_back(createStringSanMatcher(matcher));
      }
    for (const auto& config_san_matcher : subject_alt_name_matchers_) {
      if (config_san_matcher->match(general_name)) {
        return true;
#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
  std::vector<SanMatcherPtr> subject_alt_name_matchers_{};
    const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
        san_matchers;
  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
      san_matchers_;
  auto* san_matcher = dynamic_cvc->add_match_typed_subject_alt_names();
  san_matcher->mutable_matcher()->set_exact("second san");
  san_matcher->set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
  san_matcher = default_cvc.add_match_typed_subject_alt_names();
  san_matcher->mutable_matcher()->set_exact("first san");
  san_matcher->set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
  EXPECT_EQ("first san", cvc_config.subjectAltNameMatchers()[0].matcher().exact());
  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
            cvc_config.subjectAltNameMatchers()[0].san_type());
  EXPECT_EQ("second san", cvc_config.subjectAltNameMatchers()[1].matcher().exact());
  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
            cvc_config.subjectAltNameMatchers()[1].san_type());
// Verify that using the match_subject_alt_names will result in a typed matcher, one for each of
// DNS, URI, EMAIL and IP_ADDRESS.
// TODO(pradeepcrao): Delete this test once the deprecated field is removed.
TEST_F(SecretManagerImplTest, DeprecatedSanMatcher) {
  envoy::extensions::transport_sockets::tls::v3::Secret secret_config;
  const std::string yaml =
      R"EOF(
      name: "abc.com"
      validation_context:
        trusted_ca: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" }
        allow_expired_certificate: true
        match_subject_alt_names:
          exact: "example.foo"
      )EOF";
  TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), secret_config);
  std::unique_ptr<SecretManager> secret_manager(new SecretManagerImpl(config_tracker_));
  secret_manager->addStaticSecret(secret_config);

  ASSERT_EQ(secret_manager->findStaticCertificateValidationContextProvider("undefined"), nullptr);
  ASSERT_NE(secret_manager->findStaticCertificateValidationContextProvider("abc.com"), nullptr);
  Ssl::CertificateValidationContextConfigImpl cvc_config(
      *secret_manager->findStaticCertificateValidationContextProvider("abc.com")->secret(), *api_);
  EXPECT_EQ(cvc_config.subjectAltNameMatchers().size(), 4);
  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[0].matcher().exact());
  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
            cvc_config.subjectAltNameMatchers()[0].san_type());
  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[1].matcher().exact());
  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI,
            cvc_config.subjectAltNameMatchers()[1].san_type());
  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[2].matcher().exact());
  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL,
            cvc_config.subjectAltNameMatchers()[2].san_type());
  EXPECT_EQ("example.foo", cvc_config.subjectAltNameMatchers()[3].matcher().exact());
  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS,
            cvc_config.subjectAltNameMatchers()[3].san_type());
}

              match_typed_subject_alt_names:
              - matcher:
                  exact: localhost
                san_type: URI
              - matcher:
                  exact: 127.0.0.1
                san_type: IP_ADDRESS
              match_typed_subject_alt_names:
              - matcher:
                  exact: localhost
                san_type: URI
              - matcher:
                  exact: 127.0.0.1
                san_type: IP_ADDRESS
              match_typed_subject_alt_names:
              - matcher:
                  exact: localhost
                san_type: URI
              - matcher:
                  exact: 127.0.0.1
                san_type: IP_ADDRESS
              match_typed_subject_alt_names:
              - matcher:
                  exact: localhost
                san_type: URI
              - matcher:
                  exact: 127.0.0.1
                san_type: IP_ADDRESS
    *validation_context->mutable_match_typed_subject_alt_names() = {options.san_matchers_.begin(),
                                                                    options.san_matchers_.end()};
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
    setSanMatchers(std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
                       san_matchers) {
    std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
        san_matchers_{};

envoy_cc_test(
    name = "san_matcher_test",
    srcs = [
        "san_matcher_test.cc",
    ],
    deps = [
        "//source/common/protobuf:utility_lib",
        "//source/extensions/transport_sockets/tls/cert_validator:cert_validator_lib",
        "//test/test_common:utility_lib",
        "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
    ],
)
#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"
  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw"));
  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
TEST(DefaultCertValidatorTest, TestMatchSubjectAltNameIncorrectTypeMatched) {
  bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
  envoy::type::matcher::v3::StringMatcher matcher;
  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw"));
  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher)});
  EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
}

  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw(spiffe://lyft.com/[^/]*-team)raw"));
  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher)});
  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example\.net)raw"));
  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_IPADD, matcher)});
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher)});
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_EMAIL, matcher)});
  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example.com)raw"));
  std::vector<SanMatcherPtr> san_matchers;
  san_matchers.push_back(SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
  std::vector<SanMatcherPtr> invalid_san_matchers;
  invalid_san_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
TEST(DefaultCertValidatorTest, NoSanInCert) {
  bssl::UniquePtr<X509> cert = readCertFromFile(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/fake_ca_cert.pem"));
  envoy::type::matcher::v3::StringMatcher matcher;
  matcher.MergeFrom(TestUtility::createRegexMatcher(R"raw([^.]*\.example\.net)raw"));
  std::vector<SanMatcherPtr> subject_alt_name_matchers;
  subject_alt_name_matchers.push_back(
      SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_DNS, matcher)});
  EXPECT_FALSE(DefaultCertValidator::matchSubjectAltName(cert.get(), subject_alt_name_matchers));
}

#include "envoy/extensions/transport_sockets/tls/v3/common.pb.validate.h"

#include "source/common/protobuf/message_validator_impl.h"
#include "source/common/protobuf/utility.h"
#include "source/extensions/transport_sockets/tls/cert_validator/san_matcher.h"

#include "test/test_common/utility.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace Envoy {
namespace Extensions {
namespace TransportSockets {
namespace Tls {

// Verify that we get a valid string san matcher for all valid san types.
TEST(SanMatcherConfigTest, TestValidSanType) {
  // Iterate over all san type enums.
  for (envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType san_type =
           envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MIN;
       san_type <=
       envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MAX;
       san_type = static_cast<
           envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType>(
           static_cast<int>(san_type + 1))) {
    envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher;
    san_matcher.mutable_matcher()->set_exact("foo.example");
    san_matcher.set_san_type(san_type);
    if (san_type == envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::
                        SAN_TYPE_UNSPECIFIED) {
      continue;
    } else {
      const SanMatcherPtr matcher = createStringSanMatcher(san_matcher);
      EXPECT_NE(matcher.get(), nullptr);
      // Verify that the message is valid.
      TestUtility::validate(san_matcher);
    }
  }
}

TEST(SanMatcherConfigTest, UnspecifiedSanType) {
  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher;
  san_matcher.mutable_matcher()->set_exact("foo.example");
  // Do not set san_type
  EXPECT_THROW_WITH_REGEX(TestUtility::validate(san_matcher), EnvoyException,
                          "Proto constraint validation failed");
  san_matcher.set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SAN_TYPE_UNSPECIFIED);
  EXPECT_THROW_WITH_REGEX(TestUtility::validate(san_matcher), EnvoyException,
                          "Proto constraint validation failed");
}

} // namespace Tls
} // namespace TransportSockets
} // namespace Extensions
} // namespace Envoy
void SslSPIFFECertValidatorIntegrationTest::addStringMatcher(
    const envoy::type::matcher::v3::StringMatcher& matcher) {
  san_matchers_.emplace_back();
  *san_matchers_.back().mutable_matcher() = matcher;
  san_matchers_.back().set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
  san_matchers_.emplace_back();
  *san_matchers_.back().mutable_matcher() = matcher;
  san_matchers_.back().set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI);
  san_matchers_.emplace_back();
  *san_matchers_.back().mutable_matcher() = matcher;
  san_matchers_.back().set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL);
  san_matchers_.emplace_back();
  *san_matchers_.back().mutable_matcher() = matcher;
  san_matchers_.back().set_san_type(
      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS);
}

  addStringMatcher(matcher);
  addStringMatcher(matcher);
// 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.
  void addStringMatcher(envoy::type::matcher::v3::StringMatcher const& matcher);
  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher> san_matchers_;
    san_matchers_.clear();
    for (auto& matcher : san_matchers) {
      san_matchers_.emplace_back();
      san_matchers_.back().set_san_type(
          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
      *san_matchers_.back().mutable_matcher() = matcher;

      san_matchers_.emplace_back();
      san_matchers_.back().set_san_type(
          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI);
      *san_matchers_.back().mutable_matcher() = matcher;

      san_matchers_.emplace_back();
      san_matchers_.back().set_san_type(
          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL);
      *san_matchers_.back().mutable_matcher() = matcher;

      san_matchers_.emplace_back();
      san_matchers_.back().set_san_type(
          envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS);
      *san_matchers_.back().mutable_matcher() = matcher;
    }
  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher> san_matchers_{};
      "{{ test_rundir "
      "}}/test/extensions/transport_sockets/tls/test_data/non_spiffe_san_cert.pem"));
      std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
          san_matchers = {})
  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
      san_matchers_{};
            match_typed_subject_alt_names:
            - san_type: URI
              matcher:
                exact: "spiffe://lyft.com/testclient"
// Test that we don't allow specification of both typed and untyped matchers for
// sans.
TEST_F(ServerContextConfigImplTest, DeprecatedSanMatcher) {
  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
  NiceMock<Ssl::MockContextManager> context_manager;
  NiceMock<Ssl::MockPrivateKeyMethodManager> private_key_method_manager;
  auto private_key_method_provider_ptr =
      std::make_shared<NiceMock<Ssl::MockPrivateKeyMethodProvider>>();
  const std::string yaml =
      R"EOF(
      common_tls_context:
        validation_context:
          trusted_ca: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" }
          allow_expired_certificate: true
          match_typed_subject_alt_names:
          - san_type: DNS
            matcher:
              exact: "foo.example"
          match_subject_alt_names:
            exact: "foo.example"
      )EOF";
  TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), tls_context);

  EXPECT_THROW_WITH_MESSAGE(
      ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException,
      "SAN-based verification using both match_typed_subject_alt_names and "
      "the deprecated match_subject_alt_names is not allowed");
}

      match_typed_subject_alt_names:
      - san_type: URI
        matcher:
          exact: "spiffe://lyft.com/test-team"
      match_typed_subject_alt_names:
      - san_type: IP_ADDRESS
        matcher:
          exact: "127.0.0.1"
      match_typed_subject_alt_names:
      - san_type: IP_ADDRESS
        matcher:
          exact: "::1"
      match_typed_subject_alt_names:
      - san_type: DNS
        matcher:
          exact: "example.com"
      match_typed_subject_alt_names:
      - san_type: DNS
        matcher:
          exact: "example.com"
    auto* san_matcher = validation_context->add_match_typed_subject_alt_names();
    san_matcher->mutable_matcher()->set_suffix("lyft.com");
    san_matcher->set_san_type(
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
    envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher* matcher =
        common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
    matcher->mutable_matcher()->set_exact(options.san_);
    matcher->set_san_type(
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
    matcher = common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
    matcher->mutable_matcher()->set_exact(options.san_);
    matcher->set_san_type(
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI);
    matcher = common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
    matcher->mutable_matcher()->set_exact(options.san_);
    matcher->set_san_type(
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL);
    matcher = common_context->mutable_validation_context()->add_match_typed_subject_alt_names();
    matcher->mutable_matcher()->set_exact(options.san_);
    matcher->set_san_type(
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS);
#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"
    match_typed_subject_alt_names:
    - san_type: URI
      matcher:
        exact: "spiffe://lyft.com/backend-team"
    - san_type: DNS
      matcher:
        exact: "lyft.com"
    - san_type: DNS
      matcher:
        exact: "www.lyft.com"
    match_typed_subject_alt_names:
    - san_type: URI
      matcher:
        exact: "spiffe://lyft.com/backend-team"
    - san_type: DNS
      matcher:
        exact: "lyft.com"
    - san_type: DNS
      matcher:
       exact: "www.lyft.com"
    auto* san_matcher = validation_context->add_match_typed_subject_alt_names();
    san_matcher->mutable_matcher()->set_suffix("lyft.com");
    san_matcher->set_san_type(
        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
  MOCK_METHOD(
      const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&,
      subjectAltNameMatchers, (), (const));
"source/extensions/transport_sockets/tls/cert_validator:95.7"
            match_typed_subject_alt_names:
            - matcher:
                exact: localhost
              san_type: URI
            - matcher:
                exact: 127.0.0.1
              san_type: IP_ADDRESS
          match_typed_subject_alt_names:
          - matcher:
              exact: localhost
            san_type: URI
          - matcher:
              exact: 127.0.0.1
            san_type: IP_ADDRESS
            match_typed_subject_alt_names:
            - matcher:
                exact: localhost
              san_type: URI
            - matcher:
                exact: 127.0.0.1
              san_type: IP_ADDRESS
            match_typed_subject_alt_names:
            - matcher:
                exact: localhost
              san_type: URI
            - matcher:
                exact: 127.0.0.1
              san_type: IP_ADDRESS