]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
constraints: Properly validate name constraints according to RFC 5280
authorTobias Brunner <tobias@strongswan.org>
Fri, 23 Feb 2024 16:44:44 +0000 (17:44 +0100)
committerTobias Brunner <tobias@strongswan.org>
Tue, 12 Mar 2024 08:14:44 +0000 (09:14 +0100)
The previous code was in a way too simple which resulted in it being too
strict.  For instance, it enforced that intermediate CA certificates
inherited the name constraints of their parents.  That's not required by
RFC 5280 and prevented e.g. adding constraints in an intermediate CA
certificate that's followed by another that doesn't contain any
name constraints.  That's perfectly fine as the set of constraints
specified by the parent continue to apply to that CA certificate and
the children it issues.

Name constraints were previously also applied to all identities of a
matching type, which is way too strict except for some very simple
cases.  It basically prevented multiple constraints of the same type
as e.g. an intermediate CA certificate that has permitted name constraints
for example.org and example.com couldn't issue acceptable certificates
because any SAN with one domain would get rejected by the other
constraint.  According to RFC 5280 matching one constraint is enough.

Also fixed is an issue with name constraints for IP addresses which were
previously only supported for a single level.

src/libstrongswan/plugins/constraints/constraints_validator.c
src/libstrongswan/tests/suites/test_certnames.c

index b1f60fb156037aeb720ad2c66358deb858ab26fb..27bdb8943cf3e185b151ec785126320c77c9f350 100644 (file)
@@ -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 <utils/debug.h>
 #include <asn1/asn1.h>
+#include <collections/array.h>
+#include <collections/hashtable.h>
 #include <collections/linked_list.h>
 #include <credentials/certificates/x509.h>
 
@@ -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 the given identity matches any of the given name constraints
+ */
+static bool id_matches_constraints(certificate_t *cert, identification_t *id,
+                                                                  array_t *constraints, bool permitted)
+{
+       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 to a NameConstraint
+ * Check if a certificate matches the given permitted/excluded name constraints
  */
-static bool name_constraint_matches(identification_t *constraint,
-                                                                       certificate_t *cert, bool permitted)
+static bool cert_matches_constraints(x509_t *x509, hashtable_t *types,
+                                                                        bool permitted)
 {
-       x509_t *x509 = (x509_t*)cert;
+       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_constraints_match(x509_t *x509, hashtable_t *permitted,
+                                                                  hashtable_t *excluded)
+{
+       if (permitted && !cert_matches_constraints(x509, permitted, TRUE))
+       {
+               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 name_constraint_inherited(identification_t *constraint,
-                                                                         x509_t *x509, bool permitted)
+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 *id, *a, *b;
-       bool inherited = FALSE;
+       identification_t *constraint;
+       array_t *constraints;
        id_type_t type;
+       bool success = TRUE;
 
-       if (!(x509->get_flags(x509) & X509_CA))
-       {       /* not a sub-CA, not required */
-               return TRUE;
-       }
+       collected = hashtable_create(id_type_hash, id_type_equals, 8);
 
-       type = constraint->get_type(constraint);
        enumerator = x509->create_name_constraint_enumerator(x509, permitted);
-       while (enumerator->enumerate(enumerator, &id))
+       while (enumerator->enumerate(enumerator, &constraint))
+       {
+               type = constraint->get_type(constraint);
+               switch (type)
+               {
+                       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 (!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);
+
+       if (success)
+       {
+               *out = collected;
+       }
+       else
        {
-               if (id->get_type(id) == type)
+               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))
                {
-                       if (permitted)
-                       {       /* permitted constraint can be narrowed */
-                               a = constraint;
-                               b = id;
+                       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);
+                               }
                        }
-                       else
-                       {       /* excluded constraint can be widened */
-                               a = id;
-                               b = constraint;
+                       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);
                        }
-                       switch (type)
+                       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))
                        {
-                               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;
+                               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);
                }
-               if (inherited)
+               enumerator->destroy(enumerator);
+
+               /* add all new constraints to the list */
+               while (array_remove(new_constraints, ARRAY_HEAD, &new_constraint))
                {
-                       break;
+                       array_insert(existing_constraints, ARRAY_TAIL, new_constraint);
                }
        }
-       enumerator->destroy(enumerator);
-       return inherited;
 }
 
 /**
- * Check name constraints
+ * Update the set of permitted/excluded name constraints
  */
-static bool check_name_constraints(certificate_t *subject, x509_t *issuer)
+static bool update_name_constraints(x509_t *x509, hashtable_t **existing,
+                                                                       bool permitted)
 {
        enumerator_t *enumerator;
-       identification_t *constraint;
+       hashtable_t *collected;
+       array_t *existing_constraints, *new_constraints;
+       void *type;
 
-       enumerator = issuer->create_name_constraint_enumerator(issuer, TRUE);
-       while (enumerator->enumerate(enumerator, &constraint))
+       if (!(x509->get_flags(x509) & X509_CA))
+       {
+               /* ignore end-entity certificates */
+               return TRUE;
+       }
+
+       if (!collect_constraints(x509, permitted, &collected))
        {
-               if (!name_constraint_matches(constraint, subject, TRUE))
+               return FALSE;
+       }
+       if (collected->get_count(collected))
+       {
+               if (!*existing)
                {
-                       DBG1(DBG_CFG, "certificate '%Y' does not match permitted name "
-                                "constraint '%Y'", subject->get_subject(subject), constraint);
-                       enumerator->destroy(enumerator);
-                       return FALSE;
+                       /* adopt all constraints if we haven't any yet */
+                       *existing = collected;
+                       collected = NULL;
                }
-               if (!name_constraint_inherited(constraint, (x509_t*)subject, TRUE))
+               else
                {
-                       DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit permitted name "
-                                "constraint '%Y'", subject->get_subject(subject), constraint);
+                       /* 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);
-                       return FALSE;
                }
        }
-       enumerator->destroy(enumerator);
+       DESTROY_FUNCTION_IF(collected, destroy_constraints);
+       return TRUE;
+}
 
-       enumerator = issuer->create_name_constraint_enumerator(issuer, FALSE);
-       while (enumerator->enumerate(enumerator, &constraint))
+/**
+ * Check name constraints
+ */
+static bool check_name_constraints(x509_t *issuer, u_int pathlen,
+                                                                  auth_cfg_t *auth, certificate_t **violator)
+{
+       enumerator_t *enumerator;
+       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;
+
+       subject = auth->get(auth, AUTH_RULE_SUBJECT_CERT);
+       if (!subject || subject->get_type(subject) != CERT_X509)
+       {
+               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 (name_constraint_matches(constraint, subject, FALSE))
+               if (rule == AUTH_RULE_IM_CERT &&
+                       cert->get_type(cert) == CERT_X509)
                {
-                       DBG1(DBG_CFG, "certificate '%Y' matches excluded name "
-                                "constraint '%Y'", subject->get_subject(subject), constraint);
-                       enumerator->destroy(enumerator);
-                       return FALSE;
+                       chain->insert_first(chain, cert);
                }
-               if (!name_constraint_inherited(constraint, (x509_t*)subject, FALSE))
+       }
+       enumerator->destroy(enumerator);
+       chain->insert_first(chain, issuer);
+
+       enumerator = chain->create_enumerator(chain);
+       while (enumerator->enumerate(enumerator, &x509))
+       {
+               if ((len > 0 && !name_constraints_match(x509, permitted, excluded)) ||
+                       !update_name_constraints(x509, &permitted, TRUE) ||
+                       !update_name_constraints(x509, &excluded, 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,
index 36729123657b2111ee23560118dea47504ad8271..2549fb6e33431dd3ef1cafaf7af9fe6e5d5ab5fa 100644 (file)
@@ -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,22 +84,7 @@ static certificate_t* create_cert(certificate_t *ca, char *subject, char *san,
        ck_assert(privkey);
        pubkey = privkey->get_public_key(privkey);
        ck_assert(pubkey);
-       plist = linked_list_create();
-       if (permitted)
-       {
-               plist->insert_last(plist, permitted);
-       }
-       elist = linked_list_create();
-       if (excluded)
-       {
-               elist->insert_last(elist, excluded);
-       }
-       sans = linked_list_create();
-       if (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,
@@ -108,20 +93,49 @@ static certificate_t* create_cert(certificate_t *ca, char *subject, char *san,
                                                BUILD_X509_FLAG, flags,
                                                BUILD_SIGNING_CERT, ca,
                                                BUILD_SUBJECT_ALTNAMES, sans,
-                                               BUILD_PERMITTED_NAME_CONSTRAINTS, plist,
-                                               BUILD_EXCLUDED_NAME_CONSTRAINTS, elist,
+                                               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));
-       plist->destroy_offset(plist, offsetof(identification_t, destroy));
-       elist->destroy_offset(elist, 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)
+       {
+               plist->insert_last(plist, permitted);
+       }
+       elist = linked_list_create();
+       if (excluded)
+       {
+               elist->insert_last(elist, excluded);
+       }
+       sans = linked_list_create();
+       if (san)
+       {
+               id = identification_create_from_string(san);
+               sans->insert_last(sans, id);
+       }
+       return create_cert_lists(ca, subject, sans, flags, plist, elist);
+}
+
 /**
  * Check if a certificate with given subject has a valid trustchain
  */
@@ -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,99 @@ 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_dn_levels[_i].good);
+}
+END_TEST
+
+static struct {
+       char *caconst;
+       char *imconst;
+       char *subject;
+       bool good;
+} 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_permitted_san_levels)
+{
+       certificate_t *ca, *im, *sj;
+       identification_t *id;
+
+       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)) == permitted_dninh[_i].good);
+       ck_assert(check_trust(sj->get_subject(sj)) == permitted_san_levels[_i].good);
 }
 END_TEST
 
@@ -333,28 +417,247 @@ static struct {
        char *imconst;
        char *subject;
        bool good;
-} excluded_dninh[] = {
+} excluded_dn_levels[] = {
        { "C=CH, O=strongSwan", "C=CH", "C=DE", TRUE },
-       { "C=CH, O=strongSwan", "C=DE", "C=CH", FALSE },
+       { "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_dninh)
+START_TEST(test_excluded_dn_levels)
 {
        certificate_t *ca, *im, *sj;
        identification_t *id;
 
-       id = identification_create_from_string(excluded_dninh[_i].caconst);
+       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 = identification_create_from_string(excluded_dninh[_i].imconst);
+       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_dninh[_i].subject, NULL, 0, NULL, NULL);
+       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_dninh[_i].good);
+       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_dn_levels, 0, countof(permitted_dn_levels));
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("permitted subjectAltName constraints multilevel");
+       tcase_add_checked_fixture(tc, setup, teardown);
+       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_dninh, 0, countof(permitted_dninh));
+       tcase_add_loop_test(tc, test_permitted_san_multi, 0, countof(permitted_san_multi));
        suite_add_tcase(s, tc);
 
-       tc = tcase_create("excluded DN name constraint inherit");
+       tc = tcase_create("excluded subjectAltName constraints multivalue");
        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_excluded_san_multi, 0, countof(excluded_san_multi));
        suite_add_tcase(s, tc);
 
        return s;