diff --git a/src/libstrongswan/plugins/constraints/constraints_validator.c b/src/libstrongswan/plugins/constraints/constraints_validator.c index b1f60fb15..27bdb8943 100644 --- a/src/libstrongswan/plugins/constraints/constraints_validator.c +++ b/src/libstrongswan/plugins/constraints/constraints_validator.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023-2024 Tobias Brunner * Copyright (C) 2010 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -18,6 +19,8 @@ #include #include +#include +#include #include #include @@ -101,10 +104,14 @@ static bool email_matches(identification_t *constraint, identification_t *id) return chunk_equals(c, i); } diff = chunk_create(i.ptr, i.len - c.len); - if (!diff.len || !chunk_equals(c, chunk_skip(i, diff.len))) + if (!chunk_equals(c, chunk_skip(i, diff.len))) { return FALSE; } + if (!diff.len) + { + return TRUE; + } if (c.ptr[0] == '.') { /* constraint is domain, suffix match */ return TRUE; @@ -144,41 +151,155 @@ static bool dn_matches(identification_t *constraint, identification_t *id) } /** - * Check if the given identity type matches the type of NameConstraint + * Check if a new permitted or excluded NameConstraint is matching an + * existing one */ -static bool type_matches(id_type_t constraint, id_type_t id) +static bool name_constraint_matches(identification_t *existing, + identification_t *new, bool permitted) { - switch (constraint) + identification_t *a, *b; + bool matching = FALSE; + + if (permitted) + { /* permitted constraint can be narrowed */ + a = existing; + b = new; + } + else + { /* excluded constraint can be widened */ + a = new; + b = existing; + } + switch (existing->get_type(existing)) { case ID_FQDN: + matching = fqdn_matches(a, b); + break; case ID_RFC822_ADDR: + matching = email_matches(a, b); + break; case ID_DER_ASN1_DN: - return constraint == id; + matching = dn_matches(a, b); + break; case ID_IPV4_ADDR_SUBNET: - return id == ID_IPV4_ADDR; case ID_IPV6_ADDR_SUBNET: - return id == ID_IPV6_ADDR; + matching = b->matches(b, a); + break; default: - return FALSE; + /* shouldn't happen */ + matching = FALSE; + break; + } + return matching; +} + +/** + * Get the name constraint type from an identity type + */ +static id_type_t constraint_type_from_id(id_type_t id) +{ + switch (id) + { + case ID_IPV4_ADDR: + return ID_IPV4_ADDR_SUBNET; + case ID_IPV6_ADDR: + return ID_IPV6_ADDR_SUBNET; + default: + return id; } } /** - * Check if a certificate matches to a NameConstraint + * Check if the given identity matches any of the given name constraints */ -static bool name_constraint_matches(identification_t *constraint, - certificate_t *cert, bool permitted) +static bool id_matches_constraints(certificate_t *cert, identification_t *id, + array_t *constraints, bool permitted) { - x509_t *x509 = (x509_t*)cert; + enumerator_t *enumerator; + identification_t *subject, *constraint; + id_type_t type; + bool matches = FALSE; + + subject = cert->get_subject(cert); + type = id->get_type(id); + + enumerator = array_create_enumerator(constraints); + while (enumerator->enumerate(enumerator, &constraint)) + { + switch (type) + { + case ID_FQDN: + matches = fqdn_matches(constraint, id); + break; + case ID_RFC822_ADDR: + matches = email_matches(constraint, id); + break; + case ID_DER_ASN1_DN: + matches = dn_matches(constraint, id); + break; + case ID_IPV4_ADDR: + case ID_IPV6_ADDR: + matches = id->matches(id, constraint); + break; + default: + /* shouldn't happen */ + break; + } + if (matches) + { + if (!permitted) + { + if (id->equals(id, subject)) + { + DBG1(DBG_CFG, "subject of certificate '%Y' matches excluded " + "name constraint '%Y'", subject, constraint); + } + else + { + DBG1(DBG_CFG, "subject alternative name '%Y' of certificate " + "'%Y' matches excluded name constraint '%Y'", + id, subject, constraint); + } + } + break; + } + } + enumerator->destroy(enumerator); + + if (!matches && permitted) + { + if (id->equals(id, subject)) + { + DBG1(DBG_CFG, "subject of certificate '%Y' does not match any " + "permitted name constraints", subject); + } + else + { + DBG1(DBG_CFG, "subject alternative name '%Y' of certificate '%Y' " + "does not match any permitted name constraints", id, subject); + } + } + return matches; +} + +/** + * Check if a certificate matches the given permitted/excluded name constraints + */ +static bool cert_matches_constraints(x509_t *x509, hashtable_t *types, + bool permitted) +{ + certificate_t *cert = (certificate_t*)x509; + array_t *constraints; enumerator_t *enumerator; identification_t *id; id_type_t type; bool matches = permitted; - type = constraint->get_type(constraint); - if (type == ID_DER_ASN1_DN) + constraints = types->get(types, (void*)(uintptr_t)ID_DER_ASN1_DN); + if (constraints) { - matches = dn_matches(constraint, cert->get_subject(cert)); + matches = id_matches_constraints(cert, cert->get_subject(cert), + constraints, permitted); if (matches != permitted) { return matches; @@ -188,34 +309,16 @@ static bool name_constraint_matches(identification_t *constraint, enumerator = x509->create_subjectAltName_enumerator(x509); while (enumerator->enumerate(enumerator, &id)) { - if (type_matches(type, id->get_type(id))) + type = constraint_type_from_id(id->get_type(id)); + constraints = types->get(types, (void*)(uintptr_t)type); + if (constraints) { - switch (type) + matches = id_matches_constraints(cert, id, constraints, permitted); + if (matches != permitted) { - case ID_FQDN: - matches = fqdn_matches(constraint, id); - break; - case ID_RFC822_ADDR: - matches = email_matches(constraint, id); - break; - case ID_DER_ASN1_DN: - matches = dn_matches(constraint, id); - break; - case ID_IPV4_ADDR_SUBNET: - case ID_IPV6_ADDR_SUBNET: - matches = id->matches(id, constraint); - break; - default: - DBG1(DBG_CFG, "%N NameConstraint matching not implemented", - id_type_names, type); - matches = FALSE; - break; + break; } } - if (matches != permitted) - { - break; - } } enumerator->destroy(enumerator); @@ -223,112 +326,297 @@ static bool name_constraint_matches(identification_t *constraint, } /** - * Check if a permitted or excluded NameConstraint has been inherited to sub-CA + * Validate the names in the given certificate against the current constraints */ -static bool name_constraint_inherited(identification_t *constraint, - x509_t *x509, bool permitted) +static bool name_constraints_match(x509_t *x509, hashtable_t *permitted, + hashtable_t *excluded) { - enumerator_t *enumerator; - identification_t *id, *a, *b; - bool inherited = FALSE; - id_type_t type; - - if (!(x509->get_flags(x509) & X509_CA)) - { /* not a sub-CA, not required */ - return TRUE; - } - - type = constraint->get_type(constraint); - enumerator = x509->create_name_constraint_enumerator(x509, permitted); - while (enumerator->enumerate(enumerator, &id)) + if (permitted && !cert_matches_constraints(x509, permitted, TRUE)) { - if (id->get_type(id) == type) + return FALSE; + } + if (excluded && cert_matches_constraints(x509, excluded, FALSE)) + { + return FALSE; + } + return TRUE; +} + +/** + * Destroy name constraints (callback for hashtable_t::destroy_function()) + */ +CALLBACK(destroy_constraints, void, + array_t *this, const void *key) +{ + array_destroy(this); +} + +/** + * Hashtable hash function + */ +static u_int id_type_hash(const void *key) +{ + uintptr_t id = (uintptr_t)key; + return chunk_hash(chunk_from_thing(id)); +} + +/** + * Hashtable equals function + */ +static bool id_type_equals(const void *a, const void *b) +{ + return (uintptr_t)a == (uintptr_t)b; +} + +/** + * Collect name constraints (permitted or excluded) of each supported type + * from the given certificate + */ +static bool collect_constraints(x509_t *x509, bool permitted, hashtable_t **out) +{ + hashtable_t *collected; + enumerator_t *enumerator; + identification_t *constraint; + array_t *constraints; + id_type_t type; + bool success = TRUE; + + collected = hashtable_create(id_type_hash, id_type_equals, 8); + + enumerator = x509->create_name_constraint_enumerator(x509, permitted); + while (enumerator->enumerate(enumerator, &constraint)) + { + type = constraint->get_type(constraint); + switch (type) { - if (permitted) - { /* permitted constraint can be narrowed */ - a = constraint; - b = id; - } - else - { /* excluded constraint can be widened */ - a = id; - b = constraint; - } - switch (type) - { - case ID_FQDN: - inherited = fqdn_matches(a, b); - break; - case ID_RFC822_ADDR: - inherited = email_matches(a, b); - break; - case ID_DER_ASN1_DN: - inherited = dn_matches(a, b); - break; - default: - DBG1(DBG_CFG, "%N NameConstraint matching not implemented", - id_type_names, type); - inherited = FALSE; - break; - } + case ID_FQDN: + case ID_RFC822_ADDR: + case ID_DER_ASN1_DN: + case ID_IPV4_ADDR_SUBNET: + case ID_IPV6_ADDR_SUBNET: + break; + default: + DBG1(DBG_CFG, "%N NameConstraint not supported", + id_type_names, type); + success = FALSE; + break; } - if (inherited) + if (!success) { break; } + constraints = collected->get(collected, (void*)(uintptr_t)type); + if (!constraints) + { + constraints = array_create(0, 8); + collected->put(collected, (void*)(uintptr_t)type, constraints); + } + array_insert(constraints, ARRAY_TAIL, constraint); } enumerator->destroy(enumerator); - return inherited; + + if (success) + { + *out = collected; + } + else + { + collected->destroy_function(collected, destroy_constraints); + } + return success; +} + +/** + * Merge existing and new permitted/excluded name constraints + */ +static void merge_constraints(certificate_t *cert, array_t *existing_constraints, + array_t *new_constraints, bool permitted) +{ + enumerator_t *enumerator, *new; + identification_t *constraint, *new_constraint; + + if (permitted) + { + array_t *to_move = NULL; + + enumerator = array_create_enumerator(existing_constraints); + while (enumerator->enumerate(enumerator, &constraint)) + { + new = array_create_enumerator(new_constraints); + while (new->enumerate(new, &new_constraint)) + { + if (name_constraint_matches(constraint, new_constraint, TRUE)) + { + array_insert_create(&to_move, ARRAY_TAIL, new_constraint); + array_remove_at(new_constraints, new); + } + } + new->destroy(new); + + /* remove the existing constraint. if it was matched, it gets + * replaced by the moved equal/narrower constraints, if not, it's + * not permitted anymore */ + array_remove_at(existing_constraints, enumerator); + } + enumerator->destroy(enumerator); + + if (to_move) + { + while (array_remove(to_move, ARRAY_HEAD, &new_constraint)) + { + array_insert(existing_constraints, ARRAY_TAIL, new_constraint); + } + array_destroy(to_move); + } + /* report ignored constraints that would widen the permitted set */ + while (array_remove(new_constraints, ARRAY_HEAD, &new_constraint)) + { + DBG1(DBG_CFG, "ignoring name constraint '%Y' in certificate " + "'%Y' that's not permitted by parent CAs", + new_constraint, cert->get_subject(cert)); + } + } + else + { + /* this is simpler as we basically adopt all new constraints, we just + * check if we can remove a constraint that gets widened */ + enumerator = array_create_enumerator(existing_constraints); + while (enumerator->enumerate(enumerator, &constraint)) + { + new = array_create_enumerator(new_constraints); + while (new->enumerate(new, &new_constraint)) + { + if (name_constraint_matches(constraint, new_constraint, FALSE)) + { + /* remove the existing constraint if it is matched, it + * gets replaced by an equal/wider constraint */ + array_remove_at(existing_constraints, enumerator); + break; + } + } + new->destroy(new); + } + enumerator->destroy(enumerator); + + /* add all new constraints to the list */ + while (array_remove(new_constraints, ARRAY_HEAD, &new_constraint)) + { + array_insert(existing_constraints, ARRAY_TAIL, new_constraint); + } + } +} + +/** + * Update the set of permitted/excluded name constraints + */ +static bool update_name_constraints(x509_t *x509, hashtable_t **existing, + bool permitted) +{ + enumerator_t *enumerator; + hashtable_t *collected; + array_t *existing_constraints, *new_constraints; + void *type; + + if (!(x509->get_flags(x509) & X509_CA)) + { + /* ignore end-entity certificates */ + return TRUE; + } + + if (!collect_constraints(x509, permitted, &collected)) + { + return FALSE; + } + if (collected->get_count(collected)) + { + if (!*existing) + { + /* adopt all constraints if we haven't any yet */ + *existing = collected; + collected = NULL; + } + else + { + /* merge sets of constraints for each type */ + enumerator = collected->create_enumerator(collected); + while (enumerator->enumerate(enumerator, &type, &new_constraints)) + { + existing_constraints = (*existing)->get(*existing, type); + if (existing_constraints) + { + /* merge constraints of known types, either allowing them to + * get narrowed or widened */ + merge_constraints((certificate_t*)x509, existing_constraints, + new_constraints, permitted); + } + else + { + /* adopt constraints for new types */ + collected->remove_at(collected, enumerator); + (*existing)->put(*existing, type, new_constraints); + } + } + enumerator->destroy(enumerator); + } + } + DESTROY_FUNCTION_IF(collected, destroy_constraints); + return TRUE; } /** * Check name constraints */ -static bool check_name_constraints(certificate_t *subject, x509_t *issuer) +static bool check_name_constraints(x509_t *issuer, u_int pathlen, + auth_cfg_t *auth, certificate_t **violator) { enumerator_t *enumerator; - identification_t *constraint; + linked_list_t *chain; + hashtable_t *permitted = NULL, *excluded = NULL; + certificate_t *subject, *cert; + auth_rule_t rule; + x509_t *x509; + int len = 0; + bool valid = TRUE; - enumerator = issuer->create_name_constraint_enumerator(issuer, TRUE); - while (enumerator->enumerate(enumerator, &constraint)) + subject = auth->get(auth, AUTH_RULE_SUBJECT_CERT); + if (!subject || subject->get_type(subject) != CERT_X509) { - if (!name_constraint_matches(constraint, subject, TRUE)) + return TRUE; + } + + /* prepare trustchain to validate name constraints top-down */ + chain = linked_list_create_with_items(subject, NULL); + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &rule, &cert)) + { + if (rule == AUTH_RULE_IM_CERT && + cert->get_type(cert) == CERT_X509) { - DBG1(DBG_CFG, "certificate '%Y' does not match permitted name " - "constraint '%Y'", subject->get_subject(subject), constraint); - enumerator->destroy(enumerator); - return FALSE; - } - if (!name_constraint_inherited(constraint, (x509_t*)subject, TRUE)) - { - DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit permitted name " - "constraint '%Y'", subject->get_subject(subject), constraint); - enumerator->destroy(enumerator); - return FALSE; + chain->insert_first(chain, cert); } } enumerator->destroy(enumerator); + chain->insert_first(chain, issuer); - enumerator = issuer->create_name_constraint_enumerator(issuer, FALSE); - while (enumerator->enumerate(enumerator, &constraint)) + enumerator = chain->create_enumerator(chain); + while (enumerator->enumerate(enumerator, &x509)) { - if (name_constraint_matches(constraint, subject, FALSE)) + if ((len > 0 && !name_constraints_match(x509, permitted, excluded)) || + !update_name_constraints(x509, &permitted, TRUE) || + !update_name_constraints(x509, &excluded, FALSE)) { - DBG1(DBG_CFG, "certificate '%Y' matches excluded name " - "constraint '%Y'", subject->get_subject(subject), constraint); - enumerator->destroy(enumerator); - return FALSE; - } - if (!name_constraint_inherited(constraint, (x509_t*)subject, FALSE)) - { - DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit excluded name " - "constraint '%Y'", subject->get_subject(subject), constraint); - enumerator->destroy(enumerator); - return FALSE; + valid = FALSE; + *violator = (certificate_t*)x509; + break; } + len++; } enumerator->destroy(enumerator); - return TRUE; + + DESTROY_FUNCTION_IF(permitted, destroy_constraints); + DESTROY_FUNCTION_IF(excluded, destroy_constraints); + chain->destroy(chain); + return valid; } /** @@ -690,14 +978,16 @@ METHOD(cert_validator_t, validate, bool, subject); return FALSE; } - if (!check_name_constraints(subject, (x509_t*)issuer)) - { - lib->credmgr->call_hook(lib->credmgr, CRED_HOOK_POLICY_VIOLATION, - subject); - return FALSE; - } if (anchor) { + certificate_t *violator; + + if (!check_name_constraints((x509_t*)issuer, pathlen, auth, &violator)) + { + lib->credmgr->call_hook(lib->credmgr, + CRED_HOOK_POLICY_VIOLATION, violator); + return FALSE; + } if (!check_policy_constraints((x509_t*)issuer, pathlen, auth)) { lib->credmgr->call_hook(lib->credmgr, diff --git a/src/libstrongswan/tests/suites/test_certnames.c b/src/libstrongswan/tests/suites/test_certnames.c index 367291236..2549fb6e3 100644 --- a/src/libstrongswan/tests/suites/test_certnames.c +++ b/src/libstrongswan/tests/suites/test_certnames.c @@ -68,15 +68,15 @@ static char keydata[] = { /** * Issue a certificate with permitted/excluded name constraints */ -static certificate_t* create_cert(certificate_t *ca, char *subject, char *san, - x509_flag_t flags, identification_t *permitted, - identification_t *excluded) +static certificate_t* create_cert_lists(certificate_t *ca, char *subject, + linked_list_t *sans, x509_flag_t flags, + linked_list_t *permitted, + linked_list_t *excluded) { private_key_t *privkey; public_key_t *pubkey; certificate_t *cert; identification_t *id; - linked_list_t *plist, *elist, *sans; privkey = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, BUILD_BLOB_ASN1_DER, chunk_from_thing(keydata), @@ -84,6 +84,39 @@ static certificate_t* create_cert(certificate_t *ca, char *subject, char *san, ck_assert(privkey); pubkey = privkey->get_public_key(privkey); ck_assert(pubkey); + + id = identification_create_from_string(subject); + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_SIGNING_KEY, privkey, + BUILD_PUBLIC_KEY, pubkey, + BUILD_SUBJECT, id, + BUILD_X509_FLAG, flags, + BUILD_SIGNING_CERT, ca, + BUILD_SUBJECT_ALTNAMES, sans, + BUILD_PERMITTED_NAME_CONSTRAINTS, permitted, + BUILD_EXCLUDED_NAME_CONSTRAINTS, excluded, + BUILD_END); + ck_assert(cert); + id->destroy(id); + sans->destroy_offset(sans, offsetof(identification_t, destroy)); + permitted->destroy_offset(permitted, offsetof(identification_t, destroy)); + excluded->destroy_offset(excluded, offsetof(identification_t, destroy)); + privkey->destroy(privkey); + pubkey->destroy(pubkey); + + return cert; +} + +/** + * Issue a certificate with single values + */ +static certificate_t* create_cert(certificate_t *ca, char *subject, char *san, + x509_flag_t flags, identification_t *permitted, + identification_t *excluded) +{ + linked_list_t *plist, *elist, *sans; + identification_t *id; + plist = linked_list_create(); if (permitted) { @@ -100,26 +133,7 @@ static certificate_t* create_cert(certificate_t *ca, char *subject, char *san, id = identification_create_from_string(san); sans->insert_last(sans, id); } - id = identification_create_from_string(subject); - cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_SIGNING_KEY, privkey, - BUILD_PUBLIC_KEY, pubkey, - BUILD_SUBJECT, id, - BUILD_X509_FLAG, flags, - BUILD_SIGNING_CERT, ca, - BUILD_SUBJECT_ALTNAMES, sans, - BUILD_PERMITTED_NAME_CONSTRAINTS, plist, - BUILD_EXCLUDED_NAME_CONSTRAINTS, elist, - BUILD_END); - ck_assert(cert); - id->destroy(id); - sans->destroy_offset(sans, offsetof(identification_t, destroy)); - plist->destroy_offset(plist, offsetof(identification_t, destroy)); - elist->destroy_offset(elist, offsetof(identification_t, destroy)); - privkey->destroy(privkey); - pubkey->destroy(pubkey); - - return cert; + return create_cert_lists(ca, subject, sans, flags, plist, elist); } /** @@ -188,26 +202,29 @@ START_TEST(test_permitted_dn) END_TEST static struct { - id_type_t ctype; char *cdata; char *subject; bool good; } permitted_san[] = { - { ID_FQDN, ".strongswan.org", "test.strongswan.org", TRUE }, - { ID_FQDN, "strongswan.org", "test.strongswan.org", TRUE }, - { ID_FQDN, "a.b.c.strongswan.org", "d.a.b.c.strongswan.org", TRUE }, - { ID_FQDN, "a.b.c.strongswan.org", "a.b.c.d.strongswan.org", FALSE }, - { ID_FQDN, "strongswan.org", "strongswan.org.com", FALSE }, - { ID_FQDN, ".strongswan.org", "strongswan.org", FALSE }, - { ID_FQDN, "strongswan.org", "nostrongswan.org", FALSE }, - { ID_FQDN, "strongswan.org", "swan.org", FALSE }, - { ID_FQDN, "strongswan.org", "swan.org", FALSE }, - { ID_RFC822_ADDR, "tester@strongswan.org", "tester@strongswan.org", TRUE }, - { ID_RFC822_ADDR, "tester@strongswan.org", "atester@strongswan.org", FALSE }, - { ID_RFC822_ADDR, "strongswan.org", "tester@strongswan.org", TRUE }, - { ID_RFC822_ADDR, "strongswan.org", "tester@test.strongswan.org", FALSE }, - { ID_RFC822_ADDR, ".strongswan.org", "tester@test.strongswan.org", TRUE }, - { ID_RFC822_ADDR, ".strongswan.org", "tester@strongswan.org", FALSE }, + { ".strongswan.org", "test.strongswan.org", TRUE }, + { "strongswan.org", "test.strongswan.org", TRUE }, + { "a.b.c.strongswan.org", "d.a.b.c.strongswan.org", TRUE }, + { "a.b.c.strongswan.org", "a.b.c.d.strongswan.org", FALSE }, + { "strongswan.org", "strongswan.org.com", FALSE }, + { ".strongswan.org", "strongswan.org", FALSE }, + { "strongswan.org", "nostrongswan.org", FALSE }, + { "strongswan.org", "swan.org", FALSE }, + { "strongswan.org", "swan.org", FALSE }, + { "tester@strongswan.org", "tester@strongswan.org", TRUE }, + { "tester@strongswan.org", "atester@strongswan.org", FALSE }, + { "email:strongswan.org", "tester@strongswan.org", TRUE }, + { "email:strongswan.org", "tester@test.strongswan.org", FALSE }, + { "email:.strongswan.org", "tester@test.strongswan.org", TRUE }, + { "email:.strongswan.org", "tester@strongswan.org", FALSE }, + { "192.168.1.0/24", "192.168.1.10", TRUE }, + { "192.168.1.0/24", "192.168.2.10", FALSE }, + { "fec0::/64", "fec0::10", TRUE }, + { "fec0::/64", "fec1::10", FALSE }, }; START_TEST(test_permitted_san) @@ -215,8 +232,7 @@ START_TEST(test_permitted_san) certificate_t *ca, *sj; identification_t *id; - id = identification_create_from_encoding(permitted_san[_i].ctype, - chunk_from_str(permitted_san[_i].cdata)); + id = identification_create_from_string(permitted_san[_i].cdata); ca = create_cert(NULL, "CN=CA", NULL, X509_CA, id, NULL); sj = create_cert(ca, "CN=SJ", permitted_san[_i].subject, 0, NULL, NULL); @@ -259,26 +275,29 @@ START_TEST(test_excluded_dn) END_TEST static struct { - id_type_t ctype; char *cdata; char *subject; bool good; } excluded_san[] = { - { ID_FQDN, ".strongswan.org", "test.strongswan.org", FALSE }, - { ID_FQDN, "strongswan.org", "test.strongswan.org", FALSE }, - { ID_FQDN, "a.b.c.strongswan.org", "d.a.b.c.strongswan.org", FALSE }, - { ID_FQDN, "a.b.c.strongswan.org", "a.b.c.d.strongswan.org", TRUE }, - { ID_FQDN, "strongswan.org", "strongswan.org.com", TRUE }, - { ID_FQDN, ".strongswan.org", "strongswan.org", TRUE }, - { ID_FQDN, "strongswan.org", "nostrongswan.org", TRUE }, - { ID_FQDN, "strongswan.org", "swan.org", TRUE }, - { ID_FQDN, "strongswan.org", "swan.org", TRUE }, - { ID_RFC822_ADDR, "tester@strongswan.org", "tester@strongswan.org", FALSE }, - { ID_RFC822_ADDR, "tester@strongswan.org", "atester@strongswan.org", TRUE }, - { ID_RFC822_ADDR, "strongswan.org", "tester@strongswan.org", FALSE }, - { ID_RFC822_ADDR, "strongswan.org", "tester@test.strongswan.org", TRUE }, - { ID_RFC822_ADDR, ".strongswan.org", "tester@test.strongswan.org", FALSE }, - { ID_RFC822_ADDR, ".strongswan.org", "tester@strongswan.org", TRUE }, + { ".strongswan.org", "test.strongswan.org", FALSE }, + { "strongswan.org", "test.strongswan.org", FALSE }, + { "a.b.c.strongswan.org", "d.a.b.c.strongswan.org", FALSE }, + { "a.b.c.strongswan.org", "a.b.c.d.strongswan.org", TRUE }, + { "strongswan.org", "strongswan.org.com", TRUE }, + { ".strongswan.org", "strongswan.org", TRUE }, + { "strongswan.org", "nostrongswan.org", TRUE }, + { "strongswan.org", "swan.org", TRUE }, + { "strongswan.org", "swan.org", TRUE }, + { "tester@strongswan.org", "tester@strongswan.org", FALSE }, + { "tester@strongswan.org", "atester@strongswan.org", TRUE }, + { "email:strongswan.org", "tester@strongswan.org", FALSE }, + { "email:strongswan.org", "tester@test.strongswan.org", TRUE }, + { "email:.strongswan.org", "tester@test.strongswan.org", FALSE }, + { "email:.strongswan.org", "tester@strongswan.org", TRUE }, + { "192.168.1.0/24", "192.168.1.10", FALSE }, + { "192.168.1.0/24", "192.168.2.10", TRUE }, + { "fec0::/64", "fec0::10", FALSE }, + { "fec0::/64", "fec1::10", TRUE }, }; START_TEST(test_excluded_san) @@ -286,8 +305,7 @@ START_TEST(test_excluded_san) certificate_t *ca, *sj; identification_t *id; - id = identification_create_from_encoding(excluded_san[_i].ctype, - chunk_from_str(excluded_san[_i].cdata)); + id = identification_create_from_string(excluded_san[_i].cdata); ca = create_cert(NULL, "CN=CA", NULL, X509_CA, NULL, id); sj = create_cert(ca, "CN=SJ", excluded_san[_i].subject, 0, NULL, NULL); @@ -298,33 +316,45 @@ START_TEST(test_excluded_san) } END_TEST +/** + * Create an identity if the given string is not NULL + */ +static identification_t *create_test_id(char *id) +{ + return id ? identification_create_from_string(id) : NULL; +} + static struct { char *caconst; char *imconst; char *subject; bool good; -} permitted_dninh[] = { +} permitted_dn_levels[] = { { "C=CH", "C=CH, O=strongSwan", "C=CH, O=strongSwan, CN=tester", TRUE }, + { "C=CH", NULL, "C=CH, O=strongSwan, CN=tester", TRUE }, + { NULL, "C=CH, O=strongSwan", "C=CH, O=strongSwan, CN=tester", TRUE }, { "C=CH", "C=DE, O=strongSwan", "C=CH, O=strongSwan, CN=tester", FALSE }, + { "C=CH", "C=DE", "C=DE, O=strongSwan, CN=tester", FALSE }, { "C=CH, O=strongSwan", "C=CH", "C=CH", FALSE }, + { "C=CH, O=strongSwan, CN=Intermediate", NULL, "C=CH", FALSE }, }; -START_TEST(test_permitted_dninh) +START_TEST(test_permitted_dn_levels) { certificate_t *ca, *im, *sj; identification_t *id; - id = identification_create_from_string(permitted_dninh[_i].caconst); + id = create_test_id(permitted_dn_levels[_i].caconst); ca = create_cert(NULL, "C=CH, O=strongSwan, CN=CA", NULL, X509_CA, id, NULL); - id = identification_create_from_string(permitted_dninh[_i].imconst); + id = create_test_id(permitted_dn_levels[_i].imconst); im = create_cert(ca, "C=CH, O=strongSwan, CN=IM", NULL, X509_CA, id, NULL); - sj = create_cert(im, permitted_dninh[_i].subject, NULL, 0, NULL, NULL); + sj = create_cert(im, permitted_dn_levels[_i].subject, NULL, 0, NULL, NULL); creds->add_cert(creds, TRUE, ca); creds->add_cert(creds, FALSE, im); creds->add_cert(creds, FALSE, sj); - ck_assert(check_trust(sj->get_subject(sj)) == permitted_dninh[_i].good); + ck_assert(check_trust(sj->get_subject(sj)) == permitted_dn_levels[_i].good); } END_TEST @@ -333,28 +363,301 @@ static struct { char *imconst; char *subject; bool good; -} excluded_dninh[] = { - { "C=CH, O=strongSwan", "C=CH", "C=DE", TRUE }, - { "C=CH, O=strongSwan", "C=DE", "C=CH", FALSE }, - { "C=CH", "C=CH, O=strongSwan", "C=CH, O=strongSwan, CN=tester", FALSE }, +} permitted_san_levels[] = { + { "strongswan.org", NULL, "strongswan.org", TRUE }, + { "strongswan.org", NULL, "vpn.strongswan.org", TRUE }, + { "strongswan.org", NULL, "strongswan.com", FALSE }, + { NULL, "strongswan.org", "strongswan.org", TRUE }, + { NULL, "strongswan.org", "strongswan.com", FALSE }, + { "strongswan.org", "strongswan.org", "strongswan.org", TRUE }, + { "strongswan.org", "strongswan.com", "strongswan.com", FALSE }, + { "strongswan.org", "vpn.strongswan.org", "strongswan.org", FALSE }, + { "strongswan.org", "vpn.strongswan.org", "vpn.strongswan.org", TRUE }, + { "strongswan.org", "vpn.strongswan.org", "a.vpn.strongswan.org", TRUE }, + { "strongswan.org", NULL, "tester@strongswan.org", TRUE }, + { "tester@strongswan.org", NULL, "tester@strongswan.org", TRUE }, + { "email:strongswan.org", NULL, "tester@strongswan.org", TRUE }, + { "email:strongswan.org", NULL, "tester@strongswan.com", FALSE }, + { "email:strongswan.org", "tester@strongswan.org", "tester@strongswan.org", TRUE }, + { "email:strongswan.org", "tester@strongswan.org", "alice@strongswan.org", FALSE }, + { "email:strongswan.org", "strongswan.org", "vpn.strongswan.org", TRUE }, + { "192.168.1.0/24", NULL, "192.168.1.10", TRUE }, + { "192.168.1.0/24", NULL, "192.168.2.10", FALSE }, + { "192.168.1.0/24", "192.168.2.0/24", "192.168.1.10", FALSE }, + { "192.168.1.0/24", "192.168.1.0/28", "192.168.1.10", TRUE }, + { "192.168.1.0/24", "192.168.1.16/28", "192.168.1.10", FALSE }, + { "fec0::/64", NULL, "fec0::10", TRUE }, + { "fec0::/64", NULL, "fec1::10", FALSE }, + { "fec0::/64", "fec1::/64", "fec1::10", FALSE }, + { "fec0::/64", "fec0::/123", "fec0::10", TRUE }, + { "fec0::/64", "fec0::20/123", "fec0::10", FALSE }, }; -START_TEST(test_excluded_dninh) +START_TEST(test_permitted_san_levels) { certificate_t *ca, *im, *sj; identification_t *id; - id = identification_create_from_string(excluded_dninh[_i].caconst); - ca = create_cert(NULL, "C=CH, O=strongSwan, CN=CA", NULL, X509_CA, NULL, id); - id = identification_create_from_string(excluded_dninh[_i].imconst); - im = create_cert(ca, "C=DE, CN=IM", NULL, X509_CA, NULL, id); - sj = create_cert(im, excluded_dninh[_i].subject, NULL, 0, NULL, NULL); + id = create_test_id(permitted_san_levels[_i].caconst); + ca = create_cert(NULL, "CN=CA", NULL, X509_CA, id, NULL); + id = create_test_id(permitted_san_levels[_i].imconst); + im = create_cert(ca, "CN=IM", NULL, X509_CA, id, NULL); + sj = create_cert(im, "CN=EE", permitted_san_levels[_i].subject, 0, NULL, NULL); creds->add_cert(creds, TRUE, ca); creds->add_cert(creds, FALSE, im); creds->add_cert(creds, FALSE, sj); - ck_assert(check_trust(sj->get_subject(sj)) == excluded_dninh[_i].good); + ck_assert(check_trust(sj->get_subject(sj)) == permitted_san_levels[_i].good); +} +END_TEST + +static struct { + char *caconst; + char *imconst; + char *subject; + bool good; +} excluded_dn_levels[] = { + { "C=CH, O=strongSwan", "C=CH", "C=DE", TRUE }, + { "C=CH, O=strongSwan", "C=CH", "C=CH", FALSE }, + { "C=CH, O=strongSwan", "C=DE", "C=CH", TRUE }, + { "C=CH, O=strongSwan", "C=DE", "C=DE", FALSE }, + { "C=CH, O=strongSwan", "C=DE", "C=CH, O=strongSwan", FALSE }, + { NULL, "C=CH", "C=CH, O=strongSwan", FALSE }, + { "C=CH", NULL, "C=CH, O=strongSwan", FALSE }, + { "C=CH", "C=CH, O=strongSwan", "C=CH, O=strongSwan, CN=tester", FALSE }, + { "C=DE", NULL, "C=CH, O=strongSwan, CN=tester", FALSE }, +}; + +START_TEST(test_excluded_dn_levels) +{ + certificate_t *ca, *im, *sj; + identification_t *id; + + id = create_test_id(excluded_dn_levels[_i].caconst); + ca = create_cert(NULL, "C=CH, O=strongSwan, CN=CA", NULL, X509_CA, NULL, id); + id = create_test_id(excluded_dn_levels[_i].imconst); + im = create_cert(ca, "C=DE, CN=IM", NULL, X509_CA, NULL, id); + sj = create_cert(im, excluded_dn_levels[_i].subject, NULL, 0, NULL, NULL); + + creds->add_cert(creds, TRUE, ca); + creds->add_cert(creds, FALSE, im); + creds->add_cert(creds, FALSE, sj); + + ck_assert(check_trust(sj->get_subject(sj)) == excluded_dn_levels[_i].good); +} +END_TEST + +static struct { + char *caconst; + char *imconst; + char *subject; + bool good; +} excluded_san_levels[] = { + { "strongswan.org", NULL, "strongswan.org", FALSE }, + { "strongswan.org", NULL, "strongswan.com", TRUE }, + { NULL, "strongswan.org", "strongswan.org", FALSE }, + { NULL, "strongswan.org", "strongswan.com", TRUE }, + { "strongswan.org", NULL, "test.strongswan.org", FALSE }, + { "test.strongswan.org", NULL, "test.strongswan.org", FALSE }, + { "test.strongswan.org", NULL, "strongswan.org", TRUE }, + { "test.strongswan.org", "strongswan.org", "strongswan.org", FALSE }, + { "test.strongswan.org", "strongswan.org", "test.strongswan.org", FALSE }, + { "test.strongswan.org", "test.strongswan.org", "test.strongswan.org", FALSE }, + { "strongswan.org", NULL, "tester@strongswan.org", TRUE }, + { "tester@strongswan.org", NULL, "tester@strongswan.org", FALSE }, + { "tester@strongswan.org", NULL, "alice@strongswan.org", TRUE }, + { "email:strongswan.org", NULL, "tester@strongswan.org", FALSE }, + { "email:strongswan.org", NULL, "tester@strongswan.com", TRUE }, + { "email:strongswan.org", "email:strongswan.com", "tester@strongswan.org", FALSE }, + { "email:strongswan.org", "email:strongswan.com", "tester@strongswan.com", FALSE }, + { "strongswan.org", "email:strongswan.com", "tester@strongswan.com", FALSE }, + { "192.168.1.0/24", NULL, "192.168.1.10", FALSE }, + { "192.168.1.0/24", NULL, "192.168.2.10", TRUE }, + { "192.168.1.0/24", "192.168.0.0/16", "192.168.2.10", FALSE }, + { "fec0::/64", NULL, "fec0::10", FALSE }, + { "fec0::/64", NULL, "fec1::10", TRUE }, + { "fec0::/64", "fec1::/12", "fec1::10", FALSE }, +}; + +START_TEST(test_excluded_san_levels) +{ + certificate_t *ca, *im, *sj; + identification_t *id; + + id = create_test_id(excluded_san_levels[_i].caconst); + ca = create_cert(NULL, "CN=CA", NULL, X509_CA, NULL, id); + id = create_test_id(excluded_san_levels[_i].imconst); + im = create_cert(ca, "CN=IM", NULL, X509_CA, NULL, id); + sj = create_cert(im, "CN=EE", excluded_san_levels[_i].subject, 0, NULL, NULL); + + creds->add_cert(creds, TRUE, ca); + creds->add_cert(creds, FALSE, im); + creds->add_cert(creds, FALSE, sj); + + ck_assert(check_trust(sj->get_subject(sj)) == excluded_san_levels[_i].good); +} +END_TEST + +/** + * Add an identity to the given list if not NULL + */ +static void add_identity_to_list(linked_list_t *list, char *idstr) +{ + identification_t *id; + + if (idstr) + { + id = identification_create_from_string(idstr); + list->insert_last(list, id); + } +} + +/** + * Create a certificate with potentially multiple constraints/SANs + */ +static certificate_t *create_cert_multi(certificate_t *ca, char *subject, + x509_flag_t flags, + char *san1, char *san2, + char *pconst1, char *pconst2, + char *econst1, char *econst2) +{ + linked_list_t *sans, *permitted, *excluded; + + sans = linked_list_create(); + add_identity_to_list(sans, san1); + add_identity_to_list(sans, san2); + + permitted = linked_list_create(); + add_identity_to_list(permitted, pconst1); + add_identity_to_list(permitted, pconst2); + + excluded = linked_list_create(); + add_identity_to_list(excluded, econst1); + add_identity_to_list(excluded, econst2); + + return create_cert_lists(ca, subject, sans, flags, permitted, excluded); +} + +static struct { + char *caconst1; + char *caconst2; + char *imconst1; + char *imconst2; + char *san1; + char *san2; + bool good; +} permitted_san_multi[] = { + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.com", NULL, TRUE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.org", "vpn.strongswan.com", TRUE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, TRUE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.com", NULL, TRUE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.org", "vpn.strongswan.com", TRUE }, + { "strongswan.org", "strongswan.com", "strongswan.org", NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, "vpn.strongswan.org", "vpn.strongswan.com", FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.com", NULL, "vpn.strongswan.org", "vpn.strongswan.com", FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.org", NULL, "vpn.strongswan.com", NULL, FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.com", NULL, "vpn.strongswan.org", NULL, FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.com", NULL, "vpn.strongswan.com", NULL, TRUE }, + { "strongswan.org", "strongswan.com", "strongswan.net", NULL, "vpn.strongswan.com", NULL, FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.net", NULL, "vpn.strongswan.org", NULL, FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.net", NULL, "vpn.strongswan.net", NULL, FALSE }, + { "strongswan.org", "email:strongswan.org", NULL, NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", NULL, NULL, "tester@strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", NULL, NULL, "vpn.strongswan.org", "tester@strongswan.org", TRUE }, + { "strongswan.org", "email:strongswan.org", "strongswan.org", NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", "strongswan.org", NULL, "tester@strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", "strongswan.org", NULL, "vpn.strongswan.org", "tester@strongswan.org", TRUE }, + { "strongswan.org", "email:strongswan.org", "strongswan.org", "email:strongswan.com", "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", "strongswan.org", "email:strongswan.com", "tester@strongswan.org", NULL, FALSE }, + { "strongswan.org", "email:strongswan.org", "strongswan.org", "email:strongswan.com", "vpn.strongswan.org", "tester@strongswan.org", FALSE }, + { "strongswan.org", "email:strongswan.org", "email:strongswan.org", NULL, "vpn.strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", "email:strongswan.org", NULL, "tester@strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", "email:strongswan.org", NULL, "vpn.strongswan.org", "tester@strongswan.org", TRUE }, + { "strongswan.org", "email:strongswan.org", "email:strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, FALSE }, + { "strongswan.org", "email:strongswan.org", "email:strongswan.org", "strongswan.com", "tester@strongswan.org", NULL, TRUE }, + { "strongswan.org", "email:strongswan.org", "email:strongswan.org", "strongswan.com", "vpn.strongswan.org", "tester@strongswan.org", FALSE }, +}; + +START_TEST(test_permitted_san_multi) +{ + certificate_t *ca, *im, *sj; + + + ca = create_cert_multi(NULL, "CN=CA", X509_CA, NULL, NULL, + permitted_san_multi[_i].caconst1, + permitted_san_multi[_i].caconst2, NULL, NULL); + im = create_cert_multi(ca, "CN=IM", X509_CA, NULL, NULL, + permitted_san_multi[_i].imconst1, + permitted_san_multi[_i].imconst2, NULL, NULL); + sj = create_cert_multi(im, "CN=EE", 0, + permitted_san_multi[_i].san1, + permitted_san_multi[_i].san2, NULL, NULL, NULL, NULL); + + creds->add_cert(creds, TRUE, ca); + creds->add_cert(creds, FALSE, im); + creds->add_cert(creds, FALSE, sj); + + ck_assert(check_trust(sj->get_subject(sj)) == permitted_san_multi[_i].good); +} +END_TEST + +static struct { + char *caconst1; + char *caconst2; + char *imconst1; + char *imconst2; + char *san1; + char *san2; + bool good; +} excluded_san_multi[] = { + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.org", NULL, FALSE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "tester@strongswan.org", NULL, TRUE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.com", NULL, FALSE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.net", NULL, TRUE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.org", "vpn.strongswan.com", FALSE }, + { "strongswan.org", "strongswan.com", NULL, NULL, "vpn.strongswan.org", "vpn.strongswan.net", FALSE }, + { "strongswan.org", NULL, NULL, NULL, "vpn.strongswan.org", "vpn.strongswan.com", FALSE }, + { "strongswan.org", NULL, NULL, NULL, "vpn.strongswan.com", "vpn.strongswan.org", FALSE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, FALSE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.com", NULL, FALSE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.net", NULL, TRUE }, + { NULL, NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.org", "vpn.strongswan.com", FALSE }, + { "strongswan.org", "strongswan.com", "strongswan.net", NULL, "vpn.strongswan.net", NULL, FALSE }, + { "strongswan.net", NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.net", NULL, FALSE }, + { "strongswan.net", NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.org", NULL, FALSE }, + { "strongswan.net", NULL, "strongswan.org", "strongswan.com", "vpn.strongswan.com", NULL, FALSE }, + { "vpn.strongswan.org", "vpn.strongswan.com", "strongswan.org", NULL, "a.strongswan.org", NULL, FALSE }, + { "vpn.strongswan.org", "vpn.strongswan.com", "strongswan.org", NULL, "vpn.strongswan.com", NULL, FALSE }, + { "vpn.strongswan.org", "vpn.strongswan.com", "strongswan.org", NULL, "a.strongswan.com", NULL, TRUE }, + { "vpn.strongswan.org", "vpn.strongswan.com", "strongswan.org", "strongswan.com", "a.strongswan.com", NULL, FALSE }, + { "strongswan.org", "email:strongswan.org", NULL, NULL, "vpn.strongswan.org", NULL, FALSE }, + { "strongswan.org", "email:strongswan.org", NULL, NULL, "tester@strongswan.org", NULL, FALSE }, +}; + +START_TEST(test_excluded_san_multi) +{ + certificate_t *ca, *im, *sj; + + + ca = create_cert_multi(NULL, "CN=CA", X509_CA, NULL, NULL, NULL, NULL, + excluded_san_multi[_i].caconst1, + excluded_san_multi[_i].caconst2); + im = create_cert_multi(ca, "CN=IM", X509_CA, NULL, NULL, NULL, NULL, + excluded_san_multi[_i].imconst1, + excluded_san_multi[_i].imconst2); + sj = create_cert_multi(im, "CN=EE", 0, + excluded_san_multi[_i].san1, + excluded_san_multi[_i].san2, NULL, NULL, NULL, NULL); + + creds->add_cert(creds, TRUE, ca); + creds->add_cert(creds, FALSE, im); + creds->add_cert(creds, FALSE, sj); + + ck_assert(check_trust(sj->get_subject(sj)) == excluded_san_multi[_i].good); } END_TEST @@ -385,14 +688,34 @@ Suite *certnames_suite_create() tcase_add_loop_test(tc, test_excluded_san, 0, countof(excluded_san)); suite_add_tcase(s, tc); - tc = tcase_create("permitted DN name constraint inherit"); + tc = tcase_create("permitted DN name constraints multilevel"); tcase_add_checked_fixture(tc, setup, teardown); - tcase_add_loop_test(tc, test_permitted_dninh, 0, countof(permitted_dninh)); + tcase_add_loop_test(tc, test_permitted_dn_levels, 0, countof(permitted_dn_levels)); suite_add_tcase(s, tc); - tc = tcase_create("excluded DN name constraint inherit"); + tc = tcase_create("permitted subjectAltName constraints multilevel"); tcase_add_checked_fixture(tc, setup, teardown); - tcase_add_loop_test(tc, test_excluded_dninh, 0, countof(excluded_dninh)); + tcase_add_loop_test(tc, test_permitted_san_levels, 0, countof(permitted_san_levels)); + suite_add_tcase(s, tc); + + tc = tcase_create("excluded DN name constraints multilevel"); + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_loop_test(tc, test_excluded_dn_levels, 0, countof(excluded_dn_levels)); + suite_add_tcase(s, tc); + + tc = tcase_create("excluded subjectAltName constraints multilevel"); + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_loop_test(tc, test_excluded_san_levels, 0, countof(excluded_san_levels)); + suite_add_tcase(s, tc); + + tc = tcase_create("permitted subjectAltName constraints multivalue"); + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_loop_test(tc, test_permitted_san_multi, 0, countof(permitted_san_multi)); + suite_add_tcase(s, tc); + + tc = tcase_create("excluded subjectAltName constraints multivalue"); + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_loop_test(tc, test_excluded_san_multi, 0, countof(excluded_san_multi)); suite_add_tcase(s, tc); return s;