Section to configure the number of reserved threads per priority class
see JOB PRIORITY MANAGEMENT in **strongswan.conf**(5).
+charon.rdn_matching = strict
+ How RDNs in subject DNs of certificates are matched against configured
+ identities (_strict_, _reordered_, or _relaxed_).
+
+ How RDNs in subject DNs of certificates are matched against configured
+ identities. Possible values are _strict_ (the default), _reordered_, and
+ _relaxed_. With _strict_ the number, type and order of all RDNs has to
+ match, wildcards (*) for the values of RDNs are allowed (that's the case
+ for all three variants). Using _reordered_ also matches DNs if the RDNs
+ appear in a different order, the number and type still has to match.
+ Finally, _relaxed_ also allows matches of DNs that contain more RDNs than
+ the configured identity (missing RDNs are treated like a wildcard match).
+
+ Note that _reordered_ and _relaxed_ impose a considerable overhead on memory
+ usage and runtime, in particular, for mismatches, compared to _static_.
+
charon.receive_delay = 0
Delay in ms for receiving packets, to simulate larger RTT.
return match == expected;
}
+static char* rdn_matching[] = { NULL, "reordered", "relaxed" };
+
+static struct {
+ char *id;
+ id_match_t match[3];
+} matches_data[] = {
+ /* C=CH, E=moon@strongswan.org, CN=moon */
+ { "C=CH, E=moon@strongswan.org, CN=moon", {
+ ID_MATCH_PERFECT, ID_MATCH_PERFECT, ID_MATCH_PERFECT }},
+ { "C=CH, email=moon@strongswan.org, CN=moon", {
+ ID_MATCH_PERFECT, ID_MATCH_PERFECT, ID_MATCH_PERFECT }},
+ { "C=CH, emailAddress=moon@strongswan.org, CN=moon", {
+ ID_MATCH_PERFECT, ID_MATCH_PERFECT, ID_MATCH_PERFECT }},
+ { "CN=moon, C=CH, E=moon@strongswan.org", {
+ ID_MATCH_NONE, ID_MATCH_PERFECT, ID_MATCH_PERFECT }},
+ { "C=CH, E=*@strongswan.org, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_NONE }},
+ { "C=CH, E=*, CN=moon", {
+ ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD }},
+ { "C=CH, E=*, CN=*", {
+ ID_MATCH_ONE_WILDCARD - 1, ID_MATCH_ONE_WILDCARD - 1, ID_MATCH_ONE_WILDCARD - 1 }},
+ { "C=*, E=*, CN=*", {
+ ID_MATCH_ONE_WILDCARD - 2, ID_MATCH_ONE_WILDCARD - 2, ID_MATCH_ONE_WILDCARD - 2 }},
+ { "C=*, E=*, CN=*, O=BADInc", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_NONE }},
+ { "C=CH, CN=*", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD - 1 }},
+ { "C=*, E=*", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD - 2 }},
+ { "C=*, E=a@b.c, CN=*", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_NONE }},
+ { "C=CH, O=strongSwan, E=*, CN=*", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_NONE }},
+ { "", {
+ ID_MATCH_ANY, ID_MATCH_ANY, ID_MATCH_ANY }},
+ { "%any", {
+ ID_MATCH_ANY, ID_MATCH_ANY, ID_MATCH_ANY }},
+};
+
START_TEST(test_matches)
{
identification_t *a;
+ int i;
+
+ if (rdn_matching[_i])
+ {
+ lib->settings->set_str(lib->settings, "%s.rdn_matching",
+ rdn_matching[_i], lib->ns);
+ }
a = identification_create_from_string("C=CH, E=moon@strongswan.org, CN=moon");
- ck_assert(id_matches(a, "C=CH, E=moon@strongswan.org, CN=moon", ID_MATCH_PERFECT));
- ck_assert(id_matches(a, "C=CH, email=moon@strongswan.org, CN=moon", ID_MATCH_PERFECT));
- ck_assert(id_matches(a, "C=CH, emailAddress=moon@strongswan.org, CN=moon", ID_MATCH_PERFECT));
- ck_assert(id_matches(a, "C=CH, E=*@strongswan.org, CN=moon", ID_MATCH_NONE));
- ck_assert(id_matches(a, "C=CH, E=*, CN=moon", ID_MATCH_ONE_WILDCARD));
- ck_assert(id_matches(a, "C=CH, E=*, CN=*", ID_MATCH_ONE_WILDCARD - 1));
- ck_assert(id_matches(a, "C=*, E=*, CN=*", ID_MATCH_ONE_WILDCARD - 2));
- ck_assert(id_matches(a, "C=*, E=*, CN=*, O=BADInc", ID_MATCH_NONE));
- ck_assert(id_matches(a, "C=*, E=*", ID_MATCH_NONE));
- ck_assert(id_matches(a, "C=*, E=a@b.c, CN=*", ID_MATCH_NONE));
- ck_assert(id_matches(a, "%any", ID_MATCH_ANY));
+ for (i = 0; i < countof(matches_data); i++)
+ {
+ ck_assert(id_matches(a, matches_data[i].id, matches_data[i].match[_i]));
+ }
+
+ a->destroy(a);
+}
+END_TEST
+
+static struct {
+ char *id;
+ id_match_t match[3];
+} matches_two_ou_data[] = {
+ /* C=CH, OU=Research, OU=Floor A, CN=moon */
+ { "C=CH, OU=Research, OU=Floor A, CN=moon", {
+ ID_MATCH_PERFECT, ID_MATCH_PERFECT, ID_MATCH_PERFECT }},
+ { "C=CH, OU=Floor A, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD }},
+ { "C=CH, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD - 1 }},
+ { "C=CH, OU=*, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD - 1 }},
+ { "C=CH, OU=*, OU=*, CN=moon", {
+ ID_MATCH_ONE_WILDCARD - 1, ID_MATCH_ONE_WILDCARD - 1, ID_MATCH_ONE_WILDCARD - 1 }},
+ { "C=CH, OU=Research, OU=*, CN=moon", {
+ ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD }},
+ { "C=CH, OU=*, OU=Floor A, CN=moon", {
+ ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD }},
+ { "C=CH, OU=*, OU=Research, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD }},
+ { "C=CH, OU=Floor A, OU=*, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_ONE_WILDCARD, ID_MATCH_ONE_WILDCARD }},
+ { "C=CH, OU=Floor A, OU=Research, CN=moon", {
+ ID_MATCH_NONE, ID_MATCH_PERFECT, ID_MATCH_PERFECT }},
+};
+
+START_TEST(test_matches_two_ou)
+{
+ identification_t *a;
+ int i;
+
+ if (rdn_matching[_i])
+ {
+ lib->settings->set_str(lib->settings, "%s.rdn_matching",
+ rdn_matching[_i], lib->ns);
+ }
+
+ a = identification_create_from_string("C=CH, OU=Research, OU=Floor A, CN=moon");
+
+ for (i = 0; i < countof(matches_two_ou_data); i++)
+ {
+ ck_assert(id_matches(a, matches_two_ou_data[i].id, matches_two_ou_data[i].match[_i]));
+ }
a->destroy(a);
}
suite_add_tcase(s, tc);
tc = tcase_create("matches");
- tcase_add_test(tc, test_matches);
+ tcase_add_loop_test(tc, test_matches, 0, countof(rdn_matching));
+ 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);
/*
* Copyright (C) 2016 Andreas Steffen
- * Copyright (C) 2009-2015 Tobias Brunner
+ * Copyright (C) 2009-2019 Tobias Brunner
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter
* HSR Hochschule fuer Technik Rapperswil
#include <asn1/oid.h>
#include <asn1/asn1.h>
#include <crypto/hashers/hasher.h>
+#include <collections/array.h>
ENUM_BEGIN(id_match_names, ID_MATCH_NONE, ID_MATCH_MAX_WILDCARDS,
"MATCH_NONE",
return this->type;
}
+/**
+ * Check if this is a wildcard value
+ */
+static inline bool is_wildcard(chunk_t data)
+{
+ return data.len == 1 && data.ptr[0] == '*';
+}
+
METHOD(identification_t, contains_wildcards_dn, bool,
private_identification_t *this)
{
enumerator = create_part_enumerator(this);
while (enumerator->enumerate(enumerator, &type, &data))
{
- if (data.len == 1 && data.ptr[0] == '*')
+ if (is_wildcard(data))
{
contains = TRUE;
break;
}
/**
- * Compare to DNs, for equality if wc == NULL, for match otherwise
+ * Compare two RDNs for equality, comparing some string types case insensitive
+ */
+static bool rdn_equals(chunk_t oid, u_char a_type, chunk_t a, u_char b_type,
+ chunk_t b)
+{
+ if (a_type == b_type &&
+ (a_type == ASN1_PRINTABLESTRING ||
+ (a_type == ASN1_IA5STRING &&
+ asn1_known_oid(oid) == OID_EMAIL_ADDRESS)))
+ { /* ignore case for printableStrings and email RDNs */
+ return strncaseeq(a.ptr, b.ptr, a.len);
+ }
+ else
+ { /* respect case and length for everything else */
+ return memeq(a.ptr, b.ptr, a.len);
+ }
+}
+
+/**
+ * RDNs when matching DNs
+ */
+typedef struct {
+ chunk_t oid;
+ u_char type;
+ chunk_t data;
+ bool matched;
+} rdn_t;
+
+/**
+ * Match DNs (o_dn may contain wildcards and RDNs in a different order, if
+ * allow_unmatched is TRUE, t_dn may contain unmatched RDNs)
+ */
+static bool match_dn(chunk_t t_dn, chunk_t o_dn, int *wc, bool allow_unmatched)
+{
+ enumerator_t *enumerator;
+ array_t *rdns;
+ rdn_t *rdn, *found;
+ chunk_t oid, data;
+ u_char type;
+ bool finished = FALSE;
+ int i, regular = 0;
+
+ *wc = 0;
+
+ /* try a binary compare */
+ if (chunk_equals(t_dn, o_dn))
+ {
+ return TRUE;
+ }
+
+ rdns = array_create(0, 8);
+
+ enumerator = create_rdn_enumerator(o_dn);
+ while (TRUE)
+ {
+ if (!enumerator->enumerate(enumerator, &oid, &type, &data))
+ {
+ break;
+ }
+ INIT(rdn,
+ .oid = oid,
+ .type = type,
+ .data = data,
+ );
+ if (is_wildcard(data))
+ {
+ /* insert wildcards at the end, to perform exact matches first */
+ array_insert(rdns, ARRAY_TAIL, rdn);
+ }
+ else
+ {
+ array_insert(rdns, regular++, rdn);
+ }
+ /* the enumerator returns FALSE on parse error, we are finished
+ * if we have reached the end of the DN only */
+ if ((data.ptr + data.len == o_dn.ptr + o_dn.len))
+ {
+ finished = TRUE;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ if (!finished)
+ { /* invalid DN */
+ array_destroy_function(rdns, (void*)free, NULL);
+ return FALSE;
+ }
+ finished = FALSE;
+
+ enumerator = create_rdn_enumerator(t_dn);
+ while (TRUE)
+ {
+ if (!enumerator->enumerate(enumerator, &oid, &type, &data))
+ {
+ break;
+ }
+ for (i = 0, found = NULL; i < array_count(rdns); i++)
+ {
+ array_get(rdns, i, &rdn);
+ if (!rdn->matched && chunk_equals(rdn->oid, oid))
+ {
+ if (is_wildcard(rdn->data))
+ {
+ (*wc)++;
+ }
+ else if (data.len != rdn->data.len ||
+ !rdn_equals(oid, type, data, rdn->type, rdn->data))
+ {
+ continue;
+ }
+ rdn->matched = TRUE;
+ found = rdn;
+ break;
+ }
+ }
+ if (!found)
+ {
+ /* treat unmatched RDNs like wildcards if allowed */
+ if (!allow_unmatched)
+ {
+ break;
+ }
+ (*wc)++;
+ }
+ /* the enumerator returns FALSE on parse error, we are finished
+ * if we have reached the end of the DN only */
+ if ((data.ptr + data.len == t_dn.ptr + t_dn.len))
+ {
+ finished = TRUE;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ if (finished)
+ {
+ for (i = 0; i < array_count(rdns); i++)
+ {
+ array_get(rdns, i, &rdn);
+ if (!rdn->matched)
+ {
+ finished = FALSE;
+ }
+ }
+ }
+ array_destroy_function(rdns, (void*)free, NULL);
+ return finished;
+}
+
+/**
+ * Reordered RDNs are fine, but match all
+ */
+static bool match_dn_reordered(chunk_t t_dn, chunk_t o_dn, int *wc)
+{
+ return match_dn(t_dn, o_dn, wc, FALSE);
+}
+
+/**
+ * t_dn may contain more RDNs than o_dn
+ */
+static bool match_dn_relaxed(chunk_t t_dn, chunk_t o_dn, int *wc)
+{
+ return match_dn(t_dn, o_dn, wc, TRUE);
+}
+
+/**
+ * Compare two DNs, for equality if wc == NULL, with wildcard matching otherwise
*/
static bool compare_dn(chunk_t t_dn, chunk_t o_dn, int *wc)
{
{
*wc = 0;
}
- else
+ else if (t_dn.len != o_dn.len)
{
- if (t_dn.len != o_dn.len)
- {
- return FALSE;
- }
+ return FALSE;
}
- /* try a binary compare */
+
if (chunk_equals(t_dn, o_dn))
{
return TRUE;
{
break;
}
- if (wc && o_data.len == 1 && o_data.ptr[0] == '*')
+ if (wc && is_wildcard(o_data))
{
(*wc)++;
}
{
break;
}
- if (t_type == o_type &&
- (t_type == ASN1_PRINTABLESTRING ||
- (t_type == ASN1_IA5STRING &&
- asn1_known_oid(t_oid) == OID_EMAIL_ADDRESS)))
- { /* ignore case for printableStrings and email RDNs */
- if (strncasecmp(t_data.ptr, o_data.ptr, t_data.len) != 0)
- {
- break;
- }
- }
- else
- { /* respect case and length for everything else */
- if (!memeq(t_data.ptr, o_data.ptr, t_data.len))
- {
- break;
- }
+ if (!rdn_equals(t_oid, t_type, t_data, o_type, o_data))
+ {
+ break;
}
}
/* the enumerator returns FALSE on parse error, we are finished
return ID_MATCH_NONE;
}
-METHOD(identification_t, matches_dn, id_match_t,
- private_identification_t *this, identification_t *other)
+/**
+ * Match DNs given the matching function
+ */
+static id_match_t matches_dn_internal(private_identification_t *this,
+ identification_t *other,
+ bool (*match)(chunk_t,chunk_t,int*))
{
int wc;
if (this->type == other->get_type(other))
{
- if (compare_dn(this->encoded, other->get_encoding(other), &wc))
+ if (match(this->encoded, other->get_encoding(other), &wc))
{
wc = min(wc, ID_MATCH_ONE_WILDCARD - ID_MATCH_MAX_WILDCARDS);
return ID_MATCH_PERFECT - wc;
return ID_MATCH_NONE;
}
+METHOD(identification_t, matches_dn, id_match_t,
+ private_identification_t *this, identification_t *other)
+{
+ return matches_dn_internal(this, other, compare_dn);
+}
+
+METHOD(identification_t, matches_dn_reordered, id_match_t,
+ private_identification_t *this, identification_t *other)
+{
+ return matches_dn_internal(this, other, match_dn_reordered);
+}
+
+METHOD(identification_t, matches_dn_relaxed, id_match_t,
+ private_identification_t *this, identification_t *other)
+{
+ return matches_dn_internal(this, other, match_dn_relaxed);
+}
+
/**
* Transform netmask to CIDR bits
*/
static private_identification_t *identification_create(id_type_t type)
{
private_identification_t *this;
+ char *rdn_matching;
INIT(this,
.public = {
this->public.equals = _equals_dn;
this->public.matches = _matches_dn;
this->public.contains_wildcards = _contains_wildcards_dn;
+ /* check for more relaxed matching config */
+ rdn_matching = lib->settings->get_str(lib->settings,
+ "%s.rdn_matching", NULL, lib->ns);
+ if (streq("reordered", rdn_matching))
+ {
+ this->public.matches = _matches_dn_reordered;
+ }
+ else if (streq("relaxed", rdn_matching))
+ {
+ this->public.matches = _matches_dn_relaxed;
+ }
break;
case ID_IPV4_ADDR:
case ID_IPV6_ADDR: