/* * Copyright (C) 2016-2020 Tobias Brunner * Copyright (C) 2015 Andreas Steffen * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #define _GNU_SOURCE #include "vici_authority.h" #include "vici_builder.h" #include #include #include #include #include typedef struct private_vici_authority_t private_vici_authority_t; /** * Private data of an vici_authority_t object. */ struct private_vici_authority_t { /** * Public vici_authority_t interface. */ vici_authority_t public; /** * Dispatcher */ vici_dispatcher_t *dispatcher; /** * 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 */ rwlock_t *lock; }; typedef struct authority_t authority_t; /** * loaded certification authorities */ struct authority_t { /** * Name of the certification authority */ char *name; /** * Reference to CA certificate */ certificate_t *cert; /** * CRL URIs */ linked_list_t *crl_uris; /** * OCSP URIs */ linked_list_t *ocsp_uris; /** * Base URI used for certificates from this CA */ char *cert_uri_base; }; /** * create a new certification authority */ static authority_t *authority_create(char *name) { authority_t *authority; INIT(authority, .name = strdup(name), .crl_uris = linked_list_create(), .ocsp_uris = linked_list_create(), ); return authority; } 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); DESTROY_IF(this->cert); free(this->cert_uri_base); free(this->name); 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 */ static vici_message_t* create_reply(char *fmt, ...) { vici_builder_t *builder; va_list args; builder = vici_builder_create(); builder->add_kv(builder, "success", fmt ? "no" : "yes"); if (fmt) { va_start(args, fmt); builder->vadd_kv(builder, "errmsg", fmt, args); va_end(args); } return builder->finalize(builder); } /** * A rule to parse a key/value or list item */ typedef struct { /** name of the key/value or list */ char *name; /** function to parse value */ bool (*parse)(void *out, chunk_t value); /** result, passed to parse() */ void *out; } parse_rule_t; /** * Parse key/values using a rule-set */ static bool parse_rules(parse_rule_t *rules, int count, char *name, chunk_t value, vici_message_t **reply) { int i; for (i = 0; i < count; i++) { if (streq(name, rules[i].name)) { if (rules[i].parse(rules[i].out, value)) { return TRUE; } *reply = create_reply("invalid value for: %s, authority discarded", name); return FALSE; } } *reply = create_reply("unknown option: %s, authority discarded", name); return FALSE; } /** * Parse callback data, passed to each callback */ typedef struct { private_vici_authority_t *this; vici_message_t *reply; } request_data_t; /** * Data associated with an authority load */ typedef struct { request_data_t *request; authority_t *authority; char *handle; uint32_t slot; char *module; char *file; } load_data_t; /** * Clean up data associated with an authority load */ static void free_load_data(load_data_t *data) { if (data->authority) { authority_destroy(data->authority); } free(data->handle); free(data->module); free(data->file); free(data); } /** * Parse a string */ CALLBACK(parse_string, bool, char **str, chunk_t v) { if (!chunk_printable(v, NULL, ' ')) { return FALSE; } *str = strndup(v.ptr, v.len); return TRUE; } /** * Parse a uint32_t */ CALLBACK(parse_uint32, bool, uint32_t *out, chunk_t v) { char buf[16], *end; u_long l; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } l = strtoul(buf, &end, 0); if (*end == 0) { *out = l; return TRUE; } return FALSE; } /** * Parse list of URIs */ CALLBACK(parse_uris, bool, linked_list_t *out, chunk_t v) { char *uri; if (!chunk_printable(v, NULL, ' ')) { return FALSE; } uri = strndup(v.ptr, v.len); out->insert_last(out, uri); return TRUE; } /** * Parse a CA certificate */ CALLBACK(parse_cacert, bool, certificate_t **cacert, chunk_t v) { certificate_t *cert; x509_t *x509; cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_BLOB_PEM, v, BUILD_END); if (!cert) { return create_reply("parsing %N certificate failed", certificate_type_names, CERT_X509); } x509 = (x509_t*)cert; if ((x509->get_flags(x509) & X509_CA) != X509_CA) { cert->destroy(cert); return create_reply("certificate without CA flag, rejected"); } *cacert = cert; return TRUE; } CALLBACK(authority_kv, bool, load_data_t *data, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "cacert", parse_cacert, &data->authority->cert }, { "file", parse_string, &data->file }, { "handle", parse_string, &data->handle }, { "slot", parse_uint32, &data->slot }, { "module", parse_string, &data->module }, { "cert_uri_base", parse_string, &data->authority->cert_uri_base }, }; return parse_rules(rules, countof(rules), name, value, &data->request->reply); } CALLBACK(authority_li, bool, load_data_t *data, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "crl_uris", parse_uris, data->authority->crl_uris }, { "ocsp_uris", parse_uris, data->authority->ocsp_uris }, }; return parse_rules(rules, countof(rules), name, value, &data->request->reply); } static void log_authority_data(authority_t *authority) { enumerator_t *enumerator; identification_t *subject; bool first = TRUE; char *uri; subject = authority->cert->get_subject(authority->cert); DBG2(DBG_CFG, " cacert = %Y", subject); enumerator = authority->crl_uris->create_enumerator(authority->crl_uris); while (enumerator->enumerate(enumerator, &uri)) { if (first) { DBG2(DBG_CFG, " crl_uris = %s", uri); first = FALSE; } else { DBG2(DBG_CFG, " %s", uri); } } enumerator->destroy(enumerator); first = TRUE; enumerator = authority->ocsp_uris->create_enumerator(authority->ocsp_uris); while (enumerator->enumerate(enumerator, &uri)) { if (first) { DBG2(DBG_CFG, " ocsp_uris = %s", uri); first = FALSE; } else { DBG2(DBG_CFG, " %s", uri); } } enumerator->destroy(enumerator); if (authority->cert_uri_base) { DBG2(DBG_CFG, " cert_uri_base = %s", authority->cert_uri_base); } } CALLBACK(authority_sn, bool, request_data_t *request, vici_message_t *message, vici_parse_context_t *ctx, char *name) { enumerator_t *enumerator; linked_list_t *authorities; authority_t *authority; load_data_t *data; chunk_t handle; INIT(data, .request = request, .authority = authority_create(name), .slot = -1, ); DBG2(DBG_CFG, " authority %s:", name); if (!message->parse(message, ctx, NULL, authority_kv, authority_li, data)) { free_load_data(data); return FALSE; } if (!data->authority->cert) { if (data->file) { data->authority->cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, data->file, BUILD_END); } else if (data->handle) { handle = chunk_from_hex(chunk_from_str(data->handle), NULL); if (data->slot != -1) { data->authority->cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_PKCS11_KEYID, handle, BUILD_PKCS11_SLOT, data->slot, data->module ? BUILD_PKCS11_MODULE : BUILD_END, data->module, BUILD_END); } else { data->authority->cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_PKCS11_KEYID, handle, data->module ? BUILD_PKCS11_MODULE : BUILD_END, data->module, BUILD_END); } chunk_free(&handle); } } if (!data->authority->cert) { request->reply = create_reply("CA certificate missing: %s", name); free_load_data(data); return FALSE; } log_authority_data(data->authority); 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)) { if (streq(authority->name, name)) { /* remove the old authority definition */ authorities->remove_at(authorities, enumerator); request->this->certs->remove(request->this->certs, authority->cert, remove_cert); authority_destroy(authority); break; } } enumerator->destroy(enumerator); authorities->insert_last(authorities, data->authority); request->this->lock->unlock(request->this->lock); data->authority = NULL; free_load_data(data); return TRUE; } CALLBACK(load_authority, vici_message_t*, private_vici_authority_t *this, char *name, u_int id, vici_message_t *message) { request_data_t request = { .this = this, }; if (!message->parse(message, NULL, authority_sn, NULL, NULL, &request)) { if (request.reply) { return request.reply; } return create_reply("parsing request failed"); } return create_reply(NULL); } CALLBACK(unload_authority, vici_message_t*, private_vici_authority_t *this, char *name, u_int id, vici_message_t *message) { enumerator_t *enumerator; authority_t *authority; char *authority_name; bool found = FALSE; authority_name = message->get_str(message, NULL, "name"); if (!authority_name) { return create_reply("unload: missing authority name"); } this->lock->write_lock(this->lock); enumerator = this->authorities->create_enumerator(this->authorities); while (enumerator->enumerate(enumerator, &authority)) { 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; } } enumerator->destroy(enumerator); this->lock->unlock(this->lock); if (!found) { return create_reply("unload: authority '%s' not found", authority_name); } lib->credmgr->flush_cache(lib->credmgr, CERT_ANY); return create_reply(NULL); } CALLBACK(get_authorities, vici_message_t*, private_vici_authority_t *this, char *name, u_int id, vici_message_t *message) { vici_builder_t *builder; enumerator_t *enumerator; authority_t *authority; builder = vici_builder_create(); builder->begin_list(builder, "authorities"); this->lock->read_lock(this->lock); enumerator = this->authorities->create_enumerator(this->authorities); while (enumerator->enumerate(enumerator, &authority)) { builder->add_li(builder, "%s", authority->name); } enumerator->destroy(enumerator); this->lock->unlock(this->lock); builder->end_list(builder); return builder->finalize(builder); } CALLBACK(list_authorities, vici_message_t*, private_vici_authority_t *this, char *name, u_int id, vici_message_t *request) { enumerator_t *enumerator, *e; authority_t *authority; vici_builder_t *b; char *str, *uri; str = request->get_str(request, NULL, "name"); this->lock->read_lock(this->lock); enumerator = this->authorities->create_enumerator(this->authorities); while (enumerator->enumerate(enumerator, &authority)) { if (str && !streq(str, authority->name)) { continue; } b = vici_builder_create(); /* open authority section */ b->begin_section(b, authority->name); /* subject DN of cacert */ b->add_kv(b, "cacert", "%Y", authority->cert->get_subject(authority->cert)); /* list of crl_uris */ b->begin_list(b, "crl_uris"); e = authority->crl_uris->create_enumerator(authority->crl_uris); while (e->enumerate(e, &uri)) { b->add_li(b, "%s", uri); } e->destroy(e); b->end_list(b); /* list of ocsp_uris */ b->begin_list(b, "ocsp_uris"); e = authority->ocsp_uris->create_enumerator(authority->ocsp_uris); while (e->enumerate(e, &uri)) { b->add_li(b, "%s", uri); } e->destroy(e); b->end_list(b); /* cert_uri_base */ if (authority->cert_uri_base) { b->add_kv(b, "cert_uri_base", "%s", authority->cert_uri_base); } /* close authority and raise event */ b->end_section(b); this->dispatcher->raise_event(this->dispatcher, "list-authority", id, b->finalize(b)); } enumerator->destroy(enumerator); this->lock->unlock(this->lock); b = vici_builder_create(); return b->finalize(b); } static void manage_command(private_vici_authority_t *this, char *name, vici_command_cb_t cb, bool reg) { this->dispatcher->manage_command(this->dispatcher, name, reg ? cb : NULL, this); } /** * (Un-)register dispatcher functions */ static void manage_commands(private_vici_authority_t *this, bool reg) { this->dispatcher->manage_event(this->dispatcher, "list-authority", reg); manage_command(this, "load-authority", load_authority, reg); manage_command(this, "unload-authority", unload_authority, reg); manage_command(this, "get-authorities", get_authorities, reg); manage_command(this, "list-authorities", list_authorities, reg); } /** * 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; } cert_data_t; CALLBACK(cert_data_destroy, void, cert_data_t *data) { data->this->lock->unlock(data->this->lock); free(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; linked_list_t *list; if (data->type == CERT_X509_OCSP_RESPONSE) { list = authority->ocsp_uris; } else { list = authority->crl_uris; } public = authority->cert->get_public_key(authority->cert); if (public) { if (!data->id) { enumerator = list->create_enumerator(list); } else { if (public->has_fingerprint(public, data->id->get_encoding(data->id))) { enumerator = list->create_enumerator(list); } } public->destroy(public); } return enumerator; } CALLBACK(create_inner_cdp_hashandurl, enumerator_t*, authority_t *authority, cert_data_t *data) { enumerator_t *enumerator = NULL; if (!data->id || !authority->cert_uri_base) { return NULL; } if (authority->cert->has_subject(authority->cert, data->id) != ID_MATCH_NONE) { enumerator = enumerator_create_single(strdup(authority->cert_uri_base), free); } return enumerator; } METHOD(credential_set_t, create_cdp_enumerator, enumerator_t*, private_vici_authority_t *this, certificate_type_t type, identification_t *id) { cert_data_t *data; switch (type) { /* we serve CRLs, OCSP responders and URLs for "Hash and URL" */ case CERT_X509: case CERT_X509_CRL: case CERT_X509_OCSP_RESPONSE: case CERT_ANY: break; default: return NULL; } 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, 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, private_vici_authority_t *this) { manage_commands(this, FALSE); 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); } /** * See header */ vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher) { private_vici_authority_t *this; INIT(this, .public = { .set = { .create_private_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, .authorities = linked_list_create(), .certs = linked_list_create(), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); manage_commands(this, TRUE); return &this->public; }