CVE-2022-21656 (C++, published 2022-02-22T23:15Z)
Description: 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.
Severity: MEDIUM (CVSS v3 base score: 5.9)
Weakness: CWE-843 \u2014 Access of Resource Using Incompatible Type ('Type Confusion')
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.
Repository: https://github.com/envoyproxy/envoy
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 message: Specify type for matching Subject Alternative Name. (#18628)

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


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