diff --git a/configure.ac b/configure.ac index d6fe2983a..25453f144 100644 --- a/configure.ac +++ b/configure.ac @@ -1553,7 +1553,7 @@ ADD_PLUGIN([random], [s charon pki scripts manager medsrv attest n ADD_PLUGIN([nonce], [s charon nm cmd aikgen]) ADD_PLUGIN([x509], [s charon pki scripts attest nm cmd aikgen fuzz]) ADD_PLUGIN([revocation], [s charon pki nm cmd]) -ADD_PLUGIN([constraints], [s charon nm cmd]) +ADD_PLUGIN([constraints], [s charon pki nm cmd]) ADD_PLUGIN([acert], [s charon]) ADD_PLUGIN([pubkey], [s charon pki cmd aikgen]) ADD_PLUGIN([pkcs1], [s charon pki scripts manager medsrv attest nm cmd aikgen fuzz]) 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; diff --git a/src/libstrongswan/tests/suites/test_identification.c b/src/libstrongswan/tests/suites/test_identification.c index eac6b55a5..db4043769 100644 --- a/src/libstrongswan/tests/suites/test_identification.c +++ b/src/libstrongswan/tests/suites/test_identification.c @@ -786,7 +786,7 @@ START_TEST(test_matches_binary) } END_TEST -START_TEST(test_matches_range) +START_TEST(test_matches_range_addr) { identification_t *a, *b; @@ -869,6 +869,242 @@ START_TEST(test_matches_range) } END_TEST +START_TEST(test_matches_range_subnet) +{ + identification_t *a, *b; + + /* IPv4 subnets */ + a = identification_create_from_string("192.168.1.0/24"); + ck_assert(a->get_type(a) == ID_IPV4_ADDR_SUBNET); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.2", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.1/32", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0/32", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0/24", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.0.0/24", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.0.0/16", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0-192.168.1.255", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.0.0-192.168.255.255", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE)); + + /* Malformed IPv4 subnet and range encoding */ + b = identification_create_from_encoding(ID_IPV4_ADDR_SUBNET, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV4_ADDR_RANGE, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV4_ADDR_RANGE, + chunk_from_chars(0xc0,0xa8,0x01,0x28,0xc0,0xa8,0x01,0x00)); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + a->destroy(a); + + a = identification_create_from_string("192.168.1.1/32"); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "192.168.1.1/32", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.1/31", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.2", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0/24", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.1.0-192.168.1.0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.0-192.168.1.1", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.2", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.0.0-192.168.1.255", ID_MATCH_ONE_WILDCARD)); + a->destroy(a); + + /* IPv6 subnets */ + a = identification_create_from_string("fec0::0/64"); + ck_assert(a->get_type(a) == ID_IPV6_ADDR_SUBNET); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "::/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "fec0::1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::2", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::1/128", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::/128", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::/64", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::/48", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec1::/48", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::-fec0::1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::-fec0::ffff:ffff:ffff:ffff", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::-fec0::f:ffff:ffff:ffff:ffff", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec0::4001-fec0::4ffe", ID_MATCH_NONE)); + ck_assert(id_matches(a, "feb0::1-fec0::0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE)); + + /* Malformed IPv6 subnet and range encoding */ + b = identification_create_from_encoding(ID_IPV6_ADDR_SUBNET, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV6_ADDR_RANGE, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV6_ADDR_RANGE, + chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xff, + 0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 )); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + a->destroy(a); + + a = identification_create_from_string("fec0::1/128"); + ck_assert(a->get_type(a) == ID_IPV6_ADDR_SUBNET); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "::/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "fec0::1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::2", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::1/128", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::/128", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::/64", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec1::/48", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::0-fec0::0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::1-fec0::1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::0-fec0::1", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec0::1-fec0::2", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec0::-fec0::ffff:ffff:ffff:ffff", ID_MATCH_ONE_WILDCARD)); + a->destroy(a); + + /* Malformed IPv4 subnet encoding */ + a = identification_create_from_encoding(ID_IPV4_ADDR_SUBNET, chunk_empty); + ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "0.0.0.0-255.255.255.255", ID_MATCH_NONE)); + a->destroy(a); + + /* Malformed IPv6 subnet encoding */ + a = identification_create_from_encoding(ID_IPV6_ADDR_SUBNET, chunk_empty); + ck_assert(id_matches(a, "fec0::1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "::/0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ID_MATCH_NONE)); + a->destroy(a); +} +END_TEST + +START_TEST(test_matches_range_range) +{ + identification_t *a, *b; + + /* IPv4 ranges */ + a = identification_create_from_string("192.168.1.0-192.168.1.255"); + ck_assert(a->get_type(a) == ID_IPV4_ADDR_RANGE); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.1/32", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0/24", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.0.0/24", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.0.0/16", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0-192.168.1.255", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.0.0-192.168.255.255", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.0.240-192.168.1.0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE)); + + /* Malformed IPv4 subnet and range encoding */ + b = identification_create_from_encoding(ID_IPV4_ADDR_SUBNET, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV4_ADDR_RANGE, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV4_ADDR_RANGE, + chunk_from_chars(0xc0,0xa8,0x01,0x28,0xc0,0xa8,0x01,0x00)); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + a->destroy(a); + + a = identification_create_from_string("192.168.1.1-192.168.1.1"); + ck_assert(a->get_type(a) == ID_IPV4_ADDR_RANGE); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.2", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.1/32", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.0/32", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.0/24", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.0.0/24", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.0-192.168.1.0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "192.168.1.0-192.168.1.1", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.1.1-192.168.1.2", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "192.168.0.0-192.168.1.255", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE)); + a->destroy(a); + + /* IPv6 ranges */ + a = identification_create_from_string("fec0::-fec0::ffff:ffff:ffff:ffff"); + ck_assert(a->get_type(a) == ID_IPV6_ADDR_RANGE); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "::/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "fec0::1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::1/128", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::/128", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::/64", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::/48", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec1::/48", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::-fec0::1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::-fec0::ffff:ffff:ffff:ffff", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::-fec0::f:ffff:ffff:ffff:ffff", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec0::4001-fec0::4ffe", ID_MATCH_NONE)); + ck_assert(id_matches(a, "feb0::1-fec0::0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "foo@bar", ID_MATCH_NONE)); + + /* Malformed IPv6 subnet and range encoding */ + b = identification_create_from_encoding(ID_IPV6_ADDR_SUBNET, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV6_ADDR_RANGE, chunk_empty); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + b = identification_create_from_encoding(ID_IPV6_ADDR_RANGE, + chunk_from_chars(0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xff, + 0xfe,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 )); + ck_assert(a->matches(a, b) == ID_MATCH_NONE); + b->destroy(b); + a->destroy(a); + + a = identification_create_from_string("fec0::1-fec0::1"); + ck_assert(a->get_type(a) == ID_IPV6_ADDR_RANGE); + ck_assert(id_matches(a, "%any", ID_MATCH_ANY)); + ck_assert(id_matches(a, "::/0", ID_MATCH_MAX_WILDCARDS)); + ck_assert(id_matches(a, "fec0::1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::2", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::1/128", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::/128", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::/64", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec1::/48", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::0-fec0::0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "fec0::1-fec0::1", ID_MATCH_PERFECT)); + ck_assert(id_matches(a, "fec0::0-fec0::1", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec0::1-fec0::2", ID_MATCH_ONE_WILDCARD)); + ck_assert(id_matches(a, "fec0::-fec0::ffff:ffff:ffff:ffff", ID_MATCH_ONE_WILDCARD)); + a->destroy(a); + + /* Malformed IPv4 range encoding */ + a = identification_create_from_encoding(ID_IPV4_ADDR_RANGE, chunk_empty); + ck_assert(id_matches(a, "192.168.1.1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "0.0.0.0/0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "0.0.0.0-255.255.255.255", ID_MATCH_NONE)); + a->destroy(a); + + /* Malformed IPv6 range encoding */ + a = identification_create_from_encoding(ID_IPV6_ADDR_RANGE, chunk_empty); + ck_assert(id_matches(a, "fec0::1", ID_MATCH_NONE)); + ck_assert(id_matches(a, "::/0", ID_MATCH_NONE)); + ck_assert(id_matches(a, "::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ID_MATCH_NONE)); + a->destroy(a); +} +END_TEST + START_TEST(test_matches_string) { identification_t *a; @@ -1204,7 +1440,9 @@ Suite *identification_suite_create() tcase_add_loop_test(tc, test_matches_two_ou, 0, countof(rdn_matching)); tcase_add_test(tc, test_matches_any); tcase_add_test(tc, test_matches_binary); - tcase_add_test(tc, test_matches_range); + tcase_add_test(tc, test_matches_range_addr); + tcase_add_test(tc, test_matches_range_subnet); + tcase_add_test(tc, test_matches_range_range); tcase_add_test(tc, test_matches_string); tcase_add_loop_test(tc, test_matches_empty, ID_ANY, ID_KEY_ID + 1); tcase_add_loop_test(tc, test_matches_empty_reverse, ID_ANY, ID_KEY_ID + 1); diff --git a/src/libstrongswan/utils/identification.c b/src/libstrongswan/utils/identification.c index 9a8f3200e..d31955b38 100644 --- a/src/libstrongswan/utils/identification.c +++ b/src/libstrongswan/utils/identification.c @@ -1046,10 +1046,9 @@ METHOD(identification_t, matches_dn_relaxed, id_match_t, /** * Transform netmask to CIDR bits */ -static int netmask_to_cidr(char *netmask, size_t address_size) +static uint8_t netmask_to_cidr(char *netmask, uint8_t address_size) { - uint8_t byte; - int i, netbits = 0; + uint8_t i, byte, netbits = 0; for (i = 0; i < address_size; i++) { @@ -1075,118 +1074,369 @@ static int netmask_to_cidr(char *netmask, size_t address_size) return netbits; } +/** + * Converts the given network/netmask to an address range + */ +static void subnet_to_range(chunk_t encoding, uint8_t address_size, + uint8_t *from, uint8_t *to) +{ + uint8_t *network, *netmask, netbits, mask, i; + + network = encoding.ptr; + netmask = encoding.ptr + address_size; + netbits = netmask_to_cidr(netmask, address_size); + + memcpy(from, network, address_size); + memcpy(to, network, address_size); + + i = netbits / 8; + if (i < address_size) + { + mask = 0xff << (8 - netbits % 8); + from[i] = from[i] & mask; + to[i] = to[i] | ~mask; + memset(&from[i+1], 0, address_size - i - 1); + memset(&to[i+1], 0xff, address_size - i - 1); + } +} + +/** + * Matches one subnet (or address if netmask is NULL) against another + */ +static id_match_t match_subnets(uint8_t *network, uint8_t *netmask, + uint8_t *other_network, uint8_t *other_netmask, + uint8_t address_size) +{ + uint8_t netbits, other_netbits, i; + + other_netbits = other_netmask ? netmask_to_cidr(other_netmask, address_size) + : 8 * address_size; + if (!other_netbits) + { + return ID_MATCH_MAX_WILDCARDS; + } + + netbits = netmask ? netmask_to_cidr(netmask, address_size) : 8 * address_size; + + if (netbits == other_netbits) + { + return memeq(network, other_network, address_size) ? ID_MATCH_PERFECT + : ID_MATCH_NONE; + } + else if (netbits < other_netbits) + { + return ID_MATCH_NONE; + } + + for (i = 0; i < (other_netbits + 7)/8; i++) + { + if ((network[i] ^ other_network[i]) & other_netmask[i]) + { + return ID_MATCH_NONE; + } + } + return ID_MATCH_ONE_WILDCARD; +} + +/** + * Matches two address ranges against each other + */ +static id_match_t match_ranges(uint8_t *from, uint8_t *to, + uint8_t *other_from, uint8_t *other_to, + uint8_t address_size) +{ + const uint8_t zeroes[16] = { 0 }; + const uint8_t ones[16] = { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff }; + int match_from, match_to; + + if (memcmp(to, from, address_size) < 0 || + memcmp(other_to, other_from, address_size) < 0) + { + /* to is smaller than from in one of the ranges */ + return ID_MATCH_NONE; + } + else if (memeq(other_from, zeroes, address_size) && + memeq(other_to, ones, address_size)) + { + return ID_MATCH_MAX_WILDCARDS; + } + + match_from = memcmp(from, other_from, address_size); + match_to = memcmp(to, other_to, address_size); + + if (!match_from && !match_to) + { + return ID_MATCH_PERFECT; + } + else if (match_from >= 0 && match_to <= 0) + { + return ID_MATCH_ONE_WILDCARD; + } + return ID_MATCH_NONE; +} + +/** + * Match a subnet to an address + */ +static id_match_t matches_subnet_to_addr(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *network, *netmask, *address; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != 2 * address_size || + other_encoding.len != address_size) + { + return ID_MATCH_NONE; + } + network = this->encoded.ptr; + netmask = this->encoded.ptr + address_size; + address = other_encoding.ptr; + return match_subnets(network, netmask, address, NULL, address_size); +} + +/** + * Match a subnet to an address + */ +static id_match_t matches_range_to_addr(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *from, *to, *address; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != 2 * address_size || + other_encoding.len != address_size) + { + return ID_MATCH_NONE; + } + from = this->encoded.ptr; + to = this->encoded.ptr + address_size; + address = other_encoding.ptr; + return match_ranges(from, to, address, address, address_size); +} + +/** + * Matches an address to a subnet + */ +static id_match_t matches_addr_to_subnet(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *address, *network, *netmask; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != address_size || + other_encoding.len != 2 * address_size) + { + return ID_MATCH_NONE; + } + address = this->encoded.ptr; + network = other_encoding.ptr; + netmask = other_encoding.ptr + address_size; + return match_subnets(address, NULL, network, netmask, address_size); +} + +/** + * Matches a subnet to a subnet + */ +static id_match_t matches_subnet_to_subnet(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *network, *netmask, *other_network, *other_netmask; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != 2 * address_size || + other_encoding.len != 2 * address_size) + { + return ID_MATCH_NONE; + } + network = this->encoded.ptr; + netmask = this->encoded.ptr + address_size; + other_network = other_encoding.ptr; + other_netmask = other_encoding.ptr + address_size; + return match_subnets(network, netmask, other_network, other_netmask, + address_size); +} + +/** + * Matches a range to a subnet + */ +static id_match_t matches_range_to_subnet(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *from, *to, other_from[address_size], other_to[address_size]; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != 2 * address_size || + other_encoding.len != 2 * address_size) + { + return ID_MATCH_NONE; + } + + from = this->encoded.ptr; + to = this->encoded.ptr + address_size; + subnet_to_range(other_encoding, address_size, other_from, other_to); + return match_ranges(from, to, other_from, other_to, address_size); +} + +/** + * Match an address to a range + */ +static id_match_t matches_addr_to_range(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *address, *from, *to; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != address_size || + other_encoding.len != 2 * address_size) + { + return ID_MATCH_NONE; + } + address = this->encoded.ptr; + from = other_encoding.ptr; + to = other_encoding.ptr + address_size; + return match_ranges(address, address, from, to, address_size); +} + +/** + * Match a subnet to a range + */ +static id_match_t matches_subnet_to_range(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t from[address_size], to[address_size], *other_from, *other_to; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != 2 * address_size || + other_encoding.len != 2 * address_size) + { + return ID_MATCH_NONE; + } + subnet_to_range(this->encoded, address_size, from, to); + other_from = other_encoding.ptr; + other_to = other_encoding.ptr + address_size; + return match_ranges(from, to, other_from, other_to, address_size); +} + +/** + * Match a range to a range + */ +static id_match_t matches_range_to_range(private_identification_t *this, + identification_t *other, + uint8_t address_size) +{ + chunk_t other_encoding; + uint8_t *from, *to, *other_from, *other_to; + + other_encoding = other->get_encoding(other); + if (this->encoded.len != 2 * address_size || + other_encoding.len != 2 * address_size) + { + return ID_MATCH_NONE; + } + from = this->encoded.ptr; + to = this->encoded.ptr + address_size; + other_from = other_encoding.ptr; + other_to = other_encoding.ptr + address_size; + return match_ranges(from, to, other_from, other_to, address_size); +} + METHOD(identification_t, matches_range, id_match_t, private_identification_t *this, identification_t *other) { - chunk_t other_encoding; - uint8_t *address, *from, *to, *network, *netmask; - size_t address_size = 0; - int netbits, range_sign, i; + id_type_t other_type; + uint8_t address_size = 0; - if (other->get_type(other) == ID_ANY) + other_type = other->get_type(other); + if (other_type == ID_ANY) { return ID_MATCH_ANY; } - if (this->type == other->get_type(other) && + if (this->type == other_type && chunk_equals(this->encoded, other->get_encoding(other))) { return ID_MATCH_PERFECT; } - if ((this->type == ID_IPV4_ADDR && - other->get_type(other) == ID_IPV4_ADDR_SUBNET)) + if (other_type == ID_IPV4_ADDR && + (this->type == ID_IPV4_ADDR_SUBNET || this->type == ID_IPV4_ADDR_RANGE)) { address_size = sizeof(struct in_addr); } - else if ((this->type == ID_IPV6_ADDR && - other->get_type(other) == ID_IPV6_ADDR_SUBNET)) + else if (other_type == ID_IPV6_ADDR && + (this->type == ID_IPV6_ADDR_SUBNET || this->type == ID_IPV6_ADDR_RANGE)) { address_size = sizeof(struct in6_addr); } if (address_size) { - other_encoding = other->get_encoding(other); - if (this->encoded.len != address_size || - other_encoding.len != 2 * address_size) + if (this->type == ID_IPV4_ADDR_SUBNET || this->type == ID_IPV6_ADDR_SUBNET) { - return ID_MATCH_NONE; + return matches_subnet_to_addr(this, other, address_size); } - address = this->encoded.ptr; - network = other_encoding.ptr; - netmask = other_encoding.ptr + address_size; - netbits = netmask_to_cidr(netmask, address_size); - - if (netbits == 0) - { - return ID_MATCH_MAX_WILDCARDS; - } - if (netbits == 8 * address_size) - { - return memeq(address, network, address_size) ? - ID_MATCH_PERFECT : ID_MATCH_NONE; - } - for (i = 0; i < (netbits + 7)/8; i++) - { - if ((address[i] ^ network[i]) & netmask[i]) - { - return ID_MATCH_NONE; - } - } - return ID_MATCH_ONE_WILDCARD; + return matches_range_to_addr(this, other, address_size); } - if ((this->type == ID_IPV4_ADDR && - other->get_type(other) == ID_IPV4_ADDR_RANGE)) + if (other_type == ID_IPV4_ADDR_SUBNET && + (this->type == ID_IPV4_ADDR || this->type == ID_IPV4_ADDR_SUBNET || + this->type == ID_IPV4_ADDR_RANGE)) { address_size = sizeof(struct in_addr); } - else if ((this->type == ID_IPV6_ADDR && - other->get_type(other) == ID_IPV6_ADDR_RANGE)) + else if (other_type == ID_IPV6_ADDR_SUBNET && + (this->type == ID_IPV6_ADDR || this->type == ID_IPV6_ADDR_SUBNET || + this->type == ID_IPV6_ADDR_RANGE)) { address_size = sizeof(struct in6_addr); } if (address_size) { - other_encoding = other->get_encoding(other); - if (this->encoded.len != address_size || - other_encoding.len != 2 * address_size) + if (this->type == ID_IPV4_ADDR || this->type == ID_IPV6_ADDR) { - return ID_MATCH_NONE; + return matches_addr_to_subnet(this, other, address_size); } - address = this->encoded.ptr; - from = other_encoding.ptr; - to = other_encoding.ptr + address_size; - - range_sign = memcmp(to, from, address_size); - if (range_sign < 0) - { /* to is smaller than from */ - return ID_MATCH_NONE; - } - - /* check lower bound */ - for (i = 0; i < address_size; i++) + else if (this->type == ID_IPV4_ADDR_SUBNET || this->type == ID_IPV6_ADDR_SUBNET) { - if (address[i] != from[i]) - { - if (address[i] < from[i]) - { - return ID_MATCH_NONE; - } - break; - } + return matches_subnet_to_subnet(this, other, address_size); } - - /* check upper bound */ - for (i = 0; i < address_size; i++) + return matches_range_to_subnet(this, other, address_size); + } + if (other_type == ID_IPV4_ADDR_RANGE && + (this->type == ID_IPV4_ADDR || this->type == ID_IPV4_ADDR_SUBNET || + this->type == ID_IPV4_ADDR_RANGE)) + { + address_size = sizeof(struct in_addr); + } + else if (other_type == ID_IPV6_ADDR_RANGE && + (this->type == ID_IPV6_ADDR || this->type == ID_IPV6_ADDR_SUBNET || + this->type == ID_IPV6_ADDR_RANGE)) + { + address_size = sizeof(struct in6_addr); + } + if (address_size) + { + if (this->type == ID_IPV4_ADDR || this->type == ID_IPV6_ADDR) { - if (address[i] != to[i]) - { - if (address[i] > to[i]) - { - return ID_MATCH_NONE; - } - break; - } + return matches_addr_to_range(this, other, address_size); } - return range_sign ? ID_MATCH_ONE_WILDCARD : ID_MATCH_PERFECT; + else if (this->type == ID_IPV4_ADDR_SUBNET || this->type == ID_IPV6_ADDR_SUBNET) + { + return matches_subnet_to_range(this, other, address_size); + } + return matches_range_to_range(this, other, address_size); } return ID_MATCH_NONE; } @@ -1200,7 +1450,8 @@ int identification_printf_hook(printf_hook_data_t *data, private_identification_t *this = *((private_identification_t**)(args[0])); chunk_t proper; char buf[BUF_LEN], *pos; - size_t len, address_size; + uint8_t address_size; + size_t len; int written; if (this == NULL) @@ -1402,6 +1653,10 @@ static private_identification_t *identification_create(id_type_t type) break; case ID_IPV4_ADDR: case ID_IPV6_ADDR: + case ID_IPV4_ADDR_SUBNET: + case ID_IPV6_ADDR_SUBNET: + case ID_IPV4_ADDR_RANGE: + case ID_IPV6_ADDR_RANGE: this->public.hash = _hash_binary; this->public.equals = _equals_binary; this->public.matches = _matches_range;