diff --git a/src/libcharon/plugins/stroke/stroke_ca.c b/src/libcharon/plugins/stroke/stroke_ca.c index bfaa9a9d9..0dbe70745 100644 --- a/src/libcharon/plugins/stroke/stroke_ca.c +++ b/src/libcharon/plugins/stroke/stroke_ca.c @@ -175,46 +175,15 @@ CALLBACK(certs_filter, bool, cert_data_t *data, enumerator_t *orig, va_list args) { ca_cert_t *cacert; - public_key_t *public; certificate_t **out; VA_ARGS_VGET(args, out); while (orig->enumerate(orig, &cacert)) { - certificate_t *cert = cacert->cert; - - if (data->cert != CERT_ANY && data->cert != cert->get_type(cert)) + if (certificate_matches(cacert->cert, data->cert, data->key, data->id)) { - continue; - } - public = cert->get_public_key(cert); - if (public) - { - if (data->key == KEY_ANY || data->key == public->get_type(public)) - { - if (data->id && public->has_fingerprint(public, - data->id->get_encoding(data->id))) - { - public->destroy(public); - *out = cert; - return TRUE; - } - } - else - { - public->destroy(public); - continue; - } - public->destroy(public); - } - else if (data->key != KEY_ANY) - { - continue; - } - if (!data->id || cert->has_subject(cert, data->id)) - { - *out = cert; + *out = cacert->cert; return TRUE; } } diff --git a/src/libcharon/plugins/vici/vici_authority.c b/src/libcharon/plugins/vici/vici_authority.c index d78ac2122..08f6ce5c7 100644 --- a/src/libcharon/plugins/vici/vici_authority.c +++ b/src/libcharon/plugins/vici/vici_authority.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2019 Tobias Brunner + * Copyright (C) 2016-2020 Tobias Brunner * Copyright (C) 2015 Andreas Steffen * HSR Hochschule fuer Technik Rapperswil * @@ -44,15 +44,15 @@ struct private_vici_authority_t { vici_dispatcher_t *dispatcher; /** - * credential backend managed by VICI used for our ca certificates - */ - vici_cred_t *cred; - - /** - * List of certification authorities + * List of certification authorities (authority_t*) */ linked_list_t *authorities; + /** + * List of CA certificates (ca_cert_t*) + */ + linked_list_t *certs; + /** * rwlock to lock access to certification authorities */ @@ -109,10 +109,8 @@ static authority_t *authority_create(char *name) return authority; } -/** - * destroy a certification authority - */ -static void authority_destroy(authority_t *this) +CALLBACK(authority_destroy, void, + authority_t *this) { this->crl_uris->destroy_function(this->crl_uris, free); this->ocsp_uris->destroy_function(this->ocsp_uris, free); @@ -122,6 +120,108 @@ static void authority_destroy(authority_t *this) free(this); } +typedef struct ca_cert_t ca_cert_t; + +/** + * Loaded CA certificate. + */ +struct ca_cert_t { + + /** + * Reference to certificate. + */ + certificate_t *cert; + + /** + * The number of authority sections referring to this certificate. + */ + u_int count; + + /** + * TRUE if this certificate was (also) added externally. + */ + bool external; +}; + +/** + * Destroy a CA certificate entry + */ +CALLBACK(ca_cert_destroy, void, + ca_cert_t *this) +{ + this->cert->destroy(this->cert); + free(this); +} + +CALLBACK(match_cert, bool, + ca_cert_t *item, va_list args) +{ + certificate_t *cert; + + VA_ARGS_VGET(args, cert); + return cert->equals(cert, item->cert); +} + +/** + * Add a CA certificate to the local store + */ +static certificate_t *add_cert_internal(private_vici_authority_t *this, + certificate_t *cert, bool external) +{ + ca_cert_t *found; + + if (this->certs->find_first(this->certs, match_cert, (void**)&found, cert)) + { + cert->destroy(cert); + cert = found->cert->get_ref(found->cert); + } + else + { + INIT(found, + .cert = cert->get_ref(cert) + ); + this->certs->insert_first(this->certs, found); + } + if (external) + { + found->external = TRUE; + } + else + { + found->count++; + } + return cert; +} + +CALLBACK(remove_external_certs, bool, + ca_cert_t *item, void *unused) +{ + if (item->external) + { + item->external = FALSE; + + if (!item->count) + { + ca_cert_destroy(item); + return TRUE; + } + } + return FALSE; +} + +CALLBACK2(remove_cert, bool, + ca_cert_t *item, certificate_t *cert) +{ + if (cert == item->cert) + { + if (--item->count == 0 && !item->external) + { + ca_cert_destroy(item); + return TRUE; + } + } + return FALSE; +} /** * Create a (error) reply message @@ -379,7 +479,6 @@ CALLBACK(authority_sn, bool, enumerator_t *enumerator; linked_list_t *authorities; authority_t *authority; - vici_cred_t *cred; load_data_t *data; chunk_t handle; @@ -437,6 +536,9 @@ CALLBACK(authority_sn, bool, request->this->lock->write_lock(request->this->lock); + data->authority->cert = add_cert_internal(request->this, + data->authority->cert, FALSE); + authorities = request->this->authorities; enumerator = authorities->create_enumerator(authorities); while (enumerator->enumerate(enumerator, &authority)) @@ -452,11 +554,8 @@ CALLBACK(authority_sn, bool, enumerator->destroy(enumerator); authorities->insert_last(authorities, data->authority); - cred = request->this->cred; - data->authority->cert = cred->add_cert(cred, data->authority->cert); - data->authority = NULL; - request->this->lock->unlock(request->this->lock); + data->authority = NULL; free_load_data(data); return TRUE; @@ -501,6 +600,7 @@ CALLBACK(unload_authority, vici_message_t*, if (streq(authority->name, authority_name)) { this->authorities->remove_at(this->authorities, enumerator); + this->certs->remove(this->certs, authority->cert, remove_cert); authority_destroy(authority); found = TRUE; break; @@ -513,6 +613,7 @@ CALLBACK(unload_authority, vici_message_t*, { return create_reply("unload: authority '%s' not found", authority_name); } + lib->credmgr->flush_cache(lib->credmgr, CERT_ANY); return create_reply(NULL); } @@ -627,27 +728,63 @@ static void manage_commands(private_vici_authority_t *this, bool reg) } /** - * data to pass to create_inner_cdp + * Data for the certificate and CDP enumerator */ typedef struct { private_vici_authority_t *this; certificate_type_t type; + key_type_t key; identification_t *id; -} cdp_data_t; +} cert_data_t; -/** - * destroy cdp enumerator data and unlock list - */ -static void cdp_data_destroy(cdp_data_t *data) +CALLBACK(cert_data_destroy, void, + cert_data_t *data) { data->this->lock->unlock(data->this->lock); free(data); } -/** - * inner enumerator constructor for CDP URIs - */ -static enumerator_t *create_inner_cdp(authority_t *authority, cdp_data_t *data) +CALLBACK(certs_filter, bool, + cert_data_t *data, enumerator_t *orig, va_list args) +{ + ca_cert_t *ca; + certificate_t **out; + + VA_ARGS_VGET(args, out); + + while (orig->enumerate(orig, &ca)) + { + if (certificate_matches(ca->cert, data->type, data->key, data->id)) + { + *out = ca->cert; + return TRUE; + } + } + return FALSE; +} + +METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, + private_vici_authority_t *this, certificate_type_t cert, key_type_t key, + identification_t *id, bool trusted) +{ + enumerator_t *enumerator; + cert_data_t *data; + + INIT(data, + .this = this, + .type = cert, + .key = key, + .id = id, + ); + + this->lock->read_lock(this->lock); + enumerator = this->certs->create_enumerator(this->certs); + return enumerator_create_filter(enumerator, certs_filter, data, + cert_data_destroy); +} + +CALLBACK(create_inner_cdp, enumerator_t*, + authority_t *authority, cert_data_t *data) { public_key_t *public; enumerator_t *enumerator = NULL; @@ -671,7 +808,8 @@ static enumerator_t *create_inner_cdp(authority_t *authority, cdp_data_t *data) } else { - if (public->has_fingerprint(public, data->id->get_encoding(data->id))) + if (public->has_fingerprint(public, + data->id->get_encoding(data->id))) { enumerator = list->create_enumerator(list); } @@ -681,11 +819,8 @@ static enumerator_t *create_inner_cdp(authority_t *authority, cdp_data_t *data) return enumerator; } -/** - * inner enumerator constructor for "Hash and URL" - */ -static enumerator_t *create_inner_cdp_hashandurl(authority_t *authority, - cdp_data_t *data) +CALLBACK(create_inner_cdp_hashandurl, enumerator_t*, + authority_t *authority, cert_data_t *data) { enumerator_t *enumerator = NULL; @@ -694,7 +829,8 @@ static enumerator_t *create_inner_cdp_hashandurl(authority_t *authority, return NULL; } - if (authority->cert->has_subject(authority->cert, data->id) != ID_MATCH_NONE) + if (authority->cert->has_subject(authority->cert, + data->id) != ID_MATCH_NONE) { enumerator = enumerator_create_single(strdup(authority->cert_uri_base), free); @@ -706,7 +842,7 @@ METHOD(credential_set_t, create_cdp_enumerator, enumerator_t*, private_vici_authority_t *this, certificate_type_t type, identification_t *id) { - cdp_data_t *data; + cert_data_t *data; switch (type) { /* we serve CRLs, OCSP responders and URLs for "Hash and URL" */ @@ -718,17 +854,35 @@ METHOD(credential_set_t, create_cdp_enumerator, enumerator_t*, default: return NULL; } - data = malloc_thing(cdp_data_t); - data->this = this; - data->type = type; - data->id = id; + + INIT(data, + .this = this, + .type = type, + .id = id, + ); this->lock->read_lock(this->lock); - return enumerator_create_nested( this->authorities->create_enumerator(this->authorities), (type == CERT_X509) ? (void*)create_inner_cdp_hashandurl : - (void*)create_inner_cdp, data, (void*)cdp_data_destroy); + (void*)create_inner_cdp, data, cert_data_destroy); +} + +METHOD(vici_authority_t, add_ca_cert, certificate_t*, + private_vici_authority_t *this, certificate_t *cert) +{ + this->lock->write_lock(this->lock); + cert = add_cert_internal(this, cert, TRUE); + this->lock->unlock(this->lock); + return cert; +} + +METHOD(vici_authority_t, clear_ca_certs, void, + private_vici_authority_t *this) +{ + this->lock->write_lock(this->lock); + this->certs->remove(this->certs, NULL, remove_external_certs); + this->lock->unlock(this->lock); } METHOD(vici_authority_t, destroy, void, @@ -738,6 +892,7 @@ METHOD(vici_authority_t, destroy, void, this->authorities->destroy_function(this->authorities, (void*)authority_destroy); + this->certs->destroy_function(this->certs, ca_cert_destroy); this->lock->destroy(this->lock); free(this); } @@ -745,8 +900,7 @@ METHOD(vici_authority_t, destroy, void, /** * See header */ -vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher, - vici_cred_t *cred) +vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher) { private_vici_authority_t *this; @@ -754,16 +908,18 @@ vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher, .public = { .set = { .create_private_enumerator = (void*)return_null, - .create_cert_enumerator = (void*)return_null, + .create_cert_enumerator = _create_cert_enumerator, .create_shared_enumerator = (void*)return_null, .create_cdp_enumerator = _create_cdp_enumerator, .cache_cert = (void*)nop, }, + .add_ca_cert = _add_ca_cert, + .clear_ca_certs = _clear_ca_certs, .destroy = _destroy, }, .dispatcher = dispatcher, - .cred = cred, .authorities = linked_list_create(), + .certs = linked_list_create(), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); diff --git a/src/libcharon/plugins/vici/vici_authority.h b/src/libcharon/plugins/vici/vici_authority.h index 58f542cdd..187bede0b 100644 --- a/src/libcharon/plugins/vici/vici_authority.h +++ b/src/libcharon/plugins/vici/vici_authority.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2020 Tobias Brunner * Copyright (C) 2015 Andreas Steffen * HSR Hochschule fuer Technik Rapperswil * @@ -22,7 +23,6 @@ #define VICI_AUTHORITY_H_ #include "vici_dispatcher.h" -#include "vici_cred.h" typedef struct vici_authority_t vici_authority_t; @@ -36,6 +36,20 @@ struct vici_authority_t { */ credential_set_t set; + /** + * Add a CA certificate and return a reference if it is already stored, + * otherwise returns the same certificate. + * + * @param cert certificate to check + * @return reference to stored CA certificate, or original + */ + certificate_t *(*add_ca_cert)(vici_authority_t *this, certificate_t *cert); + + /** + * Remove CA certificates added via add_ca_cert(). + */ + void (*clear_ca_certs)(vici_authority_t *this); + /** * Destroy a vici_authority_t. */ @@ -46,10 +60,8 @@ struct vici_authority_t { * Create a vici_authority instance. * * @param dispatcher dispatcher to receive requests from - * @param cred in-memory credential backend managed by VICI * @return authority backend */ -vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher, - vici_cred_t *cred); +vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher); #endif /** VICI_AUTHORITY_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 0f8113f92..2a4d58eab 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1444,10 +1444,19 @@ CALLBACK(parse_cert_policy, bool, */ static bool add_cert(auth_data_t *auth, auth_rule_t rule, certificate_t *cert) { + vici_authority_t *authority; vici_cred_t *cred; - cred = auth->request->this->cred; - cert = cred->add_cert(cred, cert); + if (rule == AUTH_RULE_CA_CERT) + { + authority = auth->request->this->authority; + cert = authority->add_ca_cert(authority, cert); + } + else + { + cred = auth->request->this->cred; + cert = cred->add_cert(cred, cert); + } auth->cfg->add(auth->cfg, rule, cert); return TRUE; } @@ -1492,17 +1501,13 @@ CALLBACK(parse_cacerts, bool, CALLBACK(parse_pubkeys, bool, auth_data_t *auth, chunk_t v) { - vici_cred_t *cred; certificate_t *cert; cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_TRUSTED_PUBKEY, BUILD_BLOB_PEM, v, BUILD_END); if (cert) { - cred = auth->request->this->cred; - cert = cred->add_cert(cred, cert); - auth->cfg->add(auth->cfg, AUTH_RULE_SUBJECT_CERT, cert); - return TRUE; + return add_cert(auth, AUTH_RULE_SUBJECT_CERT, cert); } return FALSE; } diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index 038338805..17f3ff0cd 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -51,6 +51,11 @@ struct private_vici_cred_t { */ vici_dispatcher_t *dispatcher; + /** + * CA certificate store + */ + vici_authority_t *authority; + /** * credentials */ @@ -135,7 +140,6 @@ CALLBACK(load_cert, vici_message_t*, x509_flag_t ext_flag, flag = X509_NONE; x509_t *x509; chunk_t data; - bool trusted = TRUE; char *str; str = message->get_str(message, NULL, "type"); @@ -179,26 +183,31 @@ CALLBACK(load_cert, vici_message_t*, } DBG1(DBG_CFG, "loaded certificate '%Y'", cert->get_subject(cert)); - /* check if CA certificate has CA basic constraint set */ - if (flag & X509_CA) + if (type == CERT_X509) { - char err_msg[] = "ca certificate lacks CA basic constraint, rejected"; x509 = (x509_t*)cert; - - if (!(x509->get_flags(x509) & X509_CA)) + if (x509->get_flags(x509) & X509_CA) { + cert = this->authority->add_ca_cert(this->authority, cert); cert->destroy(cert); - DBG1(DBG_CFG, " %s", err_msg); - return create_reply(err_msg); + return create_reply(NULL); + } + else if (flag & X509_CA) + { + char msg[] = "ca certificate lacks CA basic constraint, rejected"; + cert->destroy(cert); + DBG1(DBG_CFG, " %s", msg); + return create_reply(msg); } } + if (type == CERT_X509_CRL) { this->creds->add_crl(this->creds, (crl_t*)cert); } else { - this->creds->add_cert(this->creds, trusted, cert); + this->creds->add_cert(this->creds, type != CERT_X509_AC, cert); } return create_reply(NULL); } @@ -536,6 +545,7 @@ CALLBACK(clear_creds, vici_message_t*, private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) { this->creds->clear(this->creds); + this->authority->clear_ca_certs(this->authority); lib->credmgr->flush_cache(lib->credmgr, CERT_ANY); return create_reply(NULL); @@ -604,7 +614,8 @@ METHOD(vici_cred_t, destroy, void, /** * See header */ -vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher) +vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher, + vici_authority_t *authority) { private_vici_cred_t *this; @@ -621,6 +632,7 @@ vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher) .destroy = _destroy, }, .dispatcher = dispatcher, + .authority = authority, .creds = mem_cred_create(), .pins = mem_cred_create(), ); diff --git a/src/libcharon/plugins/vici/vici_cred.h b/src/libcharon/plugins/vici/vici_cred.h index 6ce514786..5cfb3f7aa 100644 --- a/src/libcharon/plugins/vici/vici_cred.h +++ b/src/libcharon/plugins/vici/vici_cred.h @@ -25,6 +25,7 @@ #define VICI_CRED_H_ #include "vici_dispatcher.h" +#include "vici_authority.h" #include @@ -58,8 +59,10 @@ struct vici_cred_t { * Create a vici_cred instance. * * @param dispatcher dispatcher to receive requests from + * @param authority CA certificate storage * @return credential backend */ -vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher); +vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher, + vici_authority_t *authority); #endif /** VICI_CRED_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index 53da75e2a..c34500ac4 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -127,9 +127,8 @@ static bool register_vici(private_vici_plugin_t *this, { this->query = vici_query_create(this->dispatcher); this->control = vici_control_create(this->dispatcher); - this->cred = vici_cred_create(this->dispatcher); - this->authority = vici_authority_create(this->dispatcher, - this->cred); + this->authority = vici_authority_create(this->dispatcher); + this->cred = vici_cred_create(this->dispatcher, this->authority); lib->credmgr->add_set(lib->credmgr, &this->cred->set); lib->credmgr->add_set(lib->credmgr, &this->authority->set); this->config = vici_config_create(this->dispatcher, this->authority, diff --git a/src/libstrongswan/credentials/certificates/certificate.c b/src/libstrongswan/credentials/certificates/certificate.c index 761082986..d8e2223b7 100644 --- a/src/libstrongswan/credentials/certificates/certificate.c +++ b/src/libstrongswan/credentials/certificates/certificate.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2020 Tobias Brunner * Copyright (C) 2007 Martin Willi * Copyright (C) 2015 Andreas Steffen * HSR Hochschule fuer Technik Rapperswil @@ -61,3 +62,40 @@ bool certificate_is_newer(certificate_t *this, certificate_t *other) type, &that_update, FALSE, newer ? "replaced" : "retained"); return newer; } + +/* + * Described in header + */ +bool certificate_matches(certificate_t *cert, certificate_type_t type, + key_type_t key, identification_t *id) +{ + public_key_t *public; + + if (type != CERT_ANY && type != cert->get_type(cert)) + { + return FALSE; + } + public = cert->get_public_key(cert); + if (public) + { + if (key == KEY_ANY || key == public->get_type(public)) + { + if (id && public->has_fingerprint(public, id->get_encoding(id))) + { + public->destroy(public); + return TRUE; + } + } + else + { + public->destroy(public); + return FALSE; + } + public->destroy(public); + } + else if (key != KEY_ANY) + { + return FALSE; + } + return !id || cert->has_subject(cert, id); +} diff --git a/src/libstrongswan/credentials/certificates/certificate.h b/src/libstrongswan/credentials/certificates/certificate.h index 71620aa91..cf2f2ae69 100644 --- a/src/libstrongswan/credentials/certificates/certificate.h +++ b/src/libstrongswan/credentials/certificates/certificate.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2020 Tobias Brunner * Copyright (C) 2007-2008 Martin Willi * HSR Hochschule fuer Technik Rapperswil * @@ -204,4 +205,18 @@ struct certificate_t { */ bool certificate_is_newer(certificate_t *cert, certificate_t *other); +/** + * Check if the given certificate matches the given type, key type and identity, + * all of which are optional. + * + * Note that the identity may also be a public key fingerprint. + * + * @param cert certificate + * @param type certificate type to match, or CERT_ANY + * @param key key type to match, or KEY_ANY + * @param id identity to match, or NULL + */ +bool certificate_matches(certificate_t *cert, certificate_type_t type, + key_type_t key, identification_t *id); + #endif /** CERTIFICATE_H_ @}*/ diff --git a/src/libstrongswan/credentials/sets/mem_cred.c b/src/libstrongswan/credentials/sets/mem_cred.c index 86b232a3e..0a4d512d2 100644 --- a/src/libstrongswan/credentials/sets/mem_cred.c +++ b/src/libstrongswan/credentials/sets/mem_cred.c @@ -84,42 +84,13 @@ CALLBACK(cert_data_destroy, void, CALLBACK(certs_filter, bool, cert_data_t *data, enumerator_t *orig, va_list args) { - public_key_t *public; certificate_t *cert, **out; VA_ARGS_VGET(args, out); while (orig->enumerate(orig, &cert)) { - if (data->cert != CERT_ANY && data->cert != cert->get_type(cert)) - { - continue; - } - public = cert->get_public_key(cert); - if (public) - { - if (data->key == KEY_ANY || data->key == public->get_type(public)) - { - if (data->id && public->has_fingerprint(public, - data->id->get_encoding(data->id))) - { - public->destroy(public); - *out = cert; - return TRUE; - } - } - else - { - public->destroy(public); - continue; - } - public->destroy(public); - } - else if (data->key != KEY_ANY) - { - continue; - } - if (!data->id || cert->has_subject(cert, data->id)) + if (certificate_matches(cert, data->cert, data->key, data->id)) { *out = cert; return TRUE; diff --git a/src/libstrongswan/utils/utils/object.h b/src/libstrongswan/utils/utils/object.h index 24169dafc..174ea812a 100644 --- a/src/libstrongswan/utils/utils/object.h +++ b/src/libstrongswan/utils/utils/object.h @@ -123,4 +123,15 @@ static typeof(_cb_##name) *name = (typeof(_cb_##name)*)_cb_##name; \ static ret _cb_##name(param1, ##__VA_ARGS__) +/** + * Same as CALLBACK(), but for two void* arguments (e.g. for comparisons). + */ +#define CALLBACK2(name, ret, param1, param2, ...) \ + static ret _cb_##name(union {void *_generic; param1;} \ + __attribute__((transparent_union)), \ + union {void *_generic; param2;} \ + __attribute__((transparent_union)), ##__VA_ARGS__); \ + static typeof(_cb_##name) *name = (typeof(_cb_##name)*)_cb_##name; \ + static ret _cb_##name(param1, param2, ##__VA_ARGS__) + #endif /** OBJECT_H_ @} */