www.pudn.com > strongswan-4.2.4.rar > credential_manager.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 .
 *
 * 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: credential_manager.c 3953 2008-05-14 06:49:31Z martin $
 */

/* some clibs need it for rwlocks */
#define _GNU_SOURCE
#include 

#include "credential_manager.h"

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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;
	
	/**
	 * read-write lock to sets list
	 */
	pthread_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)
{
	pthread_rwlock_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;
	
	pthread_rwlock_rdlock(&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, ¤t))
	{
		/* 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)
{
	pthread_rwlock_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;
	
	pthread_rwlock_rdlock(&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)
{
	pthread_rwlock_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.get_private_by_keyid.
 */
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;
	pthread_rwlock_rdlock(&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)
{
	pthread_rwlock_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;
	
	pthread_rwlock_rdlock(&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, ¤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;
		}
	}
	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;
	
	pthread_rwlock_rdlock(&this->lock);
	enumerator = this->sets->create_enumerator(this->sets);
	while (enumerator->enumerate(enumerator, &set))
	{
		set->cache_cert(set, cert);
	}
	enumerator->destroy(enumerator);
	pthread_rwlock_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->get_ref(issuer),
						BUILD_CERT, subject->get_ref(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);
	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, 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_info_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, ¤t))
	{
		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_item(auth, AUTHZ_OCSP_VALIDATION, &valid);
	}
	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);
	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, 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_info_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;
	
	/* 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, ¤t))
		{
			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)
	{
		auth->add_item(auth, AUTHZ_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_info_t *auth)
{
	time_t not_before, not_after;
	
	if (!subject->get_validity(subject, NULL, ¬_before, ¬_after))
	{
		DBG1(DBG_CFG, "subject certificate invalid (valid from %T to %T)",
			 ¬_before, ¬_after);
		return FALSE;
	}
	if (!issuer->get_validity(issuer, NULL, ¬_before, ¬_after))
	{
		DBG1(DBG_CFG, "issuer certificate invalid (valid from %T to %T)",
			 ¬_before, ¬_after);
		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_info_t *result,
							   bool trusted, bool crl, bool ocsp)
{
	certificate_t *current, *issuer;
	auth_info_t *auth;
	u_int level = 0;
	
	auth = auth_info_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_item(auth, AUTHZ_CA_CERT, issuer);
				DBG1(DBG_CFG, "  using trusted ca certificate \"%D\"",
					 issuer->get_subject(issuer));
				trusted = TRUE;
			}
			else
			{
				auth->add_item(auth, AUTHZ_IM_CERT, 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_item(auth, AUTHZ_IM_CERT, 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);
	}
	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 info */
	auth_info_t *auth;
} trusted_enumerator_t;

/**
 * Implements trusted_enumerator_t.enumerate
 */
static bool trusted_enumerate(trusted_enumerator_t *this,
							  certificate_t **cert, auth_info_t **auth)
{
	certificate_t *current;
	
	DESTROY_IF(this->auth);
	this->auth = auth_info_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))
			{
				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, ¤t))
	{
		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 */
	auth_info_wrapper_t *wrapper;
} public_enumerator_t;

/**
 * Implements public_enumerator_t.enumerate
 */
static bool public_enumerate(public_enumerator_t *this,
							 public_key_t **key, auth_info_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);
	}
	pthread_rwlock_unlock(&this->this->lock);
	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_info_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_info_wrapper_create(auth);
		add_local_set(this, &enumerator->wrapper->set);
	}
	pthread_rwlock_rdlock(&this->lock);
	return &enumerator->public;
}

/**
 * Check if a certificate's keyid is contained in the auth helper
 */
static bool auth_contains_cacert(auth_info_t *auth, certificate_t *cert)
{
	enumerator_t *enumerator;
	identification_t *value;
	auth_item_t type;
	bool found = FALSE;

	enumerator = auth->create_item_enumerator(auth);
	while (enumerator->enumerate(enumerator, &type, &value))
	{
		if (type == AUTHN_CA_CERT && cert->equals(cert, (certificate_t*)value))
		{
			found = TRUE;
			break;
		}
		if (type == AUTHN_CA_CERT_KEYID)
		{
			public_key_t *public;
			identification_t *certid, *keyid;
			
			public = cert->get_public_key(cert);
			if (public)
			{
				keyid = (identification_t*)value;
				certid = public->get_id(public, keyid->get_type(keyid));
				if (certid && certid->equals(certid, keyid))
				{
					public->destroy(public);
					found = TRUE;
					break;
				}
				public->destroy(public);
			}
		}
	}
	enumerator->destroy(enumerator);
	return found;
}

/**
 * build a trustchain from subject up to a trust anchor in trusted
 */
static auth_info_t *build_trustchain(private_credential_manager_t *this,
									 certificate_t *subject, auth_info_t *auth)
{	
	certificate_t *issuer, *current;
	auth_info_t *trustchain;
	u_int level = 0;
	
	trustchain = auth_info_create();
	
	if (!auth->get_item(auth, AUTHN_CA_CERT, (void**)¤t))
	{
		/* no trust anchor specified, return this cert only */
		trustchain->add_item(trustchain, AUTHZ_SUBJECT_CERT, subject);
		return trustchain;
	}
	current = subject->get_ref(subject);
	while (TRUE)
	{
		if (auth_contains_cacert(auth, current))
		{
			trustchain->add_item(trustchain, AUTHZ_CA_CERT, current);
			current->destroy(current);
			return trustchain;
		}
		if (subject == current)
		{
			trustchain->add_item(trustchain, AUTHZ_SUBJECT_CERT, current);
		}
		else
		{
			trustchain->add_item(trustchain, AUTHZ_IM_CERT, current);
		}
		issuer = get_issuer_cert(this, current, FALSE);
		if (!issuer || issuer->equals(issuer, current) || level > MAX_CA_LEVELS)
		{
			DESTROY_IF(issuer);
			current->destroy(current);
			break;
		}
		current->destroy(current);
		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_info_t *auth)
{
	enumerator_t *enumerator;
	certificate_t *cert;
	private_key_t *private = NULL;
	auth_info_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;
		}
	}
	
	/* try to build a trustchain 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);
				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_item(auth, AUTHZ_SUBJECT_CERT, 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)
{
	pthread_rwlock_wrlock(&this->lock);
	this->sets->insert_last(this->sets, set);
	pthread_rwlock_unlock(&this->lock);
}

/**
 * Implementation of credential_manager_t.remove_set.
 */
static void remove_set(private_credential_manager_t *this, credential_set_t *set)
{
	pthread_rwlock_wrlock(&this->lock);
	this->sets->remove(this->sets, set, NULL);
	pthread_rwlock_unlock(&this->lock);
}

/**
 * Implementation of credential_manager_t.destroy
 */
static void destroy(private_credential_manager_t *this)
{
	this->sets->remove(this->sets, this->cache, NULL);
	this->sets->destroy(this->sets);
	pthread_key_delete(this->local_sets);
	this->cache->destroy(this->cache);
	pthread_rwlock_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_info_t*))get_private;
	this->public.create_public_enumerator = (enumerator_t*(*)(credential_manager_t*, key_type_t type, identification_t *id, auth_info_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->sets->insert_first(this->sets, this->cache);
	pthread_rwlock_init(&this->lock, NULL);
	
	return &this->public;
}