Skip to content

Commit

Permalink
tls: Add support for matching against OtherName SAN type (#34471)
Browse files Browse the repository at this point in the history
In the current SAN matcher, only DNS, URI, IP, EMAIL types are supported. This change adds support to match against OtherName. A new config field oid is added which helps define the type of OtherName SAN envoy needs to match against.

Signed-off-by: Arul Thileeban Sagayam <[email protected]>
  • Loading branch information
arulthileeban authored Jun 27, 2024
1 parent 8646385 commit aef38c4
Show file tree
Hide file tree
Showing 27 changed files with 1,035 additions and 3 deletions.
16 changes: 16 additions & 0 deletions api/envoy/extensions/transport_sockets/tls/v3/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,29 @@ message SubjectAltNameMatcher {
DNS = 2;
URI = 3;
IP_ADDRESS = 4;
OTHER_NAME = 5;
}

// 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.
//
// The string matching for OTHER_NAME SAN values depends on their ASN.1 type:
//
// * OBJECT: Validated against its dotted numeric notation (e.g., "1.2.3.4")
// * BOOLEAN: Validated against strings "true" or "false"
// * INTEGER/ENUMERATED: Validated against a string containing the integer value
// * NULL: Validated against an empty string
// * Other types: Validated directly against the string value
type.matcher.v3.StringMatcher matcher = 2 [(validate.rules).message = {required: true}];

// OID Value which is required if OTHER_NAME SAN type is used.
// For example, UPN OID is 1.3.6.1.4.1.311.20.2.3
// (Reference: http://oid-info.com/get/1.3.6.1.4.1.311.20.2.3).
//
// If set for SAN types other than OTHER_NAME, it will be ignored.
string oid = 3;
}

// [#next-free-field: 18]
Expand Down
6 changes: 6 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ new_features:
Added :ref:`strip_failure_response
<envoy_v3_api_field_extensions.filters.http.jwt_authn.v3.JwtAuthentication.strip_failure_response>`
to allow stripping the failure response details from the JWT authentication filter.
- area: tls
change: |
added support to match against ``OtherName`` SAN Type under :ref:`match_typed_subject_alt_names
<envoy_v3_api_field_extensions.transport_sockets.tls.v3.CertificateValidationContext.match_typed_subject_alt_names>`.
An additional field ``oid`` is added to :ref:`SubjectAltNameMatcher
<envoy_v3_api_msg_extensions.transport_sockets.tls.v3.SubjectAltNameMatcher>` to support this change.
deprecated:
- area: tracing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ CertificateValidationContextConfigImpl::getSubjectAltNameMatchers(
}
// Handle deprecated string type san matchers without san type specified, by
// creating a matcher for each supported type.
// Note: This does not handle otherName 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>
Expand Down
16 changes: 15 additions & 1 deletion source/common/tls/cert_validator/san_matcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ bool StringSanMatcher::match(const GENERAL_NAME* general_name) const {
if (general_name->type != general_name_type_) {
return false;
}
if (general_name->type == GEN_OTHERNAME) {
if (OBJ_cmp(general_name->d.otherName->type_id, general_name_oid_.get())) {
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 &&
Expand All @@ -32,7 +37,7 @@ SanMatcherPtr createStringSanMatcher(
Server::Configuration::CommonFactoryContext& context) {
// Verify that a new san type has not been added.
static_assert(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType_MAX ==
4);
5);

switch (matcher.san_type()) {
PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
Expand All @@ -44,6 +49,15 @@ SanMatcherPtr createStringSanMatcher(
return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_URI, matcher.matcher(), context)};
case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS:
return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_IPADD, matcher.matcher(), context)};
case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::OTHER_NAME: {
// Invalid/Empty OID returns a nullptr from OBJ_txt2obj
bssl::UniquePtr<ASN1_OBJECT> oid(OBJ_txt2obj(matcher.oid().c_str(), 0));
if (oid == nullptr) {
return nullptr;
}
return SanMatcherPtr{std::make_unique<StringSanMatcher>(GEN_OTHERNAME, matcher.matcher(),
context, std::move(oid))};
}
case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SAN_TYPE_UNSPECIFIED:
PANIC("unhandled value");
}
Expand Down
7 changes: 5 additions & 2 deletions source/common/tls/cert_validator/san_matcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ class StringSanMatcher : public SanMatcher {
bool match(const GENERAL_NAME* general_name) const override;
~StringSanMatcher() override = default;
StringSanMatcher(int general_name_type, envoy::type::matcher::v3::StringMatcher matcher,
Server::Configuration::CommonFactoryContext& context)
: general_name_type_(general_name_type), matcher_(matcher, context) {}
Server::Configuration::CommonFactoryContext& context,
bssl::UniquePtr<ASN1_OBJECT>&& general_name_oid = nullptr)
: general_name_type_(general_name_type), matcher_(matcher, context),
general_name_oid_(std::move(general_name_oid)) {}

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

SanMatcherPtr createStringSanMatcher(
Expand Down
130 changes: 130 additions & 0 deletions source/common/tls/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,136 @@ std::string Utility::generalNameAsString(const GENERAL_NAME* general_name) {
}
break;
}
case GEN_OTHERNAME: {
ASN1_TYPE* value = general_name->d.otherName->value;
if (value == nullptr) {
break;
}
switch (value->type) {
case V_ASN1_NULL:
break;
case V_ASN1_BOOLEAN:
san = value->value.boolean ? "true" : "false";
break;
case V_ASN1_ENUMERATED:
case V_ASN1_INTEGER: {
BIGNUM san_bn;
BN_init(&san_bn);
value->type == V_ASN1_ENUMERATED ? ASN1_ENUMERATED_to_BN(value->value.enumerated, &san_bn)
: ASN1_INTEGER_to_BN(value->value.integer, &san_bn);
char* san_char = BN_bn2dec(&san_bn);
BN_free(&san_bn);
if (san_char != nullptr) {
san.assign(san_char);
OPENSSL_free(san_char);
}
break;
}
case V_ASN1_OBJECT: {
char tmp_obj[256]; // OID Max length
int obj_len = OBJ_obj2txt(tmp_obj, 256, value->value.object, 1);
if (obj_len > 256 || obj_len < 0) {
break;
}
san.assign(tmp_obj);
break;
}
case V_ASN1_BIT_STRING: {
ASN1_BIT_STRING* tmp_str = value->value.bit_string;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_OCTET_STRING: {
ASN1_OCTET_STRING* tmp_str = value->value.octet_string;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_PRINTABLESTRING: {
ASN1_PRINTABLESTRING* tmp_str = value->value.printablestring;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_T61STRING: {
ASN1_T61STRING* tmp_str = value->value.t61string;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_IA5STRING: {
ASN1_IA5STRING* tmp_str = value->value.ia5string;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_GENERALSTRING: {
ASN1_GENERALSTRING* tmp_str = value->value.generalstring;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_BMPSTRING: {
// `ASN1_BMPSTRING` is encoded using `UCS-4`, which needs conversion to UTF-8.
unsigned char* tmp = nullptr;
if (ASN1_STRING_to_UTF8(&tmp, value->value.bmpstring) < 0) {
break;
}
san.assign(reinterpret_cast<const char*>(tmp));
OPENSSL_free(tmp);
break;
}
case V_ASN1_UNIVERSALSTRING: {
// `ASN1_UNIVERSALSTRING` is encoded using `UCS-4`, which needs conversion to UTF-8.
unsigned char* tmp = nullptr;
if (ASN1_STRING_to_UTF8(&tmp, value->value.universalstring) < 0) {
break;
}
san.assign(reinterpret_cast<const char*>(tmp));
OPENSSL_free(tmp);
break;
}
case V_ASN1_UTCTIME: {
ASN1_UTCTIME* tmp_str = value->value.utctime;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_GENERALIZEDTIME: {
ASN1_GENERALIZEDTIME* tmp_str = value->value.generalizedtime;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_VISIBLESTRING: {
ASN1_VISIBLESTRING* tmp_str = value->value.visiblestring;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_UTF8STRING: {
ASN1_UTF8STRING* tmp_str = value->value.utf8string;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_SET: {
ASN1_STRING* tmp_str = value->value.set;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
case V_ASN1_SEQUENCE: {
ASN1_STRING* tmp_str = value->value.sequence;
san.assign(reinterpret_cast<const char*>(ASN1_STRING_data(tmp_str)),
ASN1_STRING_length(tmp_str));
break;
}
default:
break;
}
}
}
return san;
}
Expand Down
Loading

0 comments on commit aef38c4

Please sign in to comment.