Merge pull request #538 from alexmiller-apple/tlsplugin_san
TLS certificate handling enhancements
This commit is contained in:
commit
7f6bced835
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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``.
|
||||
|
|
Loading…
Reference in New Issue