| /* |
| * Copyright (C) 2015 Tobias Brunner |
| * Copyright (C) 2007 Martin Willi |
| * 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 <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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. |
| */ |
| |
| #include "credential_manager.h" |
| |
| #include <library.h> |
| #include <utils/debug.h> |
| #include <threading/thread_value.h> |
| #include <threading/mutex.h> |
| #include <threading/rwlock.h> |
| #include <collections/linked_list.h> |
| #include <credentials/sets/cert_cache.h> |
| #include <credentials/sets/auth_cfg_wrapper.h> |
| #include <credentials/certificates/x509.h> |
| |
| /** |
| * Maximum length of a certificate trust chain |
| */ |
| #define MAX_TRUST_PATH_LEN 7 |
| |
| typedef struct private_credential_manager_t private_credential_manager_t; |
| |
| /** |
| * private data of credential_manager |
| */ |
| struct private_credential_manager_t { |
| |
| /** |
| * public functions |
| */ |
| credential_manager_t public; |
| |
| /** |
| * list of credential sets |
| */ |
| linked_list_t *sets; |
| |
| /** |
| * thread local set of credentials, linked_list_t with credential_set_t's |
| */ |
| thread_value_t *local_sets; |
| |
| /** |
| * Exclusive local sets, linked_list_t with credential_set_t |
| */ |
| thread_value_t *exclusive_local_sets; |
| |
| /** |
| * trust relationship and certificate cache |
| */ |
| cert_cache_t *cache; |
| |
| /** |
| * certificates queued for persistent caching |
| */ |
| linked_list_t *cache_queue; |
| |
| /** |
| * list of certificate validators, cert_validator_t |
| */ |
| linked_list_t *validators; |
| |
| /** |
| * read-write lock to sets list |
| */ |
| rwlock_t *lock; |
| |
| /** |
| * mutex for cache queue |
| */ |
| mutex_t *queue_mutex; |
| |
| /** |
| * Registered hook to call on validation errors |
| */ |
| credential_hook_t hook; |
| |
| /** |
| * Registered data to pass to hook |
| */ |
| void *hook_data; |
| }; |
| |
| /** data to pass to create_private_enumerator */ |
| typedef struct { |
| private_credential_manager_t *this; |
| key_type_t type; |
| identification_t* keyid; |
| } private_data_t; |
| |
| /** data to pass to create_cert_enumerator */ |
| typedef struct { |
| private_credential_manager_t *this; |
| certificate_type_t cert; |
| key_type_t key; |
| identification_t *id; |
| bool trusted; |
| } cert_data_t; |
| |
| /** data to pass to create_cdp_enumerator */ |
| typedef struct { |
| private_credential_manager_t *this; |
| certificate_type_t type; |
| identification_t *id; |
| } cdp_data_t; |
| |
| /** data to pass to create_shared_enumerator */ |
| typedef struct { |
| private_credential_manager_t *this; |
| shared_key_type_t type; |
| identification_t *me; |
| identification_t *other; |
| } shared_data_t; |
| |
| /** enumerator over local and global sets */ |
| typedef struct { |
| /** implements enumerator_t */ |
| enumerator_t public; |
| /** enumerator over global sets */ |
| enumerator_t *global; |
| /** enumerator over local sets */ |
| enumerator_t *local; |
| /** enumerator over exclusive local sets */ |
| enumerator_t *exclusive; |
| } sets_enumerator_t; |
| |
| METHOD(credential_manager_t, set_hook, void, |
| private_credential_manager_t *this, credential_hook_t hook, void *data) |
| { |
| this->hook = hook; |
| this->hook_data = data; |
| } |
| |
| METHOD(credential_manager_t, call_hook, void, |
| private_credential_manager_t *this, credential_hook_type_t type, |
| certificate_t *cert) |
| { |
| if (this->hook) |
| { |
| this->hook(this->hook_data, type, cert); |
| } |
| } |
| |
| METHOD(enumerator_t, sets_enumerate, bool, |
| sets_enumerator_t *this, va_list args) |
| { |
| credential_set_t **set; |
| |
| VA_ARGS_VGET(args, set); |
| |
| if (this->exclusive) |
| { |
| if (this->exclusive->enumerate(this->exclusive, set)) |
| { /* only enumerate last added */ |
| this->exclusive->destroy(this->exclusive); |
| this->exclusive = NULL; |
| return TRUE; |
| } |
| } |
| if (this->local) |
| { |
| if (this->local->enumerate(this->local, set)) |
| { |
| return TRUE; |
| } |
| /* end of local sets, look for global */ |
| this->local->destroy(this->local); |
| this->local = NULL; |
| } |
| if (this->global) |
| { |
| return this->global->enumerate(this->global, set); |
| } |
| return FALSE; |
| } |
| |
| METHOD(enumerator_t, sets_destroy, void, |
| sets_enumerator_t *this) |
| { |
| DESTROY_IF(this->global); |
| DESTROY_IF(this->local); |
| DESTROY_IF(this->exclusive); |
| free(this); |
| } |
| |
| /** |
| * create an enumerator over both, global and local sets |
| */ |
| static enumerator_t *create_sets_enumerator(private_credential_manager_t *this) |
| { |
| sets_enumerator_t *enumerator; |
| linked_list_t *list; |
| |
| INIT(enumerator, |
| .public = { |
| .enumerate = enumerator_enumerate_default, |
| .venumerate = _sets_enumerate, |
| .destroy = _sets_destroy, |
| }, |
| ); |
| |
| list = this->exclusive_local_sets->get(this->exclusive_local_sets); |
| if (list && list->get_count(list)) |
| { |
| enumerator->exclusive = list->create_enumerator(list); |
| } |
| else |
| { |
| enumerator->global = this->sets->create_enumerator(this->sets); |
| list = this->local_sets->get(this->local_sets); |
| if (list) |
| { |
| enumerator->local = list->create_enumerator(list); |
| } |
| } |
| return &enumerator->public; |
| } |
| |
| /** |
| * cleanup function for cert data |
| */ |
| static void destroy_cert_data(cert_data_t *data) |
| { |
| data->this->lock->unlock(data->this->lock); |
| free(data); |
| } |
| |
| /** |
| * enumerator constructor for certificates |
| */ |
| static enumerator_t *create_cert(credential_set_t *set, cert_data_t *data) |
| { |
| return set->create_cert_enumerator(set, data->cert, data->key, |
| data->id, data->trusted); |
| } |
| |
| METHOD(credential_manager_t, create_cert_enumerator, enumerator_t*, |
| private_credential_manager_t *this, certificate_type_t certificate, |
| key_type_t key, identification_t *id, bool trusted) |
| { |
| cert_data_t *data = malloc_thing(cert_data_t); |
| data->this = this; |
| data->cert = certificate; |
| data->key = key; |
| data->id = id; |
| data->trusted = trusted; |
| |
| this->lock->read_lock(this->lock); |
| return enumerator_create_nested(create_sets_enumerator(this), |
| (void*)create_cert, data, |
| (void*)destroy_cert_data); |
| } |
| |
| METHOD(credential_manager_t, get_cert, certificate_t*, |
| private_credential_manager_t *this, certificate_type_t cert, key_type_t key, |
| identification_t *id, bool trusted) |
| { |
| certificate_t *current, *found = NULL; |
| enumerator_t *enumerator; |
| |
| enumerator = create_cert_enumerator(this, cert, key, id, trusted); |
| if (enumerator->enumerate(enumerator, ¤t)) |
| { |
| /* TODO: best match? order by keyid, subject, subjectAltName */ |
| found = current->get_ref(current); |
| } |
| enumerator->destroy(enumerator); |
| return found; |
| } |
| |
| |
| /** |
| * cleanup function for cdp data |
| */ |
| static void destroy_cdp_data(cdp_data_t *data) |
| { |
| data->this->lock->unlock(data->this->lock); |
| free(data); |
| } |
| |
| /** |
| * enumerator constructor for CDPs |
| */ |
| static enumerator_t *create_cdp(credential_set_t *set, cdp_data_t *data) |
| { |
| return set->create_cdp_enumerator(set, data->type, data->id); |
| } |
| |
| METHOD(credential_manager_t, create_cdp_enumerator, enumerator_t*, |
| private_credential_manager_t *this, certificate_type_t type, |
| identification_t *id) |
| { |
| cdp_data_t *data; |
| |
| INIT(data, |
| .this = this, |
| .type = type, |
| .id = id, |
| ); |
| this->lock->read_lock(this->lock); |
| return enumerator_create_nested(create_sets_enumerator(this), |
| (void*)create_cdp, data, |
| (void*)destroy_cdp_data); |
| } |
| |
| /** |
| * cleanup function for private data |
| */ |
| static void destroy_private_data(private_data_t *data) |
| { |
| data->this->lock->unlock(data->this->lock); |
| free(data); |
| } |
| |
| /** |
| * enumerator constructor for private keys |
| */ |
| static enumerator_t *create_private(credential_set_t *set, private_data_t *data) |
| { |
| return set->create_private_enumerator(set, data->type, data->keyid); |
| } |
| |
| /** |
| * Create an enumerator over private keys |
| */ |
| static enumerator_t *create_private_enumerator( |
| private_credential_manager_t *this, key_type_t key, identification_t *keyid) |
| { |
| private_data_t *data; |
| |
| INIT(data, |
| .this = this, |
| .type = key, |
| .keyid = keyid, |
| ); |
| this->lock->read_lock(this->lock); |
| return enumerator_create_nested(create_sets_enumerator(this), |
| (void*)create_private, data, |
| (void*)destroy_private_data); |
| } |
| |
| /** |
| * Look up a private key by its key identifier |
| */ |
| static private_key_t* get_private_by_keyid(private_credential_manager_t *this, |
| key_type_t key, identification_t *keyid) |
| { |
| private_key_t *found = NULL; |
| enumerator_t *enumerator; |
| |
| enumerator = create_private_enumerator(this, key, keyid); |
| if (enumerator->enumerate(enumerator, &found)) |
| { |
| found->get_ref(found); |
| } |
| enumerator->destroy(enumerator); |
| return found; |
| } |
| |
| /** |
| * cleanup function for shared data |
| */ |
| static void destroy_shared_data(shared_data_t *data) |
| { |
| data->this->lock->unlock(data->this->lock); |
| free(data); |
| } |
| |
| /** |
| * enumerator constructor for shared keys |
| */ |
| static enumerator_t *create_shared(credential_set_t *set, shared_data_t *data) |
| { |
| return set->create_shared_enumerator(set, data->type, data->me, data->other); |
| } |
| |
| METHOD(credential_manager_t, create_shared_enumerator, enumerator_t*, |
| private_credential_manager_t *this, shared_key_type_t type, |
| identification_t *me, identification_t *other) |
| { |
| shared_data_t *data; |
| |
| INIT(data, |
| .this = this, |
| .type = type, |
| .me = me, |
| .other = other, |
| ); |
| this->lock->read_lock(this->lock); |
| return enumerator_create_nested(create_sets_enumerator(this), |
| (void*)create_shared, data, |
| (void*)destroy_shared_data); |
| } |
| |
| METHOD(credential_manager_t, get_shared, shared_key_t*, |
| private_credential_manager_t *this, shared_key_type_t type, |
| identification_t *me, identification_t *other) |
| { |
| shared_key_t *current, *found = NULL; |
| id_match_t best_me = ID_MATCH_NONE, best_other = ID_MATCH_NONE; |
| id_match_t match_me, match_other; |
| enumerator_t *enumerator; |
| |
| enumerator = create_shared_enumerator(this, type, me, other); |
| while (enumerator->enumerate(enumerator, ¤t, &match_me, &match_other)) |
| { |
| if (match_other > best_other || |
| (match_other == best_other && match_me > best_me)) |
| { |
| DESTROY_IF(found); |
| found = current->get_ref(current); |
| best_me = match_me; |
| best_other = match_other; |
| } |
| if (best_me == ID_MATCH_PERFECT && best_other == ID_MATCH_PERFECT) |
| { |
| break; |
| } |
| } |
| enumerator->destroy(enumerator); |
| return found; |
| } |
| |
| METHOD(credential_manager_t, add_local_set, void, |
| private_credential_manager_t *this, credential_set_t *set, bool exclusive) |
| { |
| linked_list_t *sets; |
| thread_value_t *tv; |
| |
| if (exclusive) |
| { |
| tv = this->exclusive_local_sets; |
| } |
| else |
| { |
| tv = this->local_sets; |
| } |
| sets = tv->get(tv); |
| if (!sets) |
| { |
| sets = linked_list_create(); |
| tv->set(tv, sets); |
| } |
| if (exclusive) |
| { |
| sets->insert_first(sets, set); |
| } |
| else |
| { |
| sets->insert_last(sets, set); |
| } |
| } |
| |
| METHOD(credential_manager_t, remove_local_set, void, |
| private_credential_manager_t *this, credential_set_t *set) |
| { |
| linked_list_t *sets; |
| thread_value_t *tv; |
| |
| tv = this->local_sets; |
| sets = tv->get(tv); |
| if (sets && sets->remove(sets, set, NULL) && sets->get_count(sets) == 0) |
| { |
| tv->set(tv, NULL); |
| sets->destroy(sets); |
| } |
| tv = this->exclusive_local_sets; |
| sets = tv->get(tv); |
| if (sets && sets->remove(sets, set, NULL) && sets->get_count(sets) == 0) |
| { |
| tv->set(tv, NULL); |
| sets->destroy(sets); |
| } |
| } |
| |
| METHOD(credential_manager_t, issued_by, bool, |
| private_credential_manager_t *this, certificate_t *subject, |
| certificate_t *issuer, signature_params_t **scheme) |
| { |
| if (this->cache) |
| { |
| return this->cache->issued_by(this->cache, subject, issuer, scheme); |
| } |
| return subject->issued_by(subject, issuer, scheme); |
| } |
| |
| METHOD(credential_manager_t, cache_cert, void, |
| private_credential_manager_t *this, certificate_t *cert) |
| { |
| credential_set_t *set; |
| enumerator_t *enumerator; |
| |
| if (this->lock->try_write_lock(this->lock)) |
| { |
| enumerator = this->sets->create_enumerator(this->sets); |
| while (enumerator->enumerate(enumerator, &set)) |
| { |
| set->cache_cert(set, cert); |
| } |
| enumerator->destroy(enumerator); |
| this->lock->unlock(this->lock); |
| } |
| else |
| { /* we can't cache now as other threads are active, queue for later */ |
| this->queue_mutex->lock(this->queue_mutex); |
| this->cache_queue->insert_last(this->cache_queue, cert->get_ref(cert)); |
| this->queue_mutex->unlock(this->queue_mutex); |
| } |
| } |
| |
| /** |
| * Try to cache certificates queued for caching |
| */ |
| static void cache_queue(private_credential_manager_t *this) |
| { |
| credential_set_t *set; |
| certificate_t *cert; |
| enumerator_t *enumerator; |
| |
| this->queue_mutex->lock(this->queue_mutex); |
| if (this->cache_queue->get_count(this->cache_queue) > 0 && |
| this->lock->try_write_lock(this->lock)) |
| { |
| while (this->cache_queue->remove_last(this->cache_queue, |
| (void**)&cert) == SUCCESS) |
| { |
| enumerator = this->sets->create_enumerator(this->sets); |
| while (enumerator->enumerate(enumerator, &set)) |
| { |
| set->cache_cert(set, cert); |
| } |
| enumerator->destroy(enumerator); |
| cert->destroy(cert); |
| } |
| this->lock->unlock(this->lock); |
| } |
| this->queue_mutex->unlock(this->queue_mutex); |
| } |
| |
| /** |
| * Use validators to check the lifetime of certificates |
| */ |
| static bool check_lifetime(private_credential_manager_t *this, |
| certificate_t *cert, char *label, |
| int pathlen, bool trusted, auth_cfg_t *auth) |
| { |
| time_t not_before, not_after; |
| cert_validator_t *validator; |
| enumerator_t *enumerator; |
| status_t status = NEED_MORE; |
| |
| enumerator = this->validators->create_enumerator(this->validators); |
| while (enumerator->enumerate(enumerator, &validator)) |
| { |
| if (!validator->check_lifetime) |
| { |
| continue; |
| } |
| status = validator->check_lifetime(validator, cert, |
| pathlen, trusted, auth); |
| if (status != NEED_MORE) |
| { |
| break; |
| } |
| } |
| enumerator->destroy(enumerator); |
| |
| switch (status) |
| { |
| case NEED_MORE: |
| if (!cert->get_validity(cert, NULL, ¬_before, ¬_after)) |
| { |
| DBG1(DBG_CFG, "%s certificate invalid (valid from %T to %T)", |
| label, ¬_before, FALSE, ¬_after, FALSE); |
| break; |
| } |
| return TRUE; |
| case SUCCESS: |
| return TRUE; |
| case FAILED: |
| default: |
| break; |
| } |
| call_hook(this, CRED_HOOK_EXPIRED, cert); |
| return FALSE; |
| } |
| |
| /** |
| * check a certificate for its lifetime |
| */ |
| static bool check_certificate(private_credential_manager_t *this, |
| certificate_t *subject, certificate_t *issuer, bool online, |
| int pathlen, bool trusted, auth_cfg_t *auth) |
| { |
| cert_validator_t *validator; |
| enumerator_t *enumerator; |
| |
| if (!check_lifetime(this, subject, "subject", pathlen, FALSE, auth) || |
| !check_lifetime(this, issuer, "issuer", pathlen + 1, trusted, auth)) |
| { |
| return FALSE; |
| } |
| |
| enumerator = this->validators->create_enumerator(this->validators); |
| while (enumerator->enumerate(enumerator, &validator)) |
| { |
| if (!validator->validate) |
| { |
| continue; |
| } |
| if (!validator->validate(validator, subject, issuer, |
| online, pathlen, trusted, auth)) |
| { |
| enumerator->destroy(enumerator); |
| return FALSE; |
| } |
| } |
| enumerator->destroy(enumerator); |
| return TRUE; |
| } |
| |
| /** |
| * Get a trusted certificate from a credential set |
| */ |
| static certificate_t *get_pretrusted_cert(private_credential_manager_t *this, |
| key_type_t type, identification_t *id) |
| { |
| certificate_t *subject; |
| public_key_t *public; |
| |
| subject = get_cert(this, CERT_ANY, type, id, TRUE); |
| if (!subject) |
| { |
| return NULL; |
| } |
| public = subject->get_public_key(subject); |
| if (!public) |
| { |
| subject->destroy(subject); |
| return NULL; |
| } |
| public->destroy(public); |
| return subject; |
| } |
| |
| /** |
| * Get the issuing certificate of a subject certificate |
| */ |
| static certificate_t *get_issuer_cert(private_credential_manager_t *this, |
| certificate_t *subject, bool trusted, |
| signature_params_t **scheme) |
| { |
| enumerator_t *enumerator; |
| certificate_t *issuer = NULL, *candidate; |
| |
| enumerator = create_cert_enumerator(this, subject->get_type(subject), KEY_ANY, |
| subject->get_issuer(subject), trusted); |
| while (enumerator->enumerate(enumerator, &candidate)) |
| { |
| if (issued_by(this, subject, candidate, scheme)) |
| { |
| issuer = candidate->get_ref(candidate); |
| break; |
| } |
| } |
| enumerator->destroy(enumerator); |
| return issuer; |
| } |
| |
| /** |
| * Get the strength of certificate, add it to auth |
| */ |
| static void get_key_strength(certificate_t *cert, auth_cfg_t *auth) |
| { |
| uintptr_t strength; |
| public_key_t *key; |
| key_type_t type; |
| |
| key = cert->get_public_key(cert); |
| if (key) |
| { |
| type = key->get_type(key); |
| strength = key->get_keysize(key); |
| DBG2(DBG_CFG, " certificate \"%Y\" key: %d bit %N", |
| cert->get_subject(cert), strength, key_type_names, type); |
| switch (type) |
| { |
| case KEY_RSA: |
| auth->add(auth, AUTH_RULE_RSA_STRENGTH, strength); |
| break; |
| case KEY_ECDSA: |
| auth->add(auth, AUTH_RULE_ECDSA_STRENGTH, strength); |
| break; |
| case KEY_BLISS: |
| auth->add(auth, AUTH_RULE_BLISS_STRENGTH, strength); |
| break; |
| default: |
| break; |
| } |
| key->destroy(key); |
| } |
| } |
| |
| /** |
| * try to verify the trust chain of subject, return TRUE if trusted |
| */ |
| static bool verify_trust_chain(private_credential_manager_t *this, |
| certificate_t *subject, auth_cfg_t *result, |
| bool trusted, bool online) |
| { |
| certificate_t *current, *issuer; |
| auth_cfg_t *auth; |
| signature_params_t *scheme; |
| int pathlen; |
| |
| auth = auth_cfg_create(); |
| get_key_strength(subject, auth); |
| current = subject->get_ref(subject); |
| auth->add(auth, AUTH_RULE_SUBJECT_CERT, current->get_ref(current)); |
| |
| for (pathlen = 0; pathlen <= MAX_TRUST_PATH_LEN; pathlen++) |
| { |
| issuer = get_issuer_cert(this, current, TRUE, &scheme); |
| if (issuer) |
| { |
| /* accept only self-signed CAs as trust anchor */ |
| if (issued_by(this, issuer, issuer, NULL)) |
| { |
| auth->add(auth, AUTH_RULE_CA_CERT, issuer->get_ref(issuer)); |
| DBG1(DBG_CFG, " using trusted ca certificate \"%Y\"", |
| issuer->get_subject(issuer)); |
| trusted = TRUE; |
| } |
| else |
| { |
| auth->add(auth, AUTH_RULE_IM_CERT, issuer->get_ref(issuer)); |
| DBG1(DBG_CFG, " using trusted intermediate ca certificate " |
| "\"%Y\"", issuer->get_subject(issuer)); |
| } |
| auth->add(auth, AUTH_RULE_SIGNATURE_SCHEME, scheme); |
| } |
| else |
| { |
| issuer = get_issuer_cert(this, current, FALSE, &scheme); |
| if (issuer) |
| { |
| if (current->equals(current, issuer)) |
| { |
| DBG1(DBG_CFG, " self-signed certificate \"%Y\" is not " |
| "trusted", current->get_subject(current)); |
| issuer->destroy(issuer); |
| call_hook(this, CRED_HOOK_UNTRUSTED_ROOT, current); |
| break; |
| } |
| auth->add(auth, AUTH_RULE_IM_CERT, issuer->get_ref(issuer)); |
| DBG1(DBG_CFG, " using untrusted intermediate certificate " |
| "\"%Y\"", issuer->get_subject(issuer)); |
| auth->add(auth, AUTH_RULE_SIGNATURE_SCHEME, scheme); |
| } |
| else |
| { |
| DBG1(DBG_CFG, "no issuer certificate found for \"%Y\"", |
| current->get_subject(current)); |
| DBG1(DBG_CFG, " issuer is \"%Y\"", |
| current->get_issuer(current)); |
| call_hook(this, CRED_HOOK_NO_ISSUER, current); |
| break; |
| } |
| } |
| if (!check_certificate(this, current, issuer, online, |
| pathlen, trusted, auth)) |
| { |
| trusted = FALSE; |
| issuer->destroy(issuer); |
| break; |
| } |
| if (issuer) |
| { |
| get_key_strength(issuer, auth); |
| } |
| current->destroy(current); |
| current = issuer; |
| if (trusted) |
| { |
| DBG1(DBG_CFG, " reached self-signed root ca with a " |
| "path length of %d", pathlen); |
| break; |
| } |
| } |
| current->destroy(current); |
| if (pathlen > MAX_TRUST_PATH_LEN) |
| { |
| DBG1(DBG_CFG, "maximum path length of %d exceeded", MAX_TRUST_PATH_LEN); |
| call_hook(this, CRED_HOOK_EXCEEDED_PATH_LEN, subject); |
| } |
| if (trusted) |
| { |
| result->merge(result, auth, FALSE); |
| } |
| auth->destroy(auth); |
| return trusted; |
| } |
| |
| CALLBACK(cert_equals, bool, |
| certificate_t *a, va_list args) |
| { |
| certificate_t *b; |
| |
| VA_ARGS_VGET(args, b); |
| return a->equals(a, b); |
| } |
| |
| /** |
| * enumerator for trusted certificates |
| */ |
| typedef struct { |
| /** implements enumerator_t interface */ |
| enumerator_t public; |
| /** enumerator over candidate peer certificates */ |
| enumerator_t *candidates; |
| /** reference to the credential_manager */ |
| private_credential_manager_t *this; |
| /** type of the requested key */ |
| key_type_t type; |
| /** identity the requested key belongs to */ |
| identification_t *id; |
| /** TRUE to do CRL/OCSP checking */ |
| bool online; |
| /** pretrusted certificate we have served at first invocation */ |
| certificate_t *pretrusted; |
| /** currently enumerating auth config */ |
| auth_cfg_t *auth; |
| /** list of failed candidates */ |
| linked_list_t *failed; |
| } trusted_enumerator_t; |
| |
| METHOD(enumerator_t, trusted_enumerate, bool, |
| trusted_enumerator_t *this, va_list args) |
| { |
| certificate_t *current, **cert; |
| auth_cfg_t **auth; |
| |
| VA_ARGS_VGET(args, cert, auth); |
| |
| DESTROY_IF(this->auth); |
| this->auth = auth_cfg_create(); |
| |
| if (!this->candidates) |
| { |
| /* first invocation, build enumerator for next one */ |
| this->candidates = create_cert_enumerator(this->this, CERT_ANY, |
| this->type, this->id, FALSE); |
| /* check if we have a trusted certificate for that peer */ |
| this->pretrusted = get_pretrusted_cert(this->this, this->type, this->id); |
| if (this->pretrusted) |
| { |
| /* if we find a trusted self signed certificate, we just accept it. |
| * However, in order to fulfill authorization rules, we try to build |
| * the trust chain if it is not self signed */ |
| if (issued_by(this->this, this->pretrusted, this->pretrusted, NULL) || |
| verify_trust_chain(this->this, this->pretrusted, this->auth, |
| TRUE, this->online)) |
| { |
| DBG1(DBG_CFG, " using trusted certificate \"%Y\"", |
| this->pretrusted->get_subject(this->pretrusted)); |
| *cert = this->pretrusted; |
| if (!this->auth->get(this->auth, AUTH_RULE_SUBJECT_CERT)) |
| { /* add cert to auth info, if not returned by trustchain */ |
| this->auth->add(this->auth, AUTH_RULE_SUBJECT_CERT, |
| this->pretrusted->get_ref(this->pretrusted)); |
| } |
| if (auth) |
| { |
| *auth = this->auth; |
| } |
| return TRUE; |
| } |
| } |
| } |
| /* try to verify the trust chain for each certificate found */ |
| while (this->candidates->enumerate(this->candidates, ¤t)) |
| { |
| if (this->pretrusted && |
| this->pretrusted->equals(this->pretrusted, current)) |
| { /* skip pretrusted certificate we already served */ |
| continue; |
| } |
| |
| if (this->failed->find_first(this->failed, cert_equals, NULL, current)) |
| { /* check each candidate only once */ |
| continue; |
| } |
| |
| DBG1(DBG_CFG, " using certificate \"%Y\"", |
| current->get_subject(current)); |
| if (verify_trust_chain(this->this, current, this->auth, FALSE, |
| this->online)) |
| { |
| *cert = current; |
| if (auth) |
| { |
| *auth = this->auth; |
| } |
| return TRUE; |
| } |
| this->failed->insert_last(this->failed, current->get_ref(current)); |
| } |
| return FALSE; |
| } |
| |
| METHOD(enumerator_t, trusted_destroy, void, |
| trusted_enumerator_t *this) |
| { |
| DESTROY_IF(this->pretrusted); |
| DESTROY_IF(this->auth); |
| DESTROY_IF(this->candidates); |
| this->failed->destroy_offset(this->failed, offsetof(certificate_t, destroy)); |
| /* check for delayed certificate cache queue */ |
| cache_queue(this->this); |
| free(this); |
| } |
| |
| METHOD(credential_manager_t, create_trusted_enumerator, enumerator_t*, |
| private_credential_manager_t *this, key_type_t type, |
| identification_t *id, bool online) |
| { |
| trusted_enumerator_t *enumerator; |
| |
| INIT(enumerator, |
| .public = { |
| .enumerate = enumerator_enumerate_default, |
| .venumerate = _trusted_enumerate, |
| .destroy = _trusted_destroy, |
| }, |
| .this = this, |
| .type = type, |
| .id = id, |
| .online = online, |
| .failed = linked_list_create(), |
| ); |
| return &enumerator->public; |
| } |
| |
| /** |
| * enumerator for public keys |
| */ |
| typedef struct { |
| /** implements enumerator_t interface */ |
| enumerator_t public; |
| /** enumerator over candidate peer certificates */ |
| enumerator_t *inner; |
| /** reference to the credential_manager */ |
| private_credential_manager_t *this; |
| /** currently enumerating key */ |
| public_key_t *current; |
| /** credset wrapper around auth config */ |
| auth_cfg_wrapper_t *wrapper; |
| } public_enumerator_t; |
| |
| METHOD(enumerator_t, public_enumerate, bool, |
| public_enumerator_t *this, va_list args) |
| { |
| certificate_t *cert; |
| public_key_t **key; |
| auth_cfg_t **auth; |
| |
| VA_ARGS_VGET(args, key, auth); |
| |
| while (this->inner->enumerate(this->inner, &cert, auth)) |
| { |
| DESTROY_IF(this->current); |
| this->current = cert->get_public_key(cert); |
| if (this->current) |
| { |
| *key = this->current; |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| METHOD(enumerator_t, public_destroy, void, |
| public_enumerator_t *this) |
| { |
| DESTROY_IF(this->current); |
| this->inner->destroy(this->inner); |
| if (this->wrapper) |
| { |
| remove_local_set(this->this, &this->wrapper->set); |
| this->wrapper->destroy(this->wrapper); |
| } |
| this->this->lock->unlock(this->this->lock); |
| /* check for delayed certificate cache queue */ |
| cache_queue(this->this); |
| free(this); |
| } |
| |
| METHOD(credential_manager_t, create_public_enumerator, enumerator_t*, |
| private_credential_manager_t *this, key_type_t type, identification_t *id, |
| auth_cfg_t *auth, bool online) |
| { |
| public_enumerator_t *enumerator; |
| |
| INIT(enumerator, |
| .public = { |
| .enumerate = enumerator_enumerate_default, |
| .venumerate = _public_enumerate, |
| .destroy = _public_destroy, |
| }, |
| .inner = create_trusted_enumerator(this, type, id, online), |
| .this = this, |
| ); |
| if (auth) |
| { |
| enumerator->wrapper = auth_cfg_wrapper_create(auth); |
| add_local_set(this, &enumerator->wrapper->set, FALSE); |
| } |
| this->lock->read_lock(this->lock); |
| return &enumerator->public; |
| } |
| |
| /** |
| * Check if a helper contains a certificate as trust anchor |
| */ |
| static bool auth_contains_cacert(auth_cfg_t *auth, certificate_t *cert) |
| { |
| enumerator_t *enumerator; |
| identification_t *value; |
| auth_rule_t type; |
| bool found = FALSE; |
| |
| enumerator = auth->create_enumerator(auth); |
| while (enumerator->enumerate(enumerator, &type, &value)) |
| { |
| if (type == AUTH_RULE_CA_CERT && |
| cert->equals(cert, (certificate_t*)value)) |
| { |
| found = TRUE; |
| break; |
| } |
| } |
| enumerator->destroy(enumerator); |
| return found; |
| } |
| |
| /** |
| * build a trustchain from subject up to a trust anchor in trusted |
| */ |
| static auth_cfg_t *build_trustchain(private_credential_manager_t *this, |
| certificate_t *subject, auth_cfg_t *auth) |
| { |
| certificate_t *issuer, *current; |
| auth_cfg_t *trustchain; |
| int pathlen = 0; |
| bool has_anchor; |
| |
| trustchain = auth_cfg_create(); |
| has_anchor = auth->get(auth, AUTH_RULE_CA_CERT) != NULL; |
| current = subject->get_ref(subject); |
| while (TRUE) |
| { |
| if (auth_contains_cacert(auth, current)) |
| { |
| trustchain->add(trustchain, AUTH_RULE_CA_CERT, current); |
| return trustchain; |
| } |
| if (subject == current) |
| { |
| trustchain->add(trustchain, AUTH_RULE_SUBJECT_CERT, current); |
| } |
| else |
| { |
| if (!has_anchor && issued_by(this, current, current, NULL)) |
| { /* If no trust anchor specified, accept any CA */ |
| trustchain->add(trustchain, AUTH_RULE_CA_CERT, current); |
| return trustchain; |
| } |
| trustchain->add(trustchain, AUTH_RULE_IM_CERT, current); |
| } |
| if (pathlen++ > MAX_TRUST_PATH_LEN) |
| { |
| break; |
| } |
| issuer = get_issuer_cert(this, current, FALSE, NULL); |
| if (!issuer) |
| { |
| if (!has_anchor) |
| { /* If no trust anchor specified, accept incomplete chains */ |
| return trustchain; |
| } |
| break; |
| } |
| if (has_anchor && issuer->equals(issuer, current)) |
| { |
| issuer->destroy(issuer); |
| break; |
| } |
| current = issuer; |
| } |
| trustchain->destroy(trustchain); |
| return NULL; |
| } |
| |
| /** |
| * find a private key of a given certificate |
| */ |
| static private_key_t *get_private_by_cert(private_credential_manager_t *this, |
| certificate_t *cert, key_type_t type) |
| { |
| private_key_t *private = NULL; |
| identification_t *keyid; |
| chunk_t chunk; |
| public_key_t *public; |
| |
| public = cert->get_public_key(cert); |
| if (public) |
| { |
| if (public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &chunk)) |
| { |
| keyid = identification_create_from_encoding(ID_KEY_ID, chunk); |
| private = get_private_by_keyid(this, type, keyid); |
| keyid->destroy(keyid); |
| } |
| public->destroy(public); |
| } |
| return private; |
| } |
| |
| /** |
| * Move the actually used certificate to front, so it gets returned with get() |
| */ |
| static void prefer_cert(auth_cfg_t *auth, certificate_t *cert) |
| { |
| enumerator_t *enumerator; |
| auth_rule_t rule; |
| certificate_t *current; |
| |
| enumerator = auth->create_enumerator(auth); |
| while (enumerator->enumerate(enumerator, &rule, ¤t)) |
| { |
| if (rule == AUTH_RULE_SUBJECT_CERT) |
| { |
| current->get_ref(current); |
| auth->replace(auth, enumerator, AUTH_RULE_SUBJECT_CERT, cert); |
| cert = current; |
| } |
| } |
| enumerator->destroy(enumerator); |
| auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert); |
| } |
| |
| METHOD(credential_manager_t, get_private, private_key_t*, |
| private_credential_manager_t *this, key_type_t type, identification_t *id, |
| auth_cfg_t *auth) |
| { |
| enumerator_t *enumerator; |
| certificate_t *cert; |
| private_key_t *private = NULL; |
| auth_cfg_t *trustchain; |
| auth_rule_t rule; |
| |
| /* check if this is a lookup by key ID, and do it if so */ |
| if (id && id->get_type(id) == ID_KEY_ID) |
| { |
| private = get_private_by_keyid(this, type, id); |
| if (private) |
| { |
| return private; |
| } |
| } |
| |
| if (auth) |
| { |
| /* try to find a trustchain with one of the configured subject certs */ |
| enumerator = auth->create_enumerator(auth); |
| while (enumerator->enumerate(enumerator, &rule, &cert)) |
| { |
| if (rule == AUTH_RULE_SUBJECT_CERT) |
| { |
| private = get_private_by_cert(this, cert, type); |
| if (private) |
| { |
| trustchain = build_trustchain(this, cert, auth); |
| if (trustchain) |
| { |
| auth->merge(auth, trustchain, FALSE); |
| prefer_cert(auth, cert->get_ref(cert)); |
| trustchain->destroy(trustchain); |
| break; |
| } |
| private->destroy(private); |
| private = NULL; |
| } |
| } |
| } |
| enumerator->destroy(enumerator); |
| if (private) |
| { |
| return private; |
| } |
| |
| /* if none yielded a trustchain, enforce the first configured cert */ |
| cert = auth->get(auth, AUTH_RULE_SUBJECT_CERT); |
| if (cert) |
| { |
| private = get_private_by_cert(this, cert, type); |
| if (private) |
| { |
| trustchain = build_trustchain(this, cert, auth); |
| if (trustchain) |
| { |
| auth->merge(auth, trustchain, FALSE); |
| trustchain->destroy(trustchain); |
| } |
| return private; |
| } |
| } |
| |
| /* try to build a trust chain for each certificate found */ |
| enumerator = create_cert_enumerator(this, CERT_ANY, type, id, FALSE); |
| while (enumerator->enumerate(enumerator, &cert)) |
| { |
| private = get_private_by_cert(this, cert, type); |
| if (private) |
| { |
| trustchain = build_trustchain(this, cert, auth); |
| if (trustchain) |
| { |
| auth->merge(auth, trustchain, FALSE); |
| trustchain->destroy(trustchain); |
| break; |
| } |
| private->destroy(private); |
| private = NULL; |
| } |
| } |
| enumerator->destroy(enumerator); |
| } |
| |
| /* if no valid trustchain was found, fall back to the first usable cert */ |
| if (!private) |
| { |
| enumerator = create_cert_enumerator(this, CERT_ANY, type, id, FALSE); |
| while (enumerator->enumerate(enumerator, &cert)) |
| { |
| private = get_private_by_cert(this, cert, type); |
| if (private) |
| { |
| if (auth) |
| { |
| auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert->get_ref(cert)); |
| } |
| break; |
| } |
| } |
| enumerator->destroy(enumerator); |
| } |
| return private; |
| } |
| |
| METHOD(credential_manager_t, flush_cache, void, |
| private_credential_manager_t *this, certificate_type_t type) |
| { |
| if (this->cache) |
| { |
| this->cache->flush(this->cache, type); |
| } |
| } |
| |
| METHOD(credential_manager_t, add_set, void, |
| private_credential_manager_t *this, credential_set_t *set) |
| { |
| this->lock->write_lock(this->lock); |
| this->sets->insert_last(this->sets, set); |
| this->lock->unlock(this->lock); |
| } |
| |
| METHOD(credential_manager_t, remove_set, void, |
| private_credential_manager_t *this, credential_set_t *set) |
| { |
| this->lock->write_lock(this->lock); |
| this->sets->remove(this->sets, set, NULL); |
| this->lock->unlock(this->lock); |
| } |
| |
| METHOD(credential_manager_t, add_validator, void, |
| private_credential_manager_t *this, cert_validator_t *vdtr) |
| { |
| this->lock->write_lock(this->lock); |
| this->validators->insert_last(this->validators, vdtr); |
| this->lock->unlock(this->lock); |
| } |
| |
| METHOD(credential_manager_t, remove_validator, void, |
| private_credential_manager_t *this, cert_validator_t *vdtr) |
| { |
| this->lock->write_lock(this->lock); |
| this->validators->remove(this->validators, vdtr, NULL); |
| this->lock->unlock(this->lock); |
| } |
| |
| METHOD(credential_manager_t, destroy, void, |
| private_credential_manager_t *this) |
| { |
| cache_queue(this); |
| this->cache_queue->destroy(this->cache_queue); |
| if (this->cache) |
| { |
| this->sets->remove(this->sets, this->cache, NULL); |
| this->cache->destroy(this->cache); |
| } |
| this->sets->destroy(this->sets); |
| this->local_sets->destroy(this->local_sets); |
| this->exclusive_local_sets->destroy(this->exclusive_local_sets); |
| this->validators->destroy(this->validators); |
| this->lock->destroy(this->lock); |
| this->queue_mutex->destroy(this->queue_mutex); |
| free(this); |
| } |
| |
| /* |
| * see header file |
| */ |
| credential_manager_t *credential_manager_create() |
| { |
| private_credential_manager_t *this; |
| |
| INIT(this, |
| .public = { |
| .create_cert_enumerator = _create_cert_enumerator, |
| .create_shared_enumerator = _create_shared_enumerator, |
| .create_cdp_enumerator = _create_cdp_enumerator, |
| .get_cert = _get_cert, |
| .get_shared = _get_shared, |
| .get_private = _get_private, |
| .create_trusted_enumerator = _create_trusted_enumerator, |
| .create_public_enumerator = _create_public_enumerator, |
| .flush_cache = _flush_cache, |
| .cache_cert = _cache_cert, |
| .issued_by = _issued_by, |
| .add_set = _add_set, |
| .remove_set = _remove_set, |
| .add_local_set = _add_local_set, |
| .remove_local_set = _remove_local_set, |
| .add_validator = _add_validator, |
| .remove_validator = _remove_validator, |
| .set_hook = _set_hook, |
| .call_hook = _call_hook, |
| .destroy = _destroy, |
| }, |
| .sets = linked_list_create(), |
| .validators = linked_list_create(), |
| .cache_queue = linked_list_create(), |
| .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), |
| .queue_mutex = mutex_create(MUTEX_TYPE_DEFAULT), |
| ); |
| |
| this->local_sets = thread_value_create((thread_cleanup_t)this->sets->destroy); |
| this->exclusive_local_sets = thread_value_create((thread_cleanup_t)this->sets->destroy); |
| if (lib->settings->get_bool(lib->settings, "%s.cert_cache", TRUE, lib->ns)) |
| { |
| this->cache = cert_cache_create(); |
| this->sets->insert_first(this->sets, this->cache); |
| } |
| |
| return &this->public; |
| } |