summaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@verizonmedia.com>2020-11-10 14:08:12 +0000
committerTor Brede Vekterli <vekterli@verizonmedia.com>2020-11-10 14:08:12 +0000
commitaded443066847841b349afc7abea627b47778484 (patch)
treefef4b116efd79a444f46d7a09370b2d216b1b8da /vespalib
parent40cd2c7b371fd8c0b300dd251408eca5fb28bd40 (diff)
Add basic exact matching support for X509 URI SANs
Adds extraction of X509 URI peer credentials during the handshake process as well as a new SAN_URI field to the transport security options peer policy section. This implementation is NOT conformant with RFC 2459 since we don't currently support case insensitive matching of scheme, host etc., but it's good enough for our purposes for now.
Diffstat (limited to 'vespalib')
-rw-r--r--vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp10
-rw-r--r--vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp56
-rw-r--r--vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp44
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/peer_credentials.h2
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp26
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/peer_policies.h11
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp18
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp4
-rw-r--r--vespalib/src/vespa/vespalib/test/peer_policy_utils.h1
11 files changed, 137 insertions, 39 deletions
diff --git a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
index 4586beef910..7dacbd89503 100644
--- a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
+++ b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
@@ -583,10 +583,11 @@ TEST_F("Exception during verification callback processing breaks handshake", Cer
EXPECT_FALSE(f.handshake());
}
-TEST_F("Certificate verification callback observes CN and DNS SANs", CertFixture) {
+TEST_F("Certificate verification callback observes CN, DNS SANs and URI SANs", CertFixture) {
auto ck = f.create_ca_issued_peer_cert(
{{"rockets.wile.example.com"}},
- {{"DNS:crash.wile.example.com"}, {"DNS:burn.wile.example.com"}});
+ {{"DNS:crash.wile.example.com"}, {"DNS:burn.wile.example.com"},
+ {"URI:foo://bar.baz/zoid"}});
fprintf(stderr, "certs:\n%s%s\n", f.root_ca.cert->to_pem().c_str(), ck.cert->to_pem().c_str());
@@ -600,6 +601,8 @@ TEST_F("Certificate verification callback observes CN and DNS SANs", CertFixture
ASSERT_EQUAL(2u, creds.dns_sans.size());
EXPECT_EQUAL("crash.wile.example.com", creds.dns_sans[0]);
EXPECT_EQUAL("burn.wile.example.com", creds.dns_sans[1]);
+ ASSERT_EQUAL(1u, server_cb->creds.uri_sans.size());
+ EXPECT_EQUAL("foo://bar.baz/zoid", server_cb->creds.uri_sans[0]);
}
TEST_F("Last occurring CN is given to verification callback if multiple CNs are present", CertFixture) {
@@ -616,7 +619,7 @@ TEST_F("Last occurring CN is given to verification callback if multiple CNs are
}
// TODO we are likely to want IPADDR SANs at some point
-TEST_F("Only DNS SANs are enumerated", CertFixture) {
+TEST_F("Only DNS and URI SANs are enumerated", CertFixture) {
auto ck = f.create_ca_issued_peer_cert({}, {"IP:127.0.0.1"});
f.reset_client_with_cert_opts(ck, std::make_shared<PrintingCertificateCallback>());
@@ -624,6 +627,7 @@ TEST_F("Only DNS SANs are enumerated", CertFixture) {
f.reset_server_with_cert_opts(ck, server_cb);
ASSERT_TRUE(f.handshake());
EXPECT_EQUAL(0u, server_cb->creds.dns_sans.size());
+ EXPECT_EQUAL(0u, server_cb->creds.uri_sans.size());
}
// We don't test too many combinations of peer policies here, only that
diff --git a/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp b/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp
index a9e823bf3ab..9a7e1b1b585 100644
--- a/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp
+++ b/vespalib/src/tests/net/tls/policy_checking_certificate_verifier/policy_checking_certificate_verifier_test.cpp
@@ -8,7 +8,7 @@ using namespace vespalib;
using namespace vespalib::net::tls;
bool glob_matches(vespalib::stringref pattern, vespalib::stringref string_to_check) {
- auto glob = HostGlobPattern::create_from_glob(pattern);
+ auto glob = CredentialMatchPattern::create_from_glob(pattern);
return glob->matches(string_to_check);
}
@@ -61,12 +61,25 @@ TEST("special extended regex characters are ignored") {
}
// TODO CN + SANs
+PeerCredentials creds_with_sans(std::vector<vespalib::string> dns_sans, std::vector<vespalib::string> uri_sans) {
+ PeerCredentials creds;
+ creds.dns_sans = std::move(dns_sans);
+ creds.uri_sans = std::move(uri_sans);
+ return creds;
+}
+
PeerCredentials creds_with_dns_sans(std::vector<vespalib::string> dns_sans) {
PeerCredentials creds;
creds.dns_sans = std::move(dns_sans);
return creds;
}
+PeerCredentials creds_with_uri_sans(std::vector<vespalib::string> uri_sans) {
+ PeerCredentials creds;
+ creds.uri_sans = std::move(uri_sans);
+ return creds;
+}
+
PeerCredentials creds_with_cn(vespalib::stringref cn) {
PeerCredentials creds;
creds.common_name = cn;
@@ -93,7 +106,7 @@ TEST("Non-empty policies do not allow all authenticated peers") {
EXPECT_FALSE(allow_not_all.allows_all_authenticated());
}
-TEST("SAN requirement without glob pattern is matched as exact string") {
+TEST("DNS SAN requirement without glob pattern is matched as exact string") {
auto authorized = authorized_peers({policy_with({required_san_dns("hello.world")})});
EXPECT_TRUE(verify(authorized, creds_with_dns_sans({{"hello.world"}})));
EXPECT_FALSE(verify(authorized, creds_with_dns_sans({{"foo.bar"}})));
@@ -103,7 +116,7 @@ TEST("SAN requirement without glob pattern is matched as exact string") {
EXPECT_FALSE(verify(authorized, creds_with_dns_sans({{"hello.world.bar"}})));
}
-TEST("SAN requirement can include glob wildcards") {
+TEST("DNS SAN requirement can include glob wildcards") {
auto authorized = authorized_peers({policy_with({required_san_dns("*.w?rld")})});
EXPECT_TRUE(verify(authorized, creds_with_dns_sans({{"hello.world"}})));
EXPECT_TRUE(verify(authorized, creds_with_dns_sans({{"greetings.w0rld"}})));
@@ -111,23 +124,40 @@ TEST("SAN requirement can include glob wildcards") {
EXPECT_FALSE(verify(authorized, creds_with_dns_sans({{"world"}})));
}
+// FIXME make this RFC 2459-compliant with subdomain matching, case insensitity for host etc
+TEST("URI SAN requirement is matched as exact string in cheeky, pragmatic violation of RFC 2459") {
+ auto authorized = authorized_peers({policy_with({required_san_uri("foo://bar.baz/zoid")})});
+ EXPECT_TRUE(verify(authorized, creds_with_uri_sans({{"foo://bar.baz/zoid"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_uri_sans({{"foo://bar.baz/zoi"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_uri_sans({{"oo://bar.baz/zoid"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_uri_sans({{"bar://bar.baz/zoid"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_uri_sans({{"foo://bar.baz"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_uri_sans({{"foo://.baz/zoid"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_uri_sans({{"foo://BAR.baz/zoid"}})));
+}
+
TEST("multi-SAN policy requires all SANs to be present in certificate") {
auto authorized = authorized_peers({policy_with({required_san_dns("hello.world"),
- required_san_dns("foo.bar")})});
- EXPECT_TRUE(verify(authorized, creds_with_dns_sans({{"hello.world"}, {"foo.bar"}})));
- // Need both
- EXPECT_FALSE(verify(authorized, creds_with_dns_sans({{"hello.world"}})));
- EXPECT_FALSE(verify(authorized, creds_with_dns_sans({{"foo.bar"}})));
+ required_san_dns("foo.bar"),
+ required_san_uri("foo://bar/baz")})});
+ EXPECT_TRUE(verify(authorized, creds_with_sans({{"hello.world"}, {"foo.bar"}}, {{"foo://bar/baz"}})));
+ // Need all
+ EXPECT_FALSE(verify(authorized, creds_with_sans({{"hello.world"}, {"foo.bar"}}, {})));
+ EXPECT_FALSE(verify(authorized, creds_with_sans({{"hello.world"}}, {{"foo://bar/baz"}})));
+ EXPECT_FALSE(verify(authorized, creds_with_sans({{"hello.world"}}, {})));
+ EXPECT_FALSE(verify(authorized, creds_with_sans({{"foo.bar"}}, {})));
+ EXPECT_FALSE(verify(authorized, creds_with_sans({}, {{"foo://bar/baz"}})));
// OK with more SANs that strictly required
- EXPECT_TRUE(verify(authorized, creds_with_dns_sans({{"hello.world"}, {"foo.bar"}, {"baz.blorg"}})));
+ EXPECT_TRUE(verify(authorized, creds_with_sans({{"hello.world"}, {"foo.bar"}, {"baz.blorg"}},
+ {{"foo://bar/baz"}, {"hello://world/"}})));
}
-TEST("wildcard SAN in certificate is not treated as a wildcard match by policy") {
+TEST("wildcard DNS SAN in certificate is not treated as a wildcard match by policy") {
auto authorized = authorized_peers({policy_with({required_san_dns("hello.world")})});
EXPECT_FALSE(verify(authorized, creds_with_dns_sans({{"*.world"}})));
}
-TEST("wildcard SAN in certificate is still matched by wildcard policy SAN") {
+TEST("wildcard DNS SAN in certificate is still matched by wildcard policy SAN") {
auto authorized = authorized_peers({policy_with({required_san_dns("*.world")})});
EXPECT_TRUE(verify(authorized, creds_with_dns_sans({{"*.world"}})));
}
@@ -141,7 +171,8 @@ struct MultiPolicyMatchFixture {
MultiPolicyMatchFixture::MultiPolicyMatchFixture()
: authorized(authorized_peers({policy_with({required_san_dns("hello.world")}),
policy_with({required_san_dns("foo.bar")}),
- policy_with({required_san_dns("zoid.berg")})}))
+ policy_with({required_san_dns("zoid.berg")}),
+ policy_with({required_san_uri("zoid://be.rg/")})}))
{}
MultiPolicyMatchFixture::~MultiPolicyMatchFixture() = default;
@@ -150,6 +181,7 @@ TEST_F("peer verifies if it matches at least 1 policy of multiple", MultiPolicyM
EXPECT_TRUE(verify(f.authorized, creds_with_dns_sans({{"hello.world"}})));
EXPECT_TRUE(verify(f.authorized, creds_with_dns_sans({{"foo.bar"}})));
EXPECT_TRUE(verify(f.authorized, creds_with_dns_sans({{"zoid.berg"}})));
+ EXPECT_TRUE(verify(f.authorized, creds_with_uri_sans({{"zoid://be.rg/"}})));
}
TEST_F("peer verifies if it matches multiple policies", MultiPolicyMatchFixture) {
diff --git a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
index 00459a4e69c..a2bced3f7b4 100644
--- a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
+++ b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
@@ -111,10 +111,12 @@ TEST("can parse single peer policy with multiple requirements") {
const char* json = R"({
"required-credentials":[
{"field": "SAN_DNS", "must-match": "hello.world"},
+ {"field": "SAN_URI", "must-match": "foo://bar/baz"},
{"field": "CN", "must-match": "goodbye.moon"}
]
})";
EXPECT_EQUAL(authorized_peers({policy_with({required_san_dns("hello.world"),
+ required_san_uri("foo://bar/baz"),
required_cn("goodbye.moon")})}),
parse_policies(json).authorized_peers());
}
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
index e66baf87999..d4d2f394782 100644
--- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
@@ -374,6 +374,24 @@ struct GeneralNamesDeleter {
}
};
+// Returns empty string if unsupported type or bad content.
+vespalib::string get_ia5_string(const ASN1_IA5STRING* ia5_str) {
+ if ((ia5_str->type == V_ASN1_IA5STRING) && (ia5_str->data != nullptr) && (ia5_str->length > 0)) {
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ const char* data = reinterpret_cast<const char*>(::ASN1_STRING_get0_data(ia5_str));
+#else
+ const char* data = reinterpret_cast<const char*>(::ASN1_STRING_data(ia5_str));
+#endif
+ const auto length = static_cast<size_t>(::ASN1_STRING_length(ia5_str));
+ if (has_embedded_nulls(data, length)) {
+ LOG(warning, "Got X509 peer certificate with embedded nulls in SAN field");
+ return {};
+ }
+ return {data, length};
+ }
+ return {};
+}
+
using GeneralNamesPtr = std::unique_ptr<::GENERAL_NAMES, GeneralNamesDeleter>;
bool fill_certificate_subject_alternate_names(::X509* cert, PeerCredentials& creds) {
@@ -383,21 +401,19 @@ bool fill_certificate_subject_alternate_names(::X509* cert, PeerCredentials& cre
for (int i = 0; i < sk_GENERAL_NAME_num(san_names.get()); ++i) {
auto* value = sk_GENERAL_NAME_value(san_names.get(), i);
if (value->type == GEN_DNS) {
- auto* dns_name = value->d.dNSName; // const or non-const depending on version...
- if ((dns_name->type == V_ASN1_IA5STRING) && (dns_name->data != nullptr) && (dns_name->length > 0)) {
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
- const char* data = reinterpret_cast<const char*>(::ASN1_STRING_get0_data(dns_name));
-#else
- const char* data = reinterpret_cast<const char*>(::ASN1_STRING_data(dns_name));
-#endif
- const auto length = static_cast<size_t>(::ASN1_STRING_length(dns_name));
- if (has_embedded_nulls(data, length)) {
- LOG(warning, "Got X509 peer certificate with embedded nulls in SAN field");
- return false;
- }
- creds.dns_sans.emplace_back(data, length);
+ auto content = get_ia5_string(value->d.dNSName); // arg is const or non-const depending on version...
+ if (content.empty()) {
+ return false; // We assume there's something fishy with certs containing empty SANs
+ }
+ creds.dns_sans.emplace_back(std::move(content));
+ } else if (value->type == GEN_URI) {
+ auto content = get_ia5_string(value->d.uniformResourceIdentifier);
+ if (content.empty()) {
+ return false;
}
- } // TODO support GEN_IPADD SAN?
+ creds.uri_sans.emplace_back(std::move(content));
+ }
+ // TODO support GEN_IPADD SAN?
}
}
return true;
diff --git a/vespalib/src/vespa/vespalib/net/tls/peer_credentials.h b/vespalib/src/vespa/vespalib/net/tls/peer_credentials.h
index 93e5554a86e..fb43796052e 100644
--- a/vespalib/src/vespa/vespalib/net/tls/peer_credentials.h
+++ b/vespalib/src/vespa/vespalib/net/tls/peer_credentials.h
@@ -14,6 +14,8 @@ struct PeerCredentials {
vespalib::string common_name;
// 0-n DNS SAN entries. Note: "DNS:" prefix is not present in strings.
std::vector<vespalib::string> dns_sans;
+ // 0-n DNS URI entries. Note: "URI:" prefix is not present in strings.
+ std::vector<vespalib::string> uri_sans;
PeerCredentials();
~PeerCredentials();
diff --git a/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp b/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp
index 27a11b3f0f1..3a136b8147d 100644
--- a/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/peer_policies.cpp
@@ -50,7 +50,7 @@ std::string dot_separated_glob_to_regex(vespalib::stringref glob) {
return ret;
}
-class RegexHostMatchPattern : public HostGlobPattern {
+class RegexHostMatchPattern : public CredentialMatchPattern {
Regex _pattern_as_regex;
public:
explicit RegexHostMatchPattern(vespalib::stringref glob_pattern)
@@ -64,16 +64,36 @@ public:
}
};
+class ExactMatchPattern : public CredentialMatchPattern {
+ vespalib::string _must_match_exactly;
+public:
+ explicit ExactMatchPattern(vespalib::stringref str_to_match) noexcept // vespalib::string ctors marked noexcept
+ : _must_match_exactly(str_to_match)
+ {
+ }
+ ~ExactMatchPattern() override = default;
+
+ [[nodiscard]] bool matches(vespalib::stringref str) const override {
+ return (str == _must_match_exactly);
+ }
+};
+
} // anon ns
-std::shared_ptr<const HostGlobPattern> HostGlobPattern::create_from_glob(vespalib::stringref glob_pattern) {
+std::shared_ptr<const CredentialMatchPattern> CredentialMatchPattern::create_from_glob(vespalib::stringref glob_pattern) {
return std::make_shared<const RegexHostMatchPattern>(glob_pattern);
}
+std::shared_ptr<const CredentialMatchPattern> CredentialMatchPattern::create_exact_match(vespalib::stringref str) {
+ return std::make_shared<const ExactMatchPattern>(str);
+}
+
RequiredPeerCredential::RequiredPeerCredential(Field field, vespalib::string must_match_pattern)
: _field(field),
_original_pattern(std::move(must_match_pattern)),
- _match_pattern(HostGlobPattern::create_from_glob(_original_pattern))
+ // FIXME it's not RFC 2459-compliant to use exact-matching for URIs, but that's all we currently need.
+ _match_pattern(field == Field::SAN_URI ? CredentialMatchPattern::create_exact_match(_original_pattern)
+ : CredentialMatchPattern::create_from_glob(_original_pattern))
{
}
diff --git a/vespalib/src/vespa/vespalib/net/tls/peer_policies.h b/vespalib/src/vespa/vespalib/net/tls/peer_policies.h
index 9d34b62415f..aeb8dd17f64 100644
--- a/vespalib/src/vespa/vespalib/net/tls/peer_policies.h
+++ b/vespalib/src/vespa/vespalib/net/tls/peer_policies.h
@@ -8,22 +8,23 @@
namespace vespalib::net::tls {
-struct HostGlobPattern {
- virtual ~HostGlobPattern() = default;
+struct CredentialMatchPattern {
+ virtual ~CredentialMatchPattern() = default;
[[nodiscard]] virtual bool matches(vespalib::stringref str) const = 0;
- static std::shared_ptr<const HostGlobPattern> create_from_glob(vespalib::stringref pattern);
+ static std::shared_ptr<const CredentialMatchPattern> create_from_glob(vespalib::stringref pattern);
+ static std::shared_ptr<const CredentialMatchPattern> create_exact_match(vespalib::stringref pattern);
};
class RequiredPeerCredential {
public:
enum class Field {
- CN, SAN_DNS
+ CN, SAN_DNS, SAN_URI
};
private:
Field _field = Field::SAN_DNS;
vespalib::string _original_pattern;
- std::shared_ptr<const HostGlobPattern> _match_pattern;
+ std::shared_ptr<const CredentialMatchPattern> _match_pattern;
public:
RequiredPeerCredential() = default;
RequiredPeerCredential(Field field, vespalib::string must_match_pattern);
diff --git a/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp b/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp
index b8398746d38..0a06a36c074 100644
--- a/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/policy_checking_certificate_verifier.cpp
@@ -6,7 +6,7 @@ namespace vespalib::net::tls {
namespace {
-bool matches_single_san_requirement(const PeerCredentials& peer_creds, const RequiredPeerCredential& requirement) {
+bool matches_single_san_dns_requirement(const PeerCredentials& peer_creds, const RequiredPeerCredential& requirement) {
for (const auto& provided_cred : peer_creds.dns_sans) {
if (requirement.matches(provided_cred)) {
return true;
@@ -15,6 +15,15 @@ bool matches_single_san_requirement(const PeerCredentials& peer_creds, const Req
return false;
}
+bool matches_single_san_uri_requirement(const PeerCredentials& peer_creds, const RequiredPeerCredential& requirement) {
+ for (const auto& provided_cred : peer_creds.uri_sans) {
+ if (requirement.matches(provided_cred)) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool matches_cn_requirement(const PeerCredentials& peer_creds, const RequiredPeerCredential& requirement) {
return requirement.matches(peer_creds.common_name);
}
@@ -23,7 +32,12 @@ bool matches_all_policy_requirements(const PeerCredentials& peer_creds, const Pe
for (const auto& required_cred : policy.required_peer_credentials()) {
switch (required_cred.field()) {
case RequiredPeerCredential::Field::SAN_DNS:
- if (!matches_single_san_requirement(peer_creds, required_cred)) {
+ if (!matches_single_san_dns_requirement(peer_creds, required_cred)) {
+ return false;
+ }
+ continue;
+ case RequiredPeerCredential::Field::SAN_URI:
+ if (!matches_single_san_uri_requirement(peer_creds, required_cred)) {
return false;
}
continue;
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
index 80caa15e8b2..2c10b3838af 100644
--- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
@@ -57,6 +57,8 @@ RequiredPeerCredential parse_peer_credential(const Inspector& req_entry) {
field = RequiredPeerCredential::Field::CN;
} else if (field_string == "SAN_DNS") {
field = RequiredPeerCredential::Field::SAN_DNS;
+ } else if (field_string == "SAN_URI") {
+ field = RequiredPeerCredential::Field::SAN_URI;
} else {
throw IllegalArgumentException(make_string(
"Unsupported credential field type: '%s'. Supported are: CN, SAN_DNS",
diff --git a/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp b/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp
index 981ebd0c18d..0520db6a93c 100644
--- a/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp
+++ b/vespalib/src/vespa/vespalib/test/peer_policy_utils.cpp
@@ -12,6 +12,10 @@ RequiredPeerCredential required_san_dns(vespalib::stringref pattern) {
return {RequiredPeerCredential::Field::SAN_DNS, pattern};
}
+RequiredPeerCredential required_san_uri(vespalib::stringref pattern) {
+ return {RequiredPeerCredential::Field::SAN_URI, pattern};
+}
+
PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds) {
return PeerPolicy(std::move(creds));
}
diff --git a/vespalib/src/vespa/vespalib/test/peer_policy_utils.h b/vespalib/src/vespa/vespalib/test/peer_policy_utils.h
index 1a720330e7d..4ff0f828b0d 100644
--- a/vespalib/src/vespa/vespalib/test/peer_policy_utils.h
+++ b/vespalib/src/vespa/vespalib/test/peer_policy_utils.h
@@ -7,6 +7,7 @@ namespace vespalib::net::tls {
RequiredPeerCredential required_cn(vespalib::stringref pattern);
RequiredPeerCredential required_san_dns(vespalib::stringref pattern);
+RequiredPeerCredential required_san_uri(vespalib::stringref pattern);
PeerPolicy policy_with(std::vector<RequiredPeerCredential> creds);
AuthorizedPeers authorized_peers(std::vector<PeerPolicy> peer_policies);