Merge pull request #538 from alexmiller-apple/tlsplugin_san

TLS certificate handling enhancements
This commit is contained in:
Steve Atherton 2018-07-01 01:50:58 -07:00 committed by GitHub
commit 7f6bced835
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 388 additions and 79 deletions

View File

@ -24,6 +24,7 @@
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <exception>
@ -100,50 +101,135 @@ FDBLibTLSSession::~FDBLibTLSSession() {
tls_free(tls_sctx);
}
bool match_criteria(X509_NAME *name, int nid, const char *value, size_t len) {
unsigned char *name_entry_utf8 = NULL, *criteria_utf8 = NULL;
int name_entry_utf8_len, criteria_utf8_len;
ASN1_STRING *criteria = NULL;
X509_NAME_ENTRY *name_entry;
BIO *bio;
bool match_criteria_entry(const std::string& criteria, ASN1_STRING* entry, MatchType mt) {
bool rc = false;
int idx;
ASN1_STRING* asn_criteria = NULL;
unsigned char* criteria_utf8 = NULL;
int criteria_utf8_len = 0;
unsigned char* entry_utf8 = NULL;
int entry_utf8_len = 0;
if ((criteria = ASN1_IA5STRING_new()) == NULL)
if ((asn_criteria = ASN1_IA5STRING_new()) == NULL)
goto err;
if (ASN1_STRING_set(criteria, value, len) != 1)
if (ASN1_STRING_set(asn_criteria, criteria.c_str(), criteria.size()) != 1)
goto err;
if ((criteria_utf8_len = ASN1_STRING_to_UTF8(&criteria_utf8, asn_criteria)) < 1)
goto err;
if ((entry_utf8_len = ASN1_STRING_to_UTF8(&entry_utf8, entry)) < 1)
goto err;
if (mt == MatchType::EXACT) {
if (criteria_utf8_len == entry_utf8_len &&
memcmp(criteria_utf8, entry_utf8, criteria_utf8_len) == 0)
rc = true;
} else if (mt == MatchType::PREFIX) {
if (criteria_utf8_len <= entry_utf8_len &&
memcmp(criteria_utf8, entry_utf8, criteria_utf8_len) == 0)
rc = true;
} else if (mt == MatchType::SUFFIX) {
if (criteria_utf8_len <= entry_utf8_len &&
memcmp(criteria_utf8, entry_utf8 + (entry_utf8_len - criteria_utf8_len), criteria_utf8_len) == 0)
rc = true;
}
err:
ASN1_STRING_free(asn_criteria);
free(criteria_utf8);
free(entry_utf8);
return rc;
}
bool match_name_criteria(X509_NAME *name, NID nid, const std::string& criteria, MatchType mt) {
X509_NAME_ENTRY *name_entry;
int idx;
// If name does not exist, or has multiple of this RDN, refuse to proceed.
if ((idx = X509_NAME_get_index_by_NID(name, nid, -1)) < 0)
goto err;
return false;
if (X509_NAME_get_index_by_NID(name, nid, idx) != -1)
goto err;
return false;
if ((name_entry = X509_NAME_get_entry(name, idx)) == NULL)
goto err;
return false;
// Convert both to UTF8 and compare.
if ((criteria_utf8_len = ASN1_STRING_to_UTF8(&criteria_utf8, criteria)) < 1)
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;
err:
ASN1_STRING_free(criteria);
free(criteria_utf8);
free(name_entry_utf8);
return match_criteria_entry(criteria, name_entry->value, mt);
}
bool match_extension_criteria(X509 *cert, NID nid, const std::string& value, MatchType mt) {
if (nid != NID_subject_alt_name && nid != NID_issuer_alt_name) {
// I have no idea how other extensions work.
return false;
}
auto pos = value.find(':');
if (pos == value.npos) {
return false;
}
std::string value_gen = value.substr(0, pos);
std::string value_val = value.substr(pos+1, value.npos);
STACK_OF(GENERAL_NAME)* sans = reinterpret_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(cert, nid, NULL, NULL));
if (sans == NULL) {
return false;
}
int num_sans = sk_GENERAL_NAME_num( sans );
bool match_found = false;
bool rc = false;
for( int i = 0; i < num_sans && !rc; ++i ) {
GENERAL_NAME* altname = sk_GENERAL_NAME_value( sans, i );
std::string matchable;
switch (altname->type) {
case GEN_OTHERNAME:
break;
case GEN_EMAIL:
if (value_gen == "EMAIL" &&
match_criteria_entry( value_val, altname->d.rfc822Name, mt)) {
rc = true;
break;
}
case GEN_DNS:
if (value_gen == "DNS" &&
match_criteria_entry( value_val, altname->d.dNSName, mt )) {
rc = true;
break;
}
case GEN_X400:
case GEN_DIRNAME:
case GEN_EDIPARTY:
break;
case GEN_URI:
if (value_gen == "URI" &&
match_criteria_entry( value_val, altname->d.uniformResourceIdentifier, mt )) {
rc = true;
break;
}
case GEN_IPADD:
if (value_gen == "IP" &&
match_criteria_entry( value_val, altname->d.iPAddress, mt )) {
rc = true;
break;
}
case GEN_RID:
break;
}
}
sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free);
return rc;
}
bool match_criteria(X509* cert, X509_NAME* subject, NID nid, const std::string& criteria, MatchType mt, X509Location loc) {
switch(loc) {
case X509Location::NAME: {
return match_name_criteria(subject, nid, criteria, mt);
}
case X509Location::EXTENSION: {
return match_extension_criteria(cert, nid, criteria, mt);
}
}
}
std::tuple<bool,std::string> FDBLibTLSSession::check_verify(Reference<FDBLibTLSVerify> verify, struct stack_st_X509 *certs) {
X509_STORE_CTX *store_ctx = NULL;
X509_NAME *subject, *issuer;
BIO *bio = NULL;
bool rc = false;
X509* cert = NULL;
// if returning false, give a reason string
std::string reason = "";
@ -172,36 +258,38 @@ std::tuple<bool,std::string> FDBLibTLSSession::check_verify(Reference<FDBLibTLSV
}
// Check subject criteria.
if ((subject = X509_get_subject_name(sk_X509_value(store_ctx->chain, 0))) == NULL) {
cert = sk_X509_value(store_ctx->chain, 0);
if ((subject = X509_get_subject_name(cert)) == NULL) {
reason = "FDBLibTLSCertSubjectError";
goto err;
}
for (auto &pair: verify->subject_criteria) {
if (!match_criteria(subject, pair.first, pair.second.c_str(), pair.second.size())) {
if (!match_criteria(cert, subject, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) {
reason = "FDBLibTLSCertSubjectMatchFailure";
goto err;
}
}
// Check issuer criteria.
if ((issuer = X509_get_issuer_name(sk_X509_value(store_ctx->chain, 0))) == NULL) {
if ((issuer = X509_get_issuer_name(cert)) == NULL) {
reason = "FDBLibTLSCertIssuerError";
goto err;
}
for (auto &pair: verify->issuer_criteria) {
if (!match_criteria(issuer, pair.first, pair.second.c_str(), pair.second.size())) {
if (!match_criteria(cert, issuer, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) {
reason = "FDBLibTLSCertIssuerMatchFailure";
goto err;
}
}
// Check root criteria - this is the subject of the final certificate in the stack.
if ((subject = X509_get_subject_name(sk_X509_value(store_ctx->chain, sk_X509_num(store_ctx->chain) - 1))) == NULL) {
cert = sk_X509_value(store_ctx->chain, sk_X509_num(store_ctx->chain) - 1);
if ((subject = X509_get_subject_name(cert)) == NULL) {
reason = "FDBLibTLSRootSubjectError";
goto err;
}
for (auto &pair: verify->root_criteria) {
if (!match_criteria(subject, pair.first, pair.second.c_str(), pair.second.size())) {
if (!match_criteria(cert, subject, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) {
reason = "FDBLibTLSRootSubjectMatchFailure";
goto err;
}

View File

@ -24,6 +24,7 @@
#include <algorithm>
#include <exception>
#include <cstring>
static int hexValue(char c) {
static char const digits[] = "0123456789ABCDEF";
@ -133,10 +134,10 @@ static std::pair<std::string, std::string> splitPair(std::string const& input, c
return std::make_pair(input.substr(0, p), input.substr(p+1, input.size()));
}
static int abbrevToNID(std::string const& sn) {
int nid = NID_undef;
static NID abbrevToNID(std::string const& sn) {
NID nid = NID_undef;
if (sn == "C" || sn == "CN" || sn == "L" || sn == "ST" || sn == "O" || sn == "OU" || sn == "UID" || sn == "DC")
if (sn == "C" || sn == "CN" || sn == "L" || sn == "ST" || sn == "O" || sn == "OU" || sn == "UID" || sn == "DC" || sn == "subjectAltName")
nid = OBJ_sn2nid(sn.c_str());
if (nid == NID_undef)
throw std::runtime_error("abbrevToNID");
@ -144,6 +145,19 @@ static int abbrevToNID(std::string const& sn) {
return nid;
}
static X509Location locationForNID(NID nid) {
const char* name = OBJ_nid2ln(nid);
if (name == NULL) {
throw std::runtime_error("locationForNID");
}
if (strncmp(name, "X509v3", 6) == 0) {
return X509Location::EXTENSION;
} else {
// It probably isn't true that all other NIDs live in the NAME, but it is for now...
return X509Location::NAME;
}
}
FDBLibTLSVerify::FDBLibTLSVerify(std::string verify_config):
verify_cert(true), verify_time(true) {
parse_verify(verify_config);
@ -161,13 +175,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 +206,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 +229,9 @@ 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));
NID termNID = abbrevToNID(term);
const X509Location loc = locationForNID(termNID);
criteria->insert(std::make_pair(termNID, Criteria(unesc, mt, loc)));
if (remain != input.size() && input[remain] != ',')
throw std::runtime_error("parse_verify");

View File

@ -29,6 +29,41 @@
#include <map>
#include <string>
#include <utility>
typedef int NID;
enum class MatchType {
EXACT,
PREFIX,
SUFFIX,
};
enum class X509Location {
// This NID is located within a X509_NAME
NAME,
// This NID is an X509 extension, and should be parsed accordingly
EXTENSION,
};
struct Criteria {
Criteria( const std::string& s )
: criteria(s), match_type(MatchType::EXACT), location(X509Location::NAME) {}
Criteria( const std::string& s, MatchType mt )
: criteria(s), match_type(mt), location(X509Location::NAME) {}
Criteria( const std::string& s, X509Location loc)
: criteria(s), match_type(MatchType::EXACT), location(loc) {}
Criteria( const std::string& s, MatchType mt, X509Location loc)
: criteria(s), match_type(mt), location(loc) {}
std::string criteria;
MatchType match_type;
X509Location location;
bool operator==(const Criteria& c) const {
return criteria == c.criteria && match_type == c.match_type && location == c.location;
}
};
struct FDBLibTLSVerify: ReferenceCounted<FDBLibTLSVerify> {
FDBLibTLSVerify(std::string verify);
@ -42,9 +77,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< NID, Criteria > subject_criteria;
std::map< NID, Criteria > issuer_criteria;
std::map< NID, Criteria > root_criteria;
};
#endif /* FDB_LIBTLS_VERIFY_H */

View File

@ -5,7 +5,7 @@ CFLAGS ?= -O2 -g
CXXFLAGS ?= -std=c++0x
CFLAGS += -I/usr/local/include
CFLAGS += -I/usr/local/include -I../fdbrpc
LDFLAGS += -L/usr/local/lib
LIBS += -ltls -lssl -lcrypto

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,94 @@ 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"},
},
// Subject Alternative Name
{
.ca_path = "test-ca-1.pem",
.client_success = true,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"S.subjectAltName=DNS:test.foundationdb.org"},
.servername = NULL,
.server_success = true,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"Check.Valid=0"},
},
{
.ca_path = "test-ca-1.pem",
.client_success = true,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"S.subjectAltName>=DNS:test."},
.servername = NULL,
.server_success = true,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"Check.Valid=0"},
},
{
.ca_path = "test-ca-1.pem",
.client_success = true,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"S.subjectAltName<=DNS:.org"},
.servername = NULL,
.server_success = true,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"Check.Valid=0"},
},
{
.ca_path = "test-ca-1.pem",
.client_success = false,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"S.subjectAltName<=DNS:.com"},
.servername = NULL,
.server_success = true,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"Check.Valid=0"},
},
{
.ca_path = "test-ca-1.pem",
.client_success = false,
.client_path = "test-client-2.pem",
.client_password = NULL,
.client_verify = {"S.subjectAltName<=EMAIL:.com"},
.servername = NULL,
.server_success = true,
.server_path = "test-server-1.pem",
.server_password = NULL,
.server_verify = {"Check.Valid=0"},
},
};
int main(int argc, char **argv)

View File

@ -23,6 +23,7 @@
#include <vector>
#include <string.h>
#include <boost/lexical_cast.hpp>
#include <openssl/objects.h>
@ -35,7 +36,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 +48,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,15 +72,15 @@ 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) + ", " + boost::lexical_cast<std::string>((int)pair.second.match_type) + ", " + boost::lexical_cast<std::string>((int)pair.second.location) + ")}";
}
return "{" + s + "}";
}
static void logf(const char* event, void* uid, int is_error, ...) {
static void logf(const char* event, void* uid, bool is_error, ...) {
}
int FDBLibTLSVerifyTest::run() {
@ -180,6 +181,10 @@ int main(int argc, char **argv)
{
int failed = 0;
#define EXACT(x) Criteria(x, MatchType::EXACT, X509Location::NAME)
#define PREFIX(x) Criteria(x, MatchType::PREFIX, X509Location::NAME)
#define SUFFIX(x) Criteria(x, MatchType::SUFFIX, X509Location::NAME)
std::vector<FDBLibTLSVerifyTest> tests = {
FDBLibTLSVerifyTest("", true, true, {}, {}, {}),
FDBLibTLSVerifyTest("Check.Valid=1", true, true, {}, {}, {}),
@ -189,25 +194,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"}}, {}, {}),
{{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, {"XYZCorp", MatchType::EXACT, X509Location::EXTENSION}}}, {}, {}),
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"),
@ -220,6 +228,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();

View File

@ -210,18 +210,20 @@ Setting Result
Adding verification requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Requirements can be placed on the fields of the Issuer and Subject DNs in the peer's own certificate. These requirements take the form of a comma-separated list of conditions. Each condition takes the form of ``field=value``. Only certain fields from a DN can be matched against.
Requirements can be placed on the fields of the Issuer and Subject DNs in the peer's own certificate. These requirements take the form of a comma-separated list of conditions. Each condition takes the form of ``field[<>]?=value``. Only certain fields from a DN can be matched against.
====== ===================
Field Well known name
====== ===================
``CN`` Common Name
``C`` County
``L`` Locality
``ST`` State
``O`` Organization
``OU`` Organizational Unit
====== ===================
======= ===================
Field Well known name
======= ===================
``CN`` Common Name
``C`` County
``L`` Locality
``ST`` State
``O`` Organization
``OU`` Organizational Unit
``UID`` Unique Identifier
``DC`` Domain Component
======= ===================
The field of each condition may optionally have a DN prefix, which is otherwise considered to be for the Subject DN.
@ -230,6 +232,7 @@ Prefix DN
============================= ========
``S.``, ``Subject.``, or none Subject
``I.``, or ``Issuer.`` Issuer
``R.``, or ``Root.`` Root
============================= ========
Additionally, the verification can be restricted to certificates signed by a given root CA with the field ``Root.CN``. This allows you to have different requirements for different root chains.
@ -238,16 +241,78 @@ The value of a condition must be specified in a form derived from a subset of `R
By default, the fields of a peer certificate's DNs are not examined.
In addition to DNs, restrictions can be placed against X509 extensions. They are specified in the same fashion as DN requirements. The supported extensions are:
================== ========================
Field Well known name
================== ========================
``subjectAltName`` Subject Alternative Name
================== ========================
Within a subject alternative name requirement, the value specified is required to have the form ``prefix:value``, where the prefix specifies the type of value being matched against. The following prefixes are supported.
====== ===========================
Prefix Well known name
====== ===========================
DNS Domain Name
URI Uniform Resource Identifier
IP IP Address
EMAIL Email Address
====== ============================
The following operators are supported:
========= ============
Operator Match Type
========= ============
``=`` Exact Match
``>=`` Prefix Match
``<=`` Suffix Match
========= ============
Verification Examples
^^^^^^^^^^^^^^^^^^^^^
A verification string can be of the form::
Let's consider a certificate, whose abridged contents is::
Check.Unexpired=0,I.C=US,C=US,S.O=XYZCorp\, LLC
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 12938646789571341173 (0xb38f4eb406a5eb75)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=California, L=Cupertino, O=Apple Inc., OU=FDB Team
Subject: C=US, ST=California, L=Cupertino, O=Apple Inc., OU=FDB Team
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:test.foundationdb.org
DNS:prod.foundationdb.com
This verification string would:
A verification string of::
Check.Unexpired=0,I.C=US,C=US,S.O=Apple Inc.
Would pass, and:
* Skip the check on all peer certificates that the certificate is not yet expired
* Require that the Issuer have a Country field of ``US``
* Require that the Subject have a Country field of ``US``
* Require that the Subject have a Organization field of ``XYZCorp, LLC``
* Require that the Issuer has a Country field of ``US``
* Require that the Subject has a Country field of ``US``
* Require that the Subject has a Organization field of ``Apple Inc.``
A verification string of::
S.OU>=FDB,S.OU<=Team,S.subjectAltName=DNS:test.foundationdb.org
Would pass, and:
* Require that the Subject has an Organization field that starts with ``FDB``
* Require that the Subject has an Organization field that ends with ``Team``
* Require that the Subject has a Subject Alternative Name extension, which has one or more members of type DNS with a value of ``test.foundationdb.org``.
A verification string of::
S.subjectAltName>=DNS:prod.,S.subjectAltName<=DNS:.org
Would pass, and:
* Require that the Subject has a Subject Alternative Name extension, which has one or more members of type DNS that begins with the value ``prod.``.
* Require that the Subject has a Subject Alternative Name extension, which has one or more members of type DNS that ends with the value ``.com``.