strongswan/src/charon/credentials/credential_manager.c

1601 lines
41 KiB
C

/*
* Copyright (C) 2007 Martin Willi
* 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.
*
* $Id$
*/
#include <pthread.h>
#include "credential_manager.h"
#include <daemon.h>
#include <utils/mutex.h>
#include <utils/linked_list.h>
#include <credentials/sets/cert_cache.h>
#include <credentials/sets/auth_cfg_wrapper.h>
#include <credentials/sets/ocsp_response_wrapper.h>
#include <credentials/certificates/x509.h>
#include <credentials/certificates/crl.h>
#include <credentials/certificates/ocsp_request.h>
#include <credentials/certificates/ocsp_response.h>
#define MAX_CA_LEVELS 6
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
*/
pthread_key_t local_sets;
/**
* trust relationship and certificate cache
*/
cert_cache_t *cache;
/**
* certificates queued for persistent caching
*/
linked_list_t *cache_queue;
/**
* read-write lock to sets list
*/
rwlock_t *lock;
};
/** 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;
} sets_enumerator_t;
/**
* destroy a sets_enumerator_t
*/
static void sets_enumerator_destroy(sets_enumerator_t *this)
{
DESTROY_IF(this->global);
DESTROY_IF(this->local);
free(this);
}
/**
* sets_enumerator_t.enumerate
*/
static bool sets_enumerator_enumerate(sets_enumerator_t *this,
credential_set_t **set)
{
if (this->global)
{
if (this->global->enumerate(this->global, set))
{
return TRUE;
}
/* end of global sets, look for local */
this->global->destroy(this->global);
this->global = NULL;
}
if (this->local)
{
return this->local->enumerate(this->local, set);
}
return FALSE;
}
/**
* create an enumerator over both, global and local sets
*/
static enumerator_t *create_sets_enumerator(private_credential_manager_t *this)
{
linked_list_t *local;
sets_enumerator_t *enumerator = malloc_thing(sets_enumerator_t);
enumerator->public.enumerate = (void*)sets_enumerator_enumerate;
enumerator->public.destroy = (void*)sets_enumerator_destroy;
enumerator->global = this->sets->create_enumerator(this->sets);
enumerator->local = NULL;
local = pthread_getspecific(this->local_sets);
if (local)
{
enumerator->local = local->create_enumerator(local);
}
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);
}
/**
* Implementation of credential_manager_t.create_cert_enumerator.
*/
static enumerator_t *create_cert_enumerator(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);
}
/**
* Implementation of credential_manager_t.get_cert.
*/
static certificate_t *get_cert(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, &current))
{
/* TODO: best match? order by keyid, subject, sualtname */
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);
}
/**
* Implementation of credential_manager_t.create_cdp_enumerator.
*/
static enumerator_t * create_cdp_enumerator(private_credential_manager_t *this,
certificate_type_t type, identification_t *id)
{
cdp_data_t *data = malloc_thing(cdp_data_t);
data->this = this;
data->type = type;
data->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);
}
/**
* Implementation of credential_manager_t.create_private_enumerator.
*/
static enumerator_t* create_private_enumerator(
private_credential_manager_t *this,
key_type_t key, identification_t *keyid)
{
private_data_t *data;
data = malloc_thing(private_data_t);
data->this = this;
data->type = key;
data->keyid = keyid;
this->lock->read_lock(this->lock);
return enumerator_create_nested(create_sets_enumerator(this),
(void*)create_private, data,
(void*)destroy_private_data);
}
/**
* Implementation of credential_manager_t.get_private_by_keyid.
*/
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);
}
/**
* Implementation of credential_manager_t.create_shared_enumerator.
*/
static enumerator_t *create_shared_enumerator(private_credential_manager_t *this,
shared_key_type_t type,
identification_t *me, identification_t *other)
{
shared_data_t *data = malloc_thing(shared_data_t);
data->this = this;
data->type = type;
data->me = me;
data->other = other;
this->lock->read_lock(this->lock);
return enumerator_create_nested(create_sets_enumerator(this),
(void*)create_shared, data,
(void*)destroy_shared_data);
}
/**
* Implementation of credential_manager_t.get_shared.
*/
static shared_key_t *get_shared(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, &current, &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;
}
}
enumerator->destroy(enumerator);
return found;
}
/**
* add a credential set to the thread local list
*/
static void add_local_set(private_credential_manager_t *this,
credential_set_t *set)
{
linked_list_t *sets;
sets = pthread_getspecific(this->local_sets);
if (!sets)
{ /* first invocation */
sets = linked_list_create();
pthread_setspecific(this->local_sets, sets);
}
sets->insert_last(sets, set);
}
/**
* remove a credential set from the thread local list
*/
static void remove_local_set(private_credential_manager_t *this,
credential_set_t *set)
{
linked_list_t *sets;
sets = pthread_getspecific(this->local_sets);
sets->remove(sets, set, NULL);
}
/**
* Implementation of credential_manager_t.cache_cert.
*/
static void cache_cert(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);
}
else
{ /* we can't cache now as other threads are active, queue for later */
this->lock->read_lock(this->lock);
this->cache_queue->insert_last(this->cache_queue, cert->get_ref(cert));
}
this->lock->unlock(this->lock);
}
/**
* 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;
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);
}
}
/**
* forward declaration
*/
static enumerator_t *create_trusted_enumerator(private_credential_manager_t *this,
key_type_t type, identification_t *id, bool crl, bool ocsp);
/**
* Do an OCSP request
*/
static certificate_t *fetch_ocsp(private_credential_manager_t *this, char *url,
certificate_t *subject, certificate_t *issuer)
{
certificate_t *request, *response;
chunk_t send, receive;
/* TODO: requestor name, signature */
request = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST,
BUILD_CA_CERT, issuer,
BUILD_CERT, subject, BUILD_END);
if (!request)
{
DBG1(DBG_CFG, "generating ocsp request failed");
return NULL;
}
send = request->get_encoding(request);
request->destroy(request);
DBG1(DBG_CFG, " requesting ocsp status from '%s' ...", url);
if (lib->fetcher->fetch(lib->fetcher, url, &receive,
FETCH_REQUEST_DATA, send,
FETCH_REQUEST_TYPE, "application/ocsp-request",
FETCH_END) != SUCCESS)
{
DBG1(DBG_CFG, "ocsp request to %s failed", url);
chunk_free(&send);
return NULL;
}
chunk_free(&send);
response = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE,
BUILD_BLOB_ASN1_DER, receive, BUILD_END);
chunk_free(&receive);
if (!response)
{
DBG1(DBG_CFG, "parsing ocsp response failed");
return NULL;
}
return response;
}
/**
* check the signature of an OCSP response
*/
static bool verify_ocsp(private_credential_manager_t *this,
ocsp_response_t *response)
{
certificate_t *issuer, *subject;
identification_t *responder;
ocsp_response_wrapper_t *wrapper;
enumerator_t *enumerator;
bool verified = FALSE;
wrapper = ocsp_response_wrapper_create((ocsp_response_t*)response);
add_local_set(this, &wrapper->set);
subject = &response->certificate;
responder = subject->get_issuer(subject);
enumerator = create_trusted_enumerator(this, KEY_ANY, responder, FALSE, FALSE);
while (enumerator->enumerate(enumerator, &issuer, NULL))
{
if (this->cache->issued_by(this->cache, subject, issuer))
{
DBG1(DBG_CFG, " ocsp response correctly signed by \"%D\"",
issuer->get_subject(issuer));
verified = TRUE;
break;
}
}
enumerator->destroy(enumerator);
remove_local_set(this, &wrapper->set);
wrapper->destroy(wrapper);
return verified;
}
/**
* Get the better of two OCSP responses, and check for usable OCSP info
*/
static certificate_t *get_better_ocsp(private_credential_manager_t *this,
certificate_t *cand, certificate_t *best,
x509_t *subject, x509_t *issuer,
cert_validation_t *valid, bool cache)
{
ocsp_response_t *response;
time_t revocation, this_update, next_update, valid_until;
crl_reason_t reason;
bool revoked = FALSE;
response = (ocsp_response_t*)cand;
/* check ocsp signature */
if (!verify_ocsp(this, response))
{
DBG1(DBG_CFG, "ocsp response verification failed");
cand->destroy(cand);
return best;
}
/* check if response contains our certificate */
switch (response->get_status(response, subject, issuer, &revocation, &reason,
&this_update, &next_update))
{
case VALIDATION_REVOKED:
/* subject has been revoked by a valid OCSP response */
DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N",
&revocation, TRUE, crl_reason_names, reason);
revoked = TRUE;
break;
case VALIDATION_GOOD:
/* results in either good or stale */
break;
default:
case VALIDATION_FAILED:
/* candidate unusable, does not contain our cert */
DBG1(DBG_CFG, " ocsp response contains no status on our certificate");
cand->destroy(cand);
return best;
}
/* select the better of the two responses */
if (best == NULL || cand->is_newer(cand, best))
{
DESTROY_IF(best);
best = cand;
if (best->get_validity(best, NULL, NULL, &valid_until))
{
DBG1(DBG_CFG, " ocsp response is valid: until %T",
&valid_until, FALSE);
*valid = VALIDATION_GOOD;
if (cache)
{ /* cache non-stale only, stale certs get refetched */
cache_cert(this, best);
}
}
else
{
DBG1(DBG_CFG, " ocsp response is stale: since %T",
&valid_until, FALSE);
*valid = VALIDATION_STALE;
}
}
else
{
*valid = VALIDATION_STALE;
cand->destroy(cand);
}
if (revoked)
{ /* revoked always counts, even if stale */
*valid = VALIDATION_REVOKED;
}
return best;
}
/**
* validate a x509 certificate using OCSP
*/
static cert_validation_t check_ocsp(private_credential_manager_t *this,
x509_t *subject, x509_t *issuer,
auth_cfg_t *auth)
{
enumerator_t *enumerator;
cert_validation_t valid = VALIDATION_SKIPPED;
certificate_t *best = NULL, *current;
identification_t *keyid = NULL;
public_key_t *public;
char *uri = NULL;
/** lookup cache for valid OCSP responses */
enumerator = create_cert_enumerator(this, CERT_X509_OCSP_RESPONSE,
KEY_ANY, NULL, FALSE);
while (enumerator->enumerate(enumerator, &current))
{
current->get_ref(current);
best = get_better_ocsp(this, current, best, subject, issuer,
&valid, FALSE);
if (best && valid != VALIDATION_STALE)
{
DBG1(DBG_CFG, " using cached ocsp response");
break;
}
}
enumerator->destroy(enumerator);
/* derive the authorityKeyIdentifier from the issuer's public key */
current = &issuer->interface;
public = current->get_public_key(current);
if (public)
{
keyid = public->get_id(public, ID_PUBKEY_SHA1);
}
/** fetch from configured OCSP responder URLs */
if (keyid && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
{
enumerator = create_cdp_enumerator(this, CERT_X509_OCSP_RESPONSE, keyid);
while (enumerator->enumerate(enumerator, &uri))
{
current = fetch_ocsp(this, uri, &subject->interface,
&issuer->interface);
if (current)
{
best = get_better_ocsp(this, current, best, subject, issuer,
&valid, TRUE);
if (best && valid != VALIDATION_STALE)
{
break;
}
}
}
enumerator->destroy(enumerator);
}
DESTROY_IF(public);
/* fallback to URL fetching from subject certificate's URIs */
if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
{
enumerator = subject->create_ocsp_uri_enumerator(subject);
while (enumerator->enumerate(enumerator, &uri))
{
current = fetch_ocsp(this, uri, &subject->interface,
&issuer->interface);
if (current)
{
best = get_better_ocsp(this, current, best, subject, issuer,
&valid, TRUE);
if (best && valid != VALIDATION_STALE)
{
break;
}
}
}
enumerator->destroy(enumerator);
}
/* an uri was found, but no result. switch validation state to failed */
if (valid == VALIDATION_SKIPPED && uri)
{
valid = VALIDATION_FAILED;
}
if (auth)
{
auth->add(auth, AUTH_RULE_OCSP_VALIDATION, valid);
if (valid == VALIDATION_GOOD)
{ /* successful OCSP check fulfills also CRL constraint */
auth->add(auth, AUTH_RULE_CRL_VALIDATION, VALIDATION_GOOD);
}
}
DESTROY_IF(best);
return valid;
}
/**
* fetch a CRL from an URL
*/
static certificate_t* fetch_crl(private_credential_manager_t *this, char *url)
{
certificate_t *crl;
chunk_t chunk;
DBG1(DBG_CFG, " fetching crl from '%s' ...", url);
if (lib->fetcher->fetch(lib->fetcher, url, &chunk, FETCH_END) != SUCCESS)
{
DBG1(DBG_CFG, "crl fetching failed");
return NULL;
}
crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
BUILD_BLOB_ASN1_DER, chunk, BUILD_END);
chunk_free(&chunk);
if (!crl)
{
DBG1(DBG_CFG, "crl fetched successfully but parsing failed");
return NULL;
}
return crl;
}
/**
* check the signature of an CRL
*/
static bool verify_crl(private_credential_manager_t *this, certificate_t *crl)
{
certificate_t *issuer;
enumerator_t *enumerator;
bool verified = FALSE;
enumerator = create_trusted_enumerator(this, KEY_ANY, crl->get_issuer(crl),
FALSE, FALSE);
while (enumerator->enumerate(enumerator, &issuer, NULL))
{
if (this->cache->issued_by(this->cache, crl, issuer))
{
DBG1(DBG_CFG, " crl correctly signed by \"%D\"",
issuer->get_subject(issuer));
verified = TRUE;
break;
}
}
enumerator->destroy(enumerator);
return verified;
}
/**
* Get the better of two CRLs, and check for usable CRL info
*/
static certificate_t *get_better_crl(private_credential_manager_t *this,
certificate_t *cand, certificate_t *best,
x509_t *subject, x509_t *issuer,
cert_validation_t *valid, bool cache)
{
enumerator_t *enumerator;
time_t revocation, valid_until;
crl_reason_t reason;
chunk_t serial;
crl_t *crl;
/* check CRL signature */
if (!verify_crl(this, cand))
{
DBG1(DBG_CFG, "crl response verification failed");
cand->destroy(cand);
return best;
}
crl = (crl_t*)cand;
enumerator = crl->create_enumerator(crl);
while (enumerator->enumerate(enumerator, &serial, &revocation, &reason))
{
if (chunk_equals(serial, subject->get_serial(subject)))
{
DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N",
&revocation, TRUE, crl_reason_names, reason);
*valid = VALIDATION_REVOKED;
enumerator->destroy(enumerator);
DESTROY_IF(best);
return cand;
}
}
enumerator->destroy(enumerator);
/* select the better of the two CRLs */
if (best == NULL || cand->is_newer(cand, best))
{
DESTROY_IF(best);
best = cand;
if (best->get_validity(best, NULL, NULL, &valid_until))
{
DBG1(DBG_CFG, " crl is valid: until %T", &valid_until, FALSE);
*valid = VALIDATION_GOOD;
if (cache)
{ /* we cache non-stale crls only, as a stale crls are refetched */
cache_cert(this, best);
}
}
else
{
DBG1(DBG_CFG, " crl is stale: since %T", &valid_until, FALSE);
*valid = VALIDATION_STALE;
}
}
else
{
*valid = VALIDATION_STALE;
cand->destroy(cand);
}
return best;
}
/**
* validate a x509 certificate using CRL
*/
static cert_validation_t check_crl(private_credential_manager_t *this,
x509_t *subject, x509_t *issuer,
auth_cfg_t *auth)
{
cert_validation_t valid = VALIDATION_SKIPPED;
identification_t *keyid = NULL;
certificate_t *best = NULL;
certificate_t *current;
public_key_t *public;
enumerator_t *enumerator;
char *uri = NULL;
/* derive the authorityKeyIdentifier from the issuer's public key */
current = &issuer->interface;
public = current->get_public_key(current);
if (public)
{
keyid = public->get_id(public, ID_PUBKEY_SHA1);
}
/* find a cached crl by authorityKeyIdentifier */
if (keyid)
{
enumerator = create_cert_enumerator(this, CERT_X509_CRL, KEY_ANY,
keyid, FALSE);
while (enumerator->enumerate(enumerator, &current))
{
current->get_ref(current);
best = get_better_crl(this, current, best, subject, issuer,
&valid, FALSE);
if (best && valid != VALIDATION_STALE)
{
DBG1(DBG_CFG, " using cached crl");
break;
}
}
enumerator->destroy(enumerator);
}
/* fallback to fetching crls from credential sets cdps */
if (keyid && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
{
enumerator = create_cdp_enumerator(this, CERT_X509_CRL, keyid);
while (enumerator->enumerate(enumerator, &uri))
{
current = fetch_crl(this, uri);
if (current)
{
best = get_better_crl(this, current, best, subject, issuer,
&valid, TRUE);
if (best && valid != VALIDATION_STALE)
{
break;
}
}
}
enumerator->destroy(enumerator);
}
DESTROY_IF(public);
/* fallback to fetching crls from cdps from subject's certificate */
if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
{
enumerator = subject->create_crl_uri_enumerator(subject);
while (enumerator->enumerate(enumerator, &uri))
{
current = fetch_crl(this, uri);
if (current)
{
best = get_better_crl(this, current, best, subject, issuer,
&valid, TRUE);
if (best && valid != VALIDATION_STALE)
{
break;
}
}
}
enumerator->destroy(enumerator);
}
/* an uri was found, but no result. switch validation state to failed */
if (valid == VALIDATION_SKIPPED && uri)
{
valid = VALIDATION_FAILED;
}
if (auth)
{
if (valid == VALIDATION_SKIPPED)
{ /* if we skipped CRL validation, we use the result of OCSP for
* constraint checking */
auth->add(auth, AUTH_RULE_CRL_VALIDATION,
auth->get(auth, AUTH_RULE_OCSP_VALIDATION));
}
else
{
auth->add(auth, AUTH_RULE_CRL_VALIDATION, valid);
}
}
DESTROY_IF(best);
return valid;
}
/**
* check a certificate for its lifetime
*/
static bool check_certificate(private_credential_manager_t *this,
certificate_t *subject, certificate_t *issuer,
bool crl, bool ocsp, auth_cfg_t *auth)
{
time_t not_before, not_after;
if (!subject->get_validity(subject, NULL, &not_before, &not_after))
{
DBG1(DBG_CFG, "subject certificate invalid (valid from %T to %T)",
&not_before, TRUE, &not_after, TRUE);
return FALSE;
}
if (!issuer->get_validity(issuer, NULL, &not_before, &not_after))
{
DBG1(DBG_CFG, "issuer certificate invalid (valid from %T to %T)",
&not_before, TRUE, &not_after, TRUE);
return FALSE;
}
if (issuer->get_type(issuer) == CERT_X509 &&
subject->get_type(subject) == CERT_X509)
{
if (ocsp || crl)
{
DBG1(DBG_CFG, "checking certificate status of \"%D\"",
subject->get_subject(subject));
}
if (ocsp)
{
switch (check_ocsp(this, (x509_t*)subject, (x509_t*)issuer, auth))
{
case VALIDATION_GOOD:
DBG1(DBG_CFG, "certificate status is good");
return TRUE;
case VALIDATION_REVOKED:
/* has already been logged */
return FALSE;
case VALIDATION_SKIPPED:
DBG2(DBG_CFG, "ocsp check skipped, no ocsp found");
break;
case VALIDATION_STALE:
DBG1(DBG_CFG, "ocsp information stale, fallback to crl");
break;
case VALIDATION_FAILED:
DBG1(DBG_CFG, "ocsp check failed, fallback to crl");
break;
}
}
if (crl)
{
switch (check_crl(this, (x509_t*)subject, (x509_t*)issuer, auth))
{
case VALIDATION_GOOD:
DBG1(DBG_CFG, "certificate status is good");
return TRUE;
case VALIDATION_REVOKED:
/* has already been logged */
return FALSE;
case VALIDATION_FAILED:
case VALIDATION_SKIPPED:
DBG1(DBG_CFG, "certificate status is not available");
break;
case VALIDATION_STALE:
DBG1(DBG_CFG, "certificate status is unknown, crl is stale");
break;
}
}
}
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)
{
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 (this->cache->issued_by(this->cache, subject, candidate))
{
issuer = candidate->get_ref(candidate);
break;
}
}
enumerator->destroy(enumerator);
return issuer;
}
/**
* 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 crl, bool ocsp)
{
certificate_t *current, *issuer;
auth_cfg_t *auth;
u_int level = 0;
auth = auth_cfg_create();
current = subject->get_ref(subject);
while (level++ < MAX_CA_LEVELS)
{
issuer = get_issuer_cert(this, current, TRUE);
if (issuer)
{
/* accept only self-signed CAs as trust anchor */
if (this->cache->issued_by(this->cache, issuer, issuer))
{
auth->add(auth, AUTH_RULE_CA_CERT, issuer->get_ref(issuer));
DBG1(DBG_CFG, " using trusted ca certificate \"%D\"",
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 "
"\"%D\"", issuer->get_subject(issuer));
}
}
else
{
issuer = get_issuer_cert(this, current, FALSE);
if (issuer)
{
if (current->equals(current, issuer))
{
DBG1(DBG_CFG, " self-signed certificate \"%D\" is not trusted",
current->get_subject(current));
issuer->destroy(issuer);
break;
}
auth->add(auth, AUTH_RULE_IM_CERT, issuer->get_ref(issuer));
DBG1(DBG_CFG, " using untrusted intermediate certificate "
"\"%D\"", issuer->get_subject(issuer));
}
else
{
DBG1(DBG_CFG, "no issuer certificate found for \"%D\"",
current->get_subject(current));
break;
}
}
if (!check_certificate(this, current, issuer, crl, ocsp,
current == subject ? auth : NULL))
{
trusted = FALSE;
issuer->destroy(issuer);
break;
}
current->destroy(current);
current = issuer;
if (trusted)
{
break;
}
}
current->destroy(current);
if (level > MAX_CA_LEVELS)
{
DBG1(DBG_CFG, "maximum ca path length of %d levels reached", level);
}
if (trusted)
{
result->merge(result, auth, FALSE);
}
auth->destroy(auth);
return trusted;
}
/**
* 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 checking */
bool crl;
/** TRUE to do OCSP checking */
bool ocsp;
/** pretrusted certificate we have served at first invocation */
certificate_t *pretrusted;
/** currently enumerating auth config */
auth_cfg_t *auth;
} trusted_enumerator_t;
/**
* Implements trusted_enumerator_t.enumerate
*/
static bool trusted_enumerate(trusted_enumerator_t *this,
certificate_t **cert, auth_cfg_t **auth)
{
certificate_t *current;
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 (this->this->cache->issued_by(this->this->cache,
this->pretrusted, this->pretrusted) ||
verify_trust_chain(this->this, this->pretrusted, this->auth,
TRUE, this->crl, this->ocsp))
{
this->auth->add(this->auth, AUTH_RULE_SUBJECT_CERT,
this->pretrusted->get_ref(this->pretrusted));
DBG1(DBG_CFG, " using trusted certificate \"%D\"",
this->pretrusted->get_subject(this->pretrusted));
*cert = 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, &current))
{
if (this->pretrusted &&
this->pretrusted->equals(this->pretrusted, current))
{ /* skip pretrusted certificate we already served */
continue;
}
DBG1(DBG_CFG, " using certificate \"%D\"",
current->get_subject(current));
if (verify_trust_chain(this->this, current, this->auth, FALSE,
this->crl, this->ocsp))
{
*cert = current;
if (auth)
{
*auth = this->auth;
}
return TRUE;
}
}
return FALSE;
}
/**
* Implements trusted_enumerator_t.destroy
*/
static void trusted_destroy(trusted_enumerator_t *this)
{
DESTROY_IF(this->pretrusted);
DESTROY_IF(this->auth);
DESTROY_IF(this->candidates);
free(this);
}
/**
* create an enumerator over trusted certificates and their trustchain
*/
static enumerator_t *create_trusted_enumerator(private_credential_manager_t *this,
key_type_t type, identification_t *id, bool crl, bool ocsp)
{
trusted_enumerator_t *enumerator = malloc_thing(trusted_enumerator_t);
enumerator->public.enumerate = (void*)trusted_enumerate;
enumerator->public.destroy = (void*)trusted_destroy;
enumerator->candidates = NULL;
enumerator->this = this;
enumerator->type = type;
enumerator->id = id;
enumerator->crl = crl;
enumerator->ocsp = ocsp;
enumerator->pretrusted = NULL;
enumerator->auth = NULL;
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;
/**
* Implements public_enumerator_t.enumerate
*/
static bool public_enumerate(public_enumerator_t *this,
public_key_t **key, auth_cfg_t **auth)
{
certificate_t *cert;
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;
}
/**
* Implements public_enumerator_t.destroy
*/
static void public_destroy(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);
}
/**
* Implementation of credential_manager_t.create_public_enumerator.
*/
static enumerator_t* create_public_enumerator(private_credential_manager_t *this,
key_type_t type, identification_t *id, auth_cfg_t *auth)
{
public_enumerator_t *enumerator = malloc_thing(public_enumerator_t);
enumerator->public.enumerate = (void*)public_enumerate;
enumerator->public.destroy = (void*)public_destroy;
enumerator->inner = create_trusted_enumerator(this, type, id, TRUE, TRUE);
enumerator->this = this;
enumerator->current = NULL;
enumerator->wrapper = NULL;
if (auth)
{
enumerator->wrapper = auth_cfg_wrapper_create(auth);
add_local_set(this, &enumerator->wrapper->set);
}
this->lock->read_lock(this->lock);
return &enumerator->public;
}
/**
* Check if a certificate's keyid is contained in the auth helper
*/
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;
u_int level = 0;
trustchain = auth_cfg_create();
current = auth->get(auth, AUTH_RULE_CA_CERT);
if (!current)
{
/* no trust anchor specified, return this cert only */
trustchain->add(trustchain, AUTH_RULE_SUBJECT_CERT,
subject->get_ref(subject));
return trustchain;
}
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
{
trustchain->add(trustchain, AUTH_RULE_IM_CERT, current);
}
issuer = get_issuer_cert(this, current, FALSE);
if (!issuer || issuer->equals(issuer, current) || level > MAX_CA_LEVELS)
{
DESTROY_IF(issuer);
break;
}
current = issuer;
level++;
}
trustchain->destroy(trustchain);
return NULL;
}
/**
* find a private key of a give 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;
public_key_t *public;
public = cert->get_public_key(cert);
if (public)
{
keyid = public->get_id(public, ID_PUBKEY_INFO_SHA1);
if (keyid)
{
private = get_private_by_keyid(this, type, keyid);
}
public->destroy(public);
}
return private;
}
/**
* Implementation of credential_manager_t.get_private.
*/
static private_key_t *get_private(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;
/* check if this is a lookup by key ID, and do it if so */
if (id)
{
switch (id->get_type(id))
{
case ID_PUBKEY_SHA1:
case ID_PUBKEY_INFO_SHA1:
case ID_KEY_ID:
return get_private_by_keyid(this, type, id);
default:
break;
}
}
/* if a specific certificate is preferred, check for a matching key */
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)
{
auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert->get_ref(cert));
break;
}
}
enumerator->destroy(enumerator);
}
return private;
}
/**
* Implementation of credential_manager_t.flush_cache.
*/
static void flush_cache(private_credential_manager_t *this,
certificate_type_t type)
{
this->cache->flush(this->cache, type);
}
/**
* Implementation of credential_manager_t.add_set.
*/
static void add_set(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);
}
/**
* Implementation of credential_manager_t.remove_set.
*/
static void remove_set(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);
}
/**
* Implementation of credential_manager_t.destroy
*/
static void destroy(private_credential_manager_t *this)
{
cache_queue(this);
this->cache_queue->destroy(this->cache_queue);
this->sets->remove(this->sets, this->cache, NULL);
this->sets->destroy(this->sets);
pthread_key_delete(this->local_sets);
this->cache->destroy(this->cache);
this->lock->destroy(this->lock);
free(this);
}
/*
* see header file
*/
credential_manager_t *credential_manager_create()
{
private_credential_manager_t *this = malloc_thing(private_credential_manager_t);
this->public.create_cert_enumerator = (enumerator_t *(*)(credential_manager_t *this,certificate_type_t cert, key_type_t key,identification_t *id,bool))create_cert_enumerator;
this->public.create_shared_enumerator = (enumerator_t *(*)(credential_manager_t *this, shared_key_type_t type,identification_t *me, identification_t *other))create_shared_enumerator;
this->public.create_cdp_enumerator = (enumerator_t *(*)(credential_manager_t*, certificate_type_t type, identification_t *id))create_cdp_enumerator;
this->public.get_cert = (certificate_t *(*)(credential_manager_t *this,certificate_type_t cert, key_type_t key,identification_t *, bool))get_cert;
this->public.get_shared = (shared_key_t *(*)(credential_manager_t *this,shared_key_type_t type,identification_t *me, identification_t *other))get_shared;
this->public.get_private = (private_key_t*(*)(credential_manager_t*, key_type_t type, identification_t *, auth_cfg_t*))get_private;
this->public.create_public_enumerator = (enumerator_t*(*)(credential_manager_t*, key_type_t type, identification_t *id, auth_cfg_t *aut))create_public_enumerator;
this->public.flush_cache = (void(*)(credential_manager_t*, certificate_type_t type))flush_cache;
this->public.cache_cert = (void(*)(credential_manager_t*, certificate_t *cert))cache_cert;
this->public.add_set = (void(*)(credential_manager_t*, credential_set_t *set))add_set;
this->public.remove_set = (void(*)(credential_manager_t*, credential_set_t *set))remove_set;
this->public.destroy = (void(*)(credential_manager_t*))destroy;
this->sets = linked_list_create();
pthread_key_create(&this->local_sets, (void*)this->sets->destroy);
this->cache = cert_cache_create();
this->cache_queue = linked_list_create();
this->sets->insert_first(this->sets, this->cache);
this->lock = rwlock_create(RWLOCK_DEFAULT);
return &this->public;
}