Merge branch 'ca-identity-constraint'

This adds a new constraint for vici/swanctl.conf that enforces that the
certificate chain of the remote peer contains a CA certificate with a
specific identity.

This is similar to the existing CA constraints, but doesn't require that
the CA certificate is locally installed, for instance, intermediate CA
certificates received by the peers.

Wildcard identity matching (e.g. "..., OU=Research, CN=*") could also be
used for the latter, but requires trust in the intermediate CA to only
issue certificates with legitimate subject DNs (e.g. the "Sales" CA must
not issue certificates with "OU=Research").  With the new constraint
that's not necessary as long as a path length constraint prevents
intermediate CAs from issuing further intermediate CAs.
This commit is contained in:
Tobias Brunner 2019-12-06 10:10:39 +01:00
commit 96b8fa72b3
7 changed files with 75 additions and 9 deletions

View File

@ -347,6 +347,7 @@ static void log_auth(auth_cfg_t *auth)
union {
uintptr_t u;
identification_t *id;
certificate_t *cert;
char *str;
} v;
@ -373,6 +374,9 @@ static void log_auth(auth_cfg_t *auth)
case AUTH_RULE_IDENTITY:
DBG2(DBG_CFG, " id = %Y", v.id);
break;
case AUTH_RULE_CA_IDENTITY:
DBG2(DBG_CFG, " ca_id = %Y", v.id);
break;
case AUTH_RULE_AAA_IDENTITY:
DBG2(DBG_CFG, " aaa_id = %Y", v.id);
break;
@ -385,6 +389,12 @@ static void log_auth(auth_cfg_t *auth)
case AUTH_RULE_GROUP:
DBG2(DBG_CFG, " group = %Y", v.id);
break;
case AUTH_RULE_SUBJECT_CERT:
DBG2(DBG_CFG, " cert = %Y", v.cert->get_subject(v.cert));
break;
case AUTH_RULE_CA_CERT:
DBG2(DBG_CFG, " cacert = %Y", v.cert->get_subject(v.cert));
break;
default:
break;
}
@ -1360,6 +1370,15 @@ CALLBACK(parse_ike_id, bool,
return parse_id(cfg, AUTH_RULE_IDENTITY, v);
}
/**
* Parse CA identity constraint
*/
CALLBACK(parse_ca_id, bool,
auth_cfg_t *cfg, chunk_t v)
{
return parse_id(cfg, AUTH_RULE_CA_IDENTITY, v);
}
/**
* Parse AAA identity
*/
@ -1755,6 +1774,7 @@ CALLBACK(auth_kv, bool,
parse_rule_t rules[] = {
{ "auth", parse_auth, auth->cfg },
{ "id", parse_ike_id, auth->cfg },
{ "ca_id", parse_ca_id, auth->cfg },
{ "aaa_id", parse_aaa_id, auth->cfg },
{ "eap_id", parse_eap_id, auth->cfg },
{ "xauth_id", parse_xauth_id, auth->cfg },

View File

@ -765,6 +765,9 @@ static void build_auth_cfgs(peer_cfg_t *peer_cfg, bool local, vici_builder_t *b)
case AUTH_RULE_IDENTITY:
b->add_kv(b, "id", "%Y", v.id);
break;
case AUTH_RULE_CA_IDENTITY:
b->add_kv(b, "ca_id", "%Y", v.id);
break;
case AUTH_RULE_AAA_IDENTITY:
b->add_kv(b, "aaa_id", "%Y", v.id);
break;

View File

@ -42,6 +42,7 @@ ENUM(auth_rule_names, AUTH_RULE_IDENTITY, AUTH_HELPER_AC_CERT,
"RULE_EAP_VENDOR",
"RULE_XAUTH_BACKEND",
"RULE_XAUTH_IDENTITY",
"AUTH_RULE_CA_IDENTITY",
"RULE_CA_CERT",
"RULE_IM_CERT",
"RULE_SUBJECT_CERT",
@ -88,6 +89,7 @@ static inline bool is_multi_value_rule(auth_rule_t type)
case AUTH_RULE_CRL_VALIDATION:
case AUTH_RULE_GROUP:
case AUTH_RULE_SUBJECT_CERT:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_CA_CERT:
case AUTH_RULE_IM_CERT:
case AUTH_RULE_CERT_POLICY:
@ -226,6 +228,7 @@ static void init_entry(entry_t *this, auth_rule_t type, va_list args)
case AUTH_RULE_XAUTH_BACKEND:
case AUTH_RULE_XAUTH_IDENTITY:
case AUTH_RULE_GROUP:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_CA_CERT:
case AUTH_RULE_IM_CERT:
case AUTH_RULE_SUBJECT_CERT:
@ -287,6 +290,7 @@ static bool entry_equals(entry_t *e1, entry_t *e2)
return c1->equals(c1, c2);
}
case AUTH_RULE_IDENTITY:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_EAP_IDENTITY:
case AUTH_RULE_AAA_IDENTITY:
case AUTH_RULE_XAUTH_IDENTITY:
@ -325,6 +329,7 @@ static void destroy_entry_value(entry_t *entry)
switch (entry->type)
{
case AUTH_RULE_IDENTITY:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_EAP_IDENTITY:
case AUTH_RULE_AAA_IDENTITY:
case AUTH_RULE_GROUP:
@ -406,6 +411,7 @@ static void replace(private_auth_cfg_t *this, entry_enumerator_t *enumerator,
entry->value = (void*)(uintptr_t)va_arg(args, u_int);
break;
case AUTH_RULE_IDENTITY:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_EAP_IDENTITY:
case AUTH_RULE_AAA_IDENTITY:
case AUTH_RULE_XAUTH_BACKEND:
@ -486,6 +492,7 @@ METHOD(auth_cfg_t, get, void*,
case AUTH_RULE_CERT_VALIDATION_SUSPENDED:
return (void*)FALSE;
case AUTH_RULE_IDENTITY:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_EAP_IDENTITY:
case AUTH_RULE_AAA_IDENTITY:
case AUTH_RULE_XAUTH_BACKEND:
@ -808,8 +815,8 @@ METHOD(auth_cfg_t, complies, bool,
enumerator_t *e1, *e2;
bool success = TRUE, group_match = FALSE;
bool ca_match = FALSE, cert_match = FALSE;
identification_t *require_group = NULL;
certificate_t *require_ca = NULL, *require_cert = NULL;
identification_t *require_group = NULL, *require_ca = NULL;
certificate_t *require_cert = NULL;
signature_params_t *ike_scheme = NULL, *scheme = NULL;
u_int strength = 0;
auth_rule_t t1, t2;
@ -824,16 +831,35 @@ METHOD(auth_cfg_t, complies, bool,
case AUTH_RULE_CA_CERT:
case AUTH_RULE_IM_CERT:
{
certificate_t *cert;
certificate_t *cert, *ca;
/* for CA certs, a match of a single cert is sufficient */
require_ca = (certificate_t*)value;
ca = (certificate_t*)value;
require_ca = ca->get_subject(ca);
e2 = create_enumerator(this);
while (e2->enumerate(e2, &t2, &cert))
{
if ((t2 == AUTH_RULE_CA_CERT || t2 == AUTH_RULE_IM_CERT) &&
cert->equals(cert, require_ca))
cert->equals(cert, ca))
{
ca_match = TRUE;
}
}
e2->destroy(e2);
break;
}
case AUTH_RULE_CA_IDENTITY:
{
certificate_t *cert;
require_ca = (identification_t*)value;
e2 = create_enumerator(this);
while (e2->enumerate(e2, &t2, &cert))
{
if ((t2 == AUTH_RULE_CA_CERT || t2 == AUTH_RULE_IM_CERT) &&
cert->has_subject(cert, require_ca))
{
ca_match = TRUE;
}
@ -1138,8 +1164,7 @@ METHOD(auth_cfg_t, complies, bool,
if (log_error)
{
DBG1(DBG_CFG, "constraint check failed: peer not "
"authenticated by CA '%Y'",
require_ca->get_subject(require_ca));
"authenticated by CA '%Y'", require_ca);
}
return FALSE;
}
@ -1205,6 +1230,7 @@ static void merge(private_auth_cfg_t *this, private_auth_cfg_t *other, bool copy
break;
}
case AUTH_RULE_IDENTITY:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_EAP_IDENTITY:
case AUTH_RULE_AAA_IDENTITY:
case AUTH_RULE_GROUP:
@ -1337,6 +1363,7 @@ METHOD(auth_cfg_t, clone_, auth_cfg_t*,
switch (type)
{
case AUTH_RULE_IDENTITY:
case AUTH_RULE_CA_IDENTITY:
case AUTH_RULE_EAP_IDENTITY:
case AUTH_RULE_AAA_IDENTITY:
case AUTH_RULE_GROUP:

View File

@ -84,6 +84,8 @@ enum auth_rule_t {
AUTH_RULE_XAUTH_BACKEND,
/** XAuth identity to use or require, identification_t* */
AUTH_RULE_XAUTH_IDENTITY,
/** subject of certificate authority, identification_t* */
AUTH_RULE_CA_IDENTITY,
/** certificate authority, certificate_t* */
AUTH_RULE_CA_CERT,
/** intermediate certificate in trustchain, certificate_t* */

View File

@ -183,6 +183,10 @@ CALLBACK(conn_sn, int,
{
printf(" id: %s\n", auth->get(auth, "id"));
}
if (auth->get(auth, "ca_id"))
{
printf(" ca_id: %s\n", auth->get(auth, "ca_id"));
}
if (auth->get(auth, "eap_id"))
{
printf(" eap_id: %s\n", auth->get(auth, "eap_id"));

View File

@ -593,6 +593,16 @@ connections.<conn>.remote<suffix>.cacert<suffix>.slot =
connections.<conn>.remote<suffix>.cacert<suffix>.module =
Optional PKCS#11 module name.
connections.<conn>.remote<suffix>.ca_id =
Identity in CA certificate to accept for authentication.
The specified identity must be contained in one (intermediate) CA
of the remote peer trustchain, either as subject or as subjectAltName.
This has the same effect as specifying _cacerts_ to force clients under
a CA to specific connections; it does not require the CA certificate to
be available locally, and can be received from the peer during the
IKE exchange.
connections.<conn>.remote<suffix>.pubkeys =
Comma separated list of raw public keys to accept for authentication.

View File

@ -10,7 +10,7 @@ connections {
}
remote {
auth = pubkey
id = "C=CH, O=strongSwan Project, OU=Research, CN=*"
ca_id = "C=CH, O=strongSwan Project, OU=Research, CN=Research CA"
}
children {
alice {
@ -32,7 +32,7 @@ connections {
}
remote {
auth = pubkey
id = "C=CH, O=strongSwan Project, OU=Sales, CN=*"
ca_id = "C=CH, O=strongSwan Project, OU=Sales, CN=Sales CA"
}
children {
venus {