Implement prefix and suffix matching for TLS certificate verification.

This extends our language for specifying verification rules from, e.g.

    S.O=XYZCorp

to also include two more operators

    S.O>=XYZ  # Prefix
    S.O<=Corp # Suffix

both of which would match against an Organization of XYZCorp (among others).
This commit is contained in:
Alex Miller 2018-06-27 18:06:24 -07:00
parent e39d2c702d
commit 70d078021f
5 changed files with 104 additions and 34 deletions

View File

@ -100,7 +100,7 @@ FDBLibTLSSession::~FDBLibTLSSession() {
tls_free(tls_sctx);
}
bool match_criteria(X509_NAME *name, int nid, const char *value, size_t len) {
bool match_criteria(X509_NAME *name, int nid, const char *value, size_t len, MatchType mt) {
unsigned char *name_entry_utf8 = NULL, *criteria_utf8 = NULL;
int name_entry_utf8_len, criteria_utf8_len;
ASN1_STRING *criteria = NULL;
@ -127,9 +127,19 @@ bool match_criteria(X509_NAME *name, int nid, const char *value, size_t len) {
goto err;
if ((name_entry_utf8_len = ASN1_STRING_to_UTF8(&name_entry_utf8, name_entry->value)) < 1)
goto err;
if (criteria_utf8_len == name_entry_utf8_len &&
memcmp(criteria_utf8, name_entry_utf8, criteria_utf8_len) == 0)
rc = true;
if (mt == MatchType::EXACT) {
if (criteria_utf8_len == name_entry_utf8_len &&
memcmp(criteria_utf8, name_entry_utf8, criteria_utf8_len) == 0)
rc = true;
} else if (mt == MatchType::PREFIX) {
if (criteria_utf8_len <= name_entry_utf8_len &&
memcmp(criteria_utf8, name_entry_utf8, criteria_utf8_len) == 0)
rc = true;
} else if (mt == MatchType::SUFFIX) {
if (criteria_utf8_len <= name_entry_utf8_len &&
memcmp(criteria_utf8, name_entry_utf8 + (name_entry_utf8_len - criteria_utf8_len), criteria_utf8_len) == 0)
rc = true;
}
err:
ASN1_STRING_free(criteria);
@ -177,7 +187,7 @@ std::tuple<bool,std::string> FDBLibTLSSession::check_verify(Reference<FDBLibTLSV
goto err;
}
for (auto &pair: verify->subject_criteria) {
if (!match_criteria(subject, pair.first, pair.second.c_str(), pair.second.size())) {
if (!match_criteria(subject, pair.first, pair.second.criteria.c_str(), pair.second.criteria.size(), pair.second.match_type)) {
reason = "FDBLibTLSCertSubjectMatchFailure";
goto err;
}
@ -189,7 +199,7 @@ std::tuple<bool,std::string> FDBLibTLSSession::check_verify(Reference<FDBLibTLSV
goto err;
}
for (auto &pair: verify->issuer_criteria) {
if (!match_criteria(issuer, pair.first, pair.second.c_str(), pair.second.size())) {
if (!match_criteria(issuer, pair.first, pair.second.criteria.c_str(), pair.second.criteria.size(), pair.second.match_type)) {
reason = "FDBLibTLSCertIssuerMatchFailure";
goto err;
}
@ -201,7 +211,7 @@ std::tuple<bool,std::string> FDBLibTLSSession::check_verify(Reference<FDBLibTLSV
goto err;
}
for (auto &pair: verify->root_criteria) {
if (!match_criteria(subject, pair.first, pair.second.c_str(), pair.second.size())) {
if (!match_criteria(subject, pair.first, pair.second.criteria.c_str(), pair.second.criteria.size(), pair.second.match_type)) {
reason = "FDBLibTLSRootSubjectMatchFailure";
goto err;
}

View File

@ -161,13 +161,18 @@ void FDBLibTLSVerify::parse_verify(std::string input) {
if (eq == input.npos)
throw std::runtime_error("parse_verify");
std::string term = input.substr(s, eq - s);
MatchType mt = MatchType::EXACT;
if (input[eq-1] == '>') mt = MatchType::PREFIX;
if (input[eq-1] == '<') mt = MatchType::SUFFIX;
std::string term = input.substr(s, eq - s - (mt == MatchType::EXACT ? 0 : 1));
if (term.find("Check.") == 0) {
if (eq + 2 > input.size())
throw std::runtime_error("parse_verify");
if (eq + 2 != input.size() && input[eq + 2] != ',')
throw std::runtime_error("parse_verify");
if (mt != MatchType::EXACT)
throw std::runtime_error("parse_verify: cannot prefix match Check");
bool* flag;
@ -187,7 +192,7 @@ void FDBLibTLSVerify::parse_verify(std::string input) {
s = eq + 3;
} else {
std::map<int, std::string>* criteria = &subject_criteria;
std::map< int, Criteria >* criteria = &subject_criteria;
if (term.find('.') != term.npos) {
auto scoped = splitPair(term, '.');
@ -210,7 +215,7 @@ void FDBLibTLSVerify::parse_verify(std::string input) {
if (remain == eq + 1)
throw std::runtime_error("parse_verify");
criteria->insert(std::make_pair(abbrevToNID(term), unesc));
criteria->insert(std::make_pair(abbrevToNID(term), Criteria(unesc, mt)));
if (remain != input.size() && input[remain] != ',')
throw std::runtime_error("parse_verify");

View File

@ -29,6 +29,25 @@
#include <map>
#include <string>
#include <utility>
enum class MatchType {
EXACT,
PREFIX,
SUFFIX,
};
struct Criteria {
Criteria( const std::string& s ) : criteria(s), match_type(MatchType::EXACT) {}
Criteria( const std::string& s, MatchType mt ) : criteria(s), match_type(mt) {}
std::string criteria;
MatchType match_type;
bool operator==(const Criteria& c) const {
return criteria == c.criteria && match_type == c.match_type;
}
};
struct FDBLibTLSVerify: ReferenceCounted<FDBLibTLSVerify> {
FDBLibTLSVerify(std::string verify);
@ -42,9 +61,9 @@ struct FDBLibTLSVerify: ReferenceCounted<FDBLibTLSVerify> {
bool verify_cert;
bool verify_time;
std::map<int, std::string> subject_criteria;
std::map<int, std::string> issuer_criteria;
std::map<int, std::string> root_criteria;
std::map< int, Criteria > subject_criteria;
std::map< int, Criteria > issuer_criteria;
std::map< int, Criteria > root_criteria;
};
#endif /* FDB_LIBTLS_VERIFY_H */

View File

@ -489,7 +489,7 @@ int FDBLibTLSPluginTest::client_server_test(const struct client_server_test* cst
return 0;
}
static void logf(const char* event, void* uid, int is_error, ...) {
static void logf(const char* event, void* uid, bool is_error, ...) {
va_list args;
std::string log_type ("INFO");
@ -1049,6 +1049,32 @@ const struct client_server_test client_server_tests[] = {
.server_password = NULL,
.server_verify = {""},
},
// Prefix and Suffix Matching
{
.ca_path = "test-ca-1.pem",
.client_success = true,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"O>=Apple Inc.,OU>=FDB"},
.servername = NULL,
.server_success = true,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"O<=Limited,OU<=Team"},
},
{
.ca_path = "test-ca-1.pem",
.client_success = false,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"O<=Apple Inc.,OU<=FDB"},
.servername = NULL,
.server_success = false,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"O>=Limited,OU>=Team"},
},
};
int main(int argc, char **argv)

View File

@ -35,7 +35,7 @@
struct FDBLibTLSVerifyTest {
FDBLibTLSVerifyTest(std::string input):
input(input), valid(false), verify_cert(true), verify_time(true), subject_criteria({}), issuer_criteria({}), root_criteria({}) {};
FDBLibTLSVerifyTest(std::string input, bool verify_cert, bool verify_time, std::map<int, std::string> subject, std::map<int, std::string> issuer, std::map<int, std::string> root):
FDBLibTLSVerifyTest(std::string input, bool verify_cert, bool verify_time, std::map<int, Criteria> subject, std::map<int, Criteria> issuer, std::map<int, Criteria> root):
input(input), valid(true), verify_cert(verify_cert), verify_time(verify_time), subject_criteria(subject), issuer_criteria(issuer), root_criteria(root) {};
~FDBLibTLSVerifyTest() {};
@ -47,9 +47,9 @@ struct FDBLibTLSVerifyTest {
bool verify_cert;
bool verify_time;
std::map<int, std::string> subject_criteria;
std::map<int, std::string> issuer_criteria;
std::map<int, std::string> root_criteria;
std::map<int, Criteria> subject_criteria;
std::map<int, Criteria> issuer_criteria;
std::map<int, Criteria> root_criteria;
};
static std::string printable( std::string const& val ) {
@ -71,10 +71,10 @@ static std::string printable( std::string const& val ) {
return s;
}
static std::string criteriaToString(std::map<int, std::string> const& criteria) {
static std::string criteriaToString(std::map<int, Criteria> const& criteria) {
std::string s;
for (auto &pair: criteria) {
s += "{" + std::to_string(pair.first) + ":" + printable(pair.second) + "}";
s += "{" + std::to_string(pair.first) + ":" + printable(pair.second.criteria) + "}";
}
return "{" + s + "}";
}
@ -180,6 +180,10 @@ int main(int argc, char **argv)
{
int failed = 0;
#define EXACT(x) Criteria(x, MatchType::EXACT)
#define PREFIX(x) Criteria(x, MatchType::PREFIX)
#define SUFFIX(x) Criteria(x, MatchType::SUFFIX)
std::vector<FDBLibTLSVerifyTest> tests = {
FDBLibTLSVerifyTest("", true, true, {}, {}, {}),
FDBLibTLSVerifyTest("Check.Valid=1", true, true, {}, {}, {}),
@ -189,26 +193,28 @@ int main(int argc, char **argv)
FDBLibTLSVerifyTest("Check.Valid=1,Check.Unexpired=0", true, false, {}, {}, {}),
FDBLibTLSVerifyTest("Check.Unexpired=0,Check.Valid=0", false, false, {}, {}, {}),
FDBLibTLSVerifyTest("Check.Unexpired=0,I.C=US,C=US,S.O=XYZCorp\\, LLC", true, false,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp, LLC"}}, {{NID_countryName, "US"}}, {}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp, LLC")}}, {{NID_countryName, EXACT("US")}}, {}),
FDBLibTLSVerifyTest("Check.Unexpired=0,I.C=US,C=US,S.O=XYZCorp\\= LLC", true, false,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp= LLC"}}, {{NID_countryName, "US"}}, {}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp= LLC")}}, {{NID_countryName, EXACT("US")}}, {}),
FDBLibTLSVerifyTest("Check.Unexpired=0,R.C=US,C=US,S.O=XYZCorp\\= LLC", true, false,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp= LLC"}}, {}, {{NID_countryName, "US"}}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp= LLC")}}, {}, {{NID_countryName, EXACT("US")}}),
FDBLibTLSVerifyTest("Check.Unexpired=0,I.C=US,C=US,S.O=XYZCorp=LLC", true, false,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp=LLC"}}, {{NID_countryName, "US"}}, {}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp=LLC")}}, {{NID_countryName, EXACT("US")}}, {}),
FDBLibTLSVerifyTest("I.C=US,C=US,Check.Unexpired=0,S.O=XYZCorp=LLC", true, false,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp=LLC"}}, {{NID_countryName, "US"}}, {}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp=LLC")}}, {{NID_countryName, EXACT("US")}}, {}),
FDBLibTLSVerifyTest("I.C=US,C=US,S.O=XYZCorp\\, LLC", true, true,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp, LLC"}}, {{NID_countryName, "US"}}, {}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp, LLC")}}, {{NID_countryName, EXACT("US")}}, {}),
FDBLibTLSVerifyTest("I.C=US,C=US,S.O=XYZCorp\\, LLC,R.CN=abc", true, true,
{{NID_countryName, "US"}, {NID_organizationName, "XYZCorp, LLC"}},
{{NID_countryName, "US"}},
{{NID_commonName, "abc"}}),
FDBLibTLSVerifyTest("C=\\,S=abc", true, true, {{NID_countryName, ",S=abc"}}, {}, {}),
FDBLibTLSVerifyTest("CN=\\61\\62\\63", true, true, {{NID_commonName, "abc"}}, {}, {}),
FDBLibTLSVerifyTest("CN=a\\62c", true, true, {{NID_commonName, "abc"}}, {}, {}),
FDBLibTLSVerifyTest("CN=a\\01c", true, true, {{NID_commonName, "a\001c"}}, {}, {}),
FDBLibTLSVerifyTest("S.subjectAltName=XYZCorp", true, true, {{NID_subject_alt_name, "XYZCorp"}}, {}, {}),
{{NID_countryName, EXACT("US")}, {NID_organizationName, EXACT("XYZCorp, LLC")}},
{{NID_countryName, EXACT("US")}},
{{NID_commonName, EXACT("abc")}}),
FDBLibTLSVerifyTest("C=\\,S=abc", true, true, {{NID_countryName, EXACT(",S=abc")}}, {}, {}),
FDBLibTLSVerifyTest("CN=\\61\\62\\63", true, true, {{NID_commonName, EXACT("abc")}}, {}, {}),
FDBLibTLSVerifyTest("CN=a\\62c", true, true, {{NID_commonName, EXACT("abc")}}, {}, {}),
FDBLibTLSVerifyTest("CN=a\\01c", true, true, {{NID_commonName, EXACT("a\001c")}}, {}, {}),
FDBLibTLSVerifyTest("S.subjectAltName=XYZCorp", true, true, {{NID_subject_alt_name, EXACT("XYZCorp")}}, {}, {}),
FDBLibTLSVerifyTest("S.O>=XYZ", true, true, {{NID_organizationName, PREFIX("XYZ")}}, {}, {}),
FDBLibTLSVerifyTest("S.O<=LLC", true, true, {{NID_organizationName, SUFFIX("LLC")}}, {}, {}),
// Invalid cases.
FDBLibTLSVerifyTest("Check.Invalid=0"),
@ -221,6 +227,10 @@ int main(int argc, char **argv)
FDBLibTLSVerifyTest("CN=abc,Check.Expired=1"),
};
#undef EXACT
#undef PREFIX
#undef SUFFIX
for (auto &test: tests)
failed |= test.run();