diff options
Diffstat (limited to 'src/libstrongswan/crypto/ca.c')
-rw-r--r-- | src/libstrongswan/crypto/ca.c | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/src/libstrongswan/crypto/ca.c b/src/libstrongswan/crypto/ca.c new file mode 100644 index 000000000..1f566a098 --- /dev/null +++ b/src/libstrongswan/crypto/ca.c @@ -0,0 +1,788 @@ +/** + * @file ca.c + * + * @brief Implementation of ca_info_t. + * + */ + +/* + * Copyright (C) 2007 Andreas Steffen + * 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 <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <pthread.h> + +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "certinfo.h" +#include "ocsp.h" + +#include <library.h> +#include <debug.h> +#include <utils/linked_list.h> +#include <utils/identification.h> +#include <utils/fetcher.h> + +typedef struct private_ca_info_t private_ca_info_t; + +/** + * Private data of a ca_info_t object. + */ +struct private_ca_info_t { + /** + * Public interface for this ca info record + */ + ca_info_t public; + + /** + * Name of the ca info record + */ + char *name; + + /** + * Time when ca info record was installed + */ + time_t installed; + + /** + * Distinguished Name of the CA + */ + x509_t *cacert; + + /** + * List of crl URIs + */ + linked_list_t *crluris; + + /** + * List of ocsp URIs + */ + linked_list_t *ocspuris; + + /** + * CRL issued by this ca + */ + crl_t *crl; + + /** + * List of certificate info records + */ + linked_list_t *certinfos; + + /** + * mutex controls access to the elements: + * name, crluris, ocspuris, crl, and certinfos + */ + pthread_mutex_t mutex; +}; + +/** + * static options set by ca_info_set_options() + */ +static bool cache_crls = FALSE; +static u_int crl_check_interval = 0; + +/** + * Implements ca_info_t.equals + */ +static bool equals(const private_ca_info_t *this, const private_ca_info_t *that) +{ + return chunk_equals(this->cacert->get_keyid(this->cacert), + that->cacert->get_keyid(that->cacert)); +} + +/** + * Implements ca_info_t.equals_name_release_info + */ +static bool equals_name_release_info(private_ca_info_t *this, const char *name) +{ + bool found; + + pthread_mutex_lock(&(this->mutex)); + found = this->name != NULL && streq(this->name, name); + + if (found) + { + this->crluris->destroy_offset(this->crluris, + offsetof(identification_t, destroy)); + this->crluris = linked_list_create(); + + this->ocspuris->destroy_offset(this->ocspuris, + offsetof(identification_t, destroy)); + this->ocspuris = linked_list_create(); + + free(this->name); + this->name = NULL; + } + + pthread_mutex_unlock(&(this->mutex)); + return found; +} + +/** + * Implements ca_info_t.is_crl_issuer + */ +static bool is_cert_issuer(private_ca_info_t *this, const x509_t *cert) +{ + return cert->is_issuer(cert, this->cacert); +} + +/** + * Implements ca_info_t.is_crl_issuer + */ +static bool is_crl_issuer(private_ca_info_t *this, const crl_t *crl) +{ + return crl->is_issuer(crl, this->cacert); +} + +/** + * Implements ca_info_t.has_crl + */ +static bool has_crl(private_ca_info_t *this) +{ + bool found; + + pthread_mutex_lock(&(this->mutex)); + found = this->crl != NULL; + pthread_mutex_unlock(&(this->mutex)); + + return found; +} + +/** + * Implements ca_info_t.has_certinfos + */ +static bool has_certinfos(private_ca_info_t *this) +{ + bool found; + + pthread_mutex_lock(&(this->mutex)); + found = this->certinfos->get_count(this->certinfos) > 0; + pthread_mutex_unlock(&(this->mutex)); + + return found; +} + +/** + * Implements ca_info_t.add_crl + */ +static void add_crl(private_ca_info_t *this, crl_t *crl) +{ + pthread_mutex_lock(&(this->mutex)); + + if (this->crl) + { + if (crl->is_newer(crl, this->crl)) + { + this->crl->destroy(this->crl); + this->crl = crl; + DBG1(" this crl is newer - existing crl replaced"); + } + else + { + crl->destroy(crl); + DBG1(" this crl is not newer - existing crl retained"); + } + } + else + { + this->crl = crl; + DBG2(" crl added"); + } + + pthread_mutex_unlock(&(this->mutex)); +} + +/** + * Implements ca_info_t.list_crl + */ +static void list_crl(private_ca_info_t *this, FILE *out, bool utc) +{ + pthread_mutex_lock(&(this->mutex)); + + fprintf(out, "%#U\n", this->crl, utc); + + pthread_mutex_unlock(&(this->mutex)); +} + +/** + * Implements ca_info_t.list_certinfos + */ +static void list_certinfos(private_ca_info_t *this, FILE *out, bool utc) +{ + pthread_mutex_lock(&(this->mutex)); + + fprintf(out," authname: '%D'\n", this->cacert->get_subject(this->cacert)); + { + chunk_t authkey = this->cacert->get_subjectKeyID(this->cacert); + + fprintf(out," authkey: %#B\n", &authkey); + } + { + iterator_t *iterator = this->certinfos->create_iterator(this->certinfos, TRUE); + certinfo_t *certinfo; + + while (iterator->iterate(iterator, (void**)&certinfo)) + { + fprintf(out, "%#Y\n", certinfo, utc); + } + iterator->destroy(iterator); + } + + pthread_mutex_unlock(&(this->mutex)); +} + +/** + * Find an exact copy of an identification in a linked list + */ +static identification_t* find_identification(linked_list_t *list, identification_t *id) +{ + identification_t *found_id = NULL, *current_id; + + iterator_t *iterator = list->create_iterator(list, TRUE); + + while (iterator->iterate(iterator, (void**)¤t_id)) + { + if (id->equals(id, current_id)) + { + found_id = current_id; + break; + } + } + iterator->destroy(iterator); + + return found_id; +} + +/** + * Add a unique identification to a linked list + */ +static identification_t *add_identification(linked_list_t *list, identification_t *id) +{ + identification_t *found_id = find_identification(list, id); + + if (found_id) + { + id->destroy(id); + return found_id; + } + else + { + list->insert_last(list, (void*)id); + return id; + } +} + +/** + * Implements ca_info_t.add_crluri + */ +static void add_crluri(private_ca_info_t *this, chunk_t uri) +{ + if (uri.len < 6 || + (strncasecmp(uri.ptr, "http", 4) != 0 && + strncasecmp(uri.ptr, "ldap", 4) != 0 && + strncasecmp(uri.ptr, "file", 4) != 0 && + strncasecmp(uri.ptr, "ftp", 3) != 0)) + { + DBG1(" invalid crl uri '%#B'", uri); + return; + } + else + { + identification_t *crluri = identification_create_from_encoding(ID_DER_ASN1_GN_URI, uri); + + pthread_mutex_lock(&(this->mutex)); + add_identification(this->crluris, crluri); + pthread_mutex_unlock(&(this->mutex)); + } +} + +/** + * Implements ca_info_t.add_ocspuri + */ +static void add_ocspuri(private_ca_info_t *this, chunk_t uri) +{ + if (uri.len < 7 || strncasecmp(uri.ptr, "http", 4) != 0) + { + DBG1(" invalid ocsp uri '%.*s'", uri.len, uri.ptr); + return; + } + else + { + identification_t *ocspuri = identification_create_from_encoding(ID_DER_ASN1_GN_URI, uri); + + pthread_mutex_lock(&(this->mutex)); + add_identification(this->ocspuris, ocspuri); + pthread_mutex_unlock(&(this->mutex)); + } +} + +/** + * Implements ca_info_t.add_info. + */ +void add_info (private_ca_info_t *this, const private_ca_info_t *that) +{ + pthread_mutex_lock(&(this->mutex)); + + if (this->name == NULL && that->name != NULL) + { + this->name = strdup(that->name); + } + + pthread_mutex_unlock(&(this->mutex)); + + { + identification_t *uri; + + iterator_t *iterator = that->crluris->create_iterator(that->crluris, TRUE); + + while (iterator->iterate(iterator, (void**)&uri)) + { + add_crluri(this, uri->get_encoding(uri)); + } + iterator->destroy(iterator); + } + + { + identification_t *uri; + + iterator_t *iterator = that->ocspuris->create_iterator(that->ocspuris, TRUE); + + while (iterator->iterate(iterator, (void**)&uri)) + { + add_ocspuri(this, uri->get_encoding(uri)); + } + iterator->destroy(iterator); + } +} + +/** + * Implements ca_info_t.get_certificate. + */ +static x509_t* get_certificate(private_ca_info_t* this) +{ + return this->cacert; +} + +/** + * caches a crl by saving it to a given crl directory + */ +void cache_crl(private_ca_info_t* this, const char *crl_dir, crl_t *crl) +{ + char buffer[BUF_LEN]; + char *path; + char *pos = buffer; + int len = BUF_LEN; + int n; + + chunk_t authKeyID = this->cacert->get_subjectKeyID(this->cacert); + chunk_t uri; + + uri.ptr = buffer; + uri.len = 7 + strlen(crl_dir) + 1 + 2*authKeyID.len + 4; + + if (uri.len >= BUF_LEN) + { + DBG1("file uri exceeds buffer length of %d bytes - crl not saved", BUF_LEN); + return; + } + + /* print the file uri prefix */ + n = snprintf(pos, len, "file://"); + pos += n; len -= n; + + /* remember the start of the path string */ + path = pos; + + /* print the default crl directory path */ + n = snprintf(pos, len, "%s/", crl_dir); + pos += n; len -= n; + + /* create and print a unique crl filename derived from the authKeyID */ + while (authKeyID.len-- > 0) + { + n = snprintf(pos, len, "%02x", *authKeyID.ptr++); + pos += n; len -= n; + } + + /* add the file suffix */ + n = snprintf(pos, len, ".crl"); + + if (crl->write_to_file(crl, path, 0022, TRUE)) + { + identification_t *crluri = identification_create_from_encoding(ID_DER_ASN1_GN_URI, uri); + + add_identification(this->crluris, crluri); + } +} + +/** + * Implements ca_info_t.verify_by_crl. + */ +static cert_status_t verify_by_crl(private_ca_info_t* this, certinfo_t *certinfo, + const char *crl_dir) +{ + rsa_public_key_t *issuer_public_key = this->cacert->get_public_key(this->cacert); + bool stale; + + pthread_mutex_lock(&(this->mutex)); + if (this->crl == NULL) + { + stale = TRUE; + DBG1("no crl is locally available"); + } + else + { + stale = !this->crl->is_valid(this->crl); + DBG1("crl is %s", stale? "stale":"valid"); + } + + if (stale && crl_check_interval > 0) + { + iterator_t *iterator = this->crluris->create_iterator(this->crluris, TRUE); + identification_t *uri; + + while (iterator->iterate(iterator, (void**)&uri)) + { + fetcher_t *fetcher; + char uri_string[BUF_LEN]; + chunk_t uri_chunk = uri->get_encoding(uri); + chunk_t response_chunk; + + snprintf(uri_string, BUF_LEN, "%.*s", uri_chunk.len, uri_chunk.ptr); + fetcher = fetcher_create(uri_string); + + response_chunk = fetcher->get(fetcher); + fetcher->destroy(fetcher); + if (response_chunk.ptr != NULL) + { + crl_t *crl = crl_create_from_chunk(response_chunk); + + if (crl == NULL) + { + free(response_chunk.ptr); + continue; + } + if (!is_crl_issuer(this, crl)) + { + DBG1(" fetched crl has wrong issuer"); + crl->destroy(crl); + continue; + } + if (!crl->verify(crl, issuer_public_key)) + { + DBG1("fetched crl signature is invalid"); + crl->destroy(crl); + continue; + } + DBG2("fetched crl signature is valid"); + + if (this->crl == NULL) + { + this->crl = crl; + } + else if (crl->is_newer(crl, this->crl)) + { + this->crl->destroy(this->crl); + this->crl = crl; + DBG1("this crl is newer - existing crl replaced"); + } + else + { + crl->destroy(crl); + DBG1("this crl is not newer - existing crl retained"); + continue; + } + if (crl->is_valid(crl)) + { + if (cache_crls && strncasecmp(uri_string, "file", 4) != 0) + { + cache_crl(this, crl_dir, crl); + } + /* we found a valid crl and therefore exit the fetch loop */ + break; + } + else + { + DBG1("fetched crl is stale"); + } + } + } + iterator->destroy(iterator); + } + + if (this->crl) + { + if (!this->crl->verify(this->crl, issuer_public_key)) + { + DBG1("crl signature is invalid"); + goto ret; + } + DBG2("crl signature is valid"); + + this->crl->get_status(this->crl, certinfo); + } + +ret: + pthread_mutex_unlock(&(this->mutex)); + return certinfo->get_status(certinfo); +} + +/** + * Implements ca_info_t.verify_by_ocsp. + */ +static cert_status_t verify_by_ocsp(private_ca_info_t* this, + certinfo_t *certinfo, + credential_store_t *credentials) +{ + bool stale; + iterator_t *iterator; + certinfo_t *cached_certinfo = NULL; + int comparison = 1; + + pthread_mutex_lock(&(this->mutex)); + + /* do we support OCSP at all? */ + if (this->ocspuris->get_count(this->ocspuris) == 0) + { + goto ret; + } + + iterator = this->certinfos->create_iterator(this->certinfos, TRUE); + + /* find the list insertion point in alphabetical order */ + while(iterator->iterate(iterator, (void**)&cached_certinfo)) + { + comparison = certinfo->compare_serialNumber(certinfo, cached_certinfo); + + if (comparison <= 0) + { + break; + } + } + + /* do we have a valid certinfo_t for this serial number in our cache? */ + if (comparison == 0) + { + stale = cached_certinfo->get_nextUpdate(cached_certinfo) < time(NULL); + DBG1("ocsp status in cache is %s", stale ? "stale":"fresh"); + } + else + { + stale = TRUE; + DBG1("ocsp status is not in cache"); + } + + if (stale) + { + ocsp_t *ocsp; + + ocsp = ocsp_create(this->cacert, this->ocspuris); + ocsp->fetch(ocsp, certinfo, credentials); + if (certinfo->get_status(certinfo) != CERT_UNDEFINED) + { + if (comparison != 0) + { + cached_certinfo = certinfo_create(certinfo->get_serialNumber(certinfo)); + + if (comparison > 0) + { + iterator->insert_after(iterator, (void *)cached_certinfo); + } + else + { + iterator->insert_before(iterator, (void *)cached_certinfo); + } + } + cached_certinfo->update(cached_certinfo, certinfo); + } + ocsp->destroy(ocsp); + } + else + { + certinfo->update(certinfo, cached_certinfo); + } + + iterator->destroy(iterator); + +ret: + pthread_mutex_unlock(&(this->mutex)); + return certinfo->get_status(certinfo); +} + +/** + * Implements ca_info_t.purge_ocsp + */ +static void purge_ocsp(private_ca_info_t *this) +{ + pthread_mutex_lock(&(this->mutex)); + + this->certinfos->destroy_offset(this->certinfos, + offsetof(certinfo_t, destroy)); + this->certinfos = linked_list_create(); + + pthread_mutex_unlock(&(this->mutex)); +} + +/** + * Implements ca_info_t.destroy + */ +static void destroy(private_ca_info_t *this) +{ + this->crluris->destroy_offset(this->crluris, + offsetof(identification_t, destroy)); + this->ocspuris->destroy_offset(this->ocspuris, + offsetof(identification_t, destroy)); + this->certinfos->destroy_offset(this->certinfos, + offsetof(certinfo_t, destroy)); + DESTROY_IF(this->crl); + free(this->name); + free(this); +} + +/** + * output handler in printf() + */ +static int print(FILE *stream, const struct printf_info *info, + const void *const *args) +{ + private_ca_info_t *this = *((private_ca_info_t**)(args[0])); + bool utc = TRUE; + int written = 0; + const x509_t *cacert; + + if (info->alt) + { + utc = *((bool*)args[1]); + } + if (this == NULL) + { + return fprintf(stream, "(null)"); + } + + pthread_mutex_lock(&(this->mutex)); + written += fprintf(stream, "%#T", &this->installed, utc); + + if (this->name) + { + written += fprintf(stream, ", \"%s\"\n", this->name); + } + else + { + written += fprintf(stream, "\n"); + } + + cacert = this->cacert; + written += fprintf(stream, " authname: '%D'\n", cacert->get_subject(cacert)); + { + chunk_t authkey = cacert->get_subjectKeyID(cacert); + + written += fprintf(stream, " authkey: %#B\n", &authkey); + } + { + chunk_t keyid = cacert->get_keyid(cacert); + + written += fprintf(stream, " keyid: %#B\n", &keyid); + } + { + identification_t *crluri; + iterator_t *iterator = this->crluris->create_iterator(this->crluris, TRUE); + bool first = TRUE; + + while (iterator->iterate(iterator, (void**)&crluri)) + { + written += fprintf(stream, " %s '%D'\n", + first? "crluris:":" ", crluri); + first = FALSE; + } + iterator->destroy(iterator); + } + { + identification_t *ocspuri; + iterator_t *iterator = this->ocspuris->create_iterator(this->ocspuris, TRUE); + bool first = TRUE; + + while (iterator->iterate(iterator, (void**)&ocspuri)) + { + written += fprintf(stream, " %s '%D'\n", + first? "ocspuris:":" ", ocspuri); + first = FALSE; + } + iterator->destroy(iterator); + } + pthread_mutex_unlock(&(this->mutex)); + return written; +} + +/** + * register printf() handlers + */ +static void __attribute__ ((constructor))print_register() +{ + register_printf_function(PRINTF_CAINFO, print, arginfo_ptr_alt_ptr_int); +} + +/* + * Described in header. + */ +void ca_info_set_options(bool cache, u_int interval) +{ + cache_crls = cache; + crl_check_interval = interval; +} + +/* + * Described in header. + */ +ca_info_t *ca_info_create(const char *name, x509_t *cacert) +{ + private_ca_info_t *this = malloc_thing(private_ca_info_t); + + /* initialize */ + this->installed = time(NULL); + this->name = (name == NULL)? NULL:strdup(name); + this->cacert = cacert; + this->crluris = linked_list_create(); + this->ocspuris = linked_list_create(); + this->certinfos = linked_list_create(); + this->crl = NULL; + + /* initialize the mutex */ + pthread_mutex_init(&(this->mutex), NULL); + + /* public functions */ + this->public.equals = (bool (*) (const ca_info_t*,const ca_info_t*))equals; + this->public.equals_name_release_info = (bool (*) (ca_info_t*,const char*))equals_name_release_info; + this->public.is_cert_issuer = (bool (*) (ca_info_t*,const x509_t*))is_cert_issuer; + this->public.is_crl_issuer = (bool (*) (ca_info_t*,const crl_t*))is_crl_issuer; + this->public.add_info = (void (*) (ca_info_t*,const ca_info_t*))add_info; + this->public.add_crl = (void (*) (ca_info_t*,crl_t*))add_crl; + this->public.has_crl = (bool (*) (ca_info_t*))has_crl; + this->public.has_certinfos = (bool (*) (ca_info_t*))has_certinfos; + this->public.list_crl = (void (*) (ca_info_t*,FILE*,bool))list_crl; + this->public.list_certinfos = (void (*) (ca_info_t*,FILE*,bool))list_certinfos; + this->public.add_crluri = (void (*) (ca_info_t*,chunk_t))add_crluri; + this->public.add_ocspuri = (void (*) (ca_info_t*,chunk_t))add_ocspuri; + this->public.get_certificate = (x509_t* (*) (ca_info_t*))get_certificate; + this->public.verify_by_crl = (cert_status_t (*) (ca_info_t*,certinfo_t*, const char*))verify_by_crl; + this->public.verify_by_ocsp = (cert_status_t (*) (ca_info_t*,certinfo_t*,credential_store_t*))verify_by_ocsp; + this->public.purge_ocsp = (void (*) (ca_info_t*))purge_ocsp; + this->public.destroy = (void (*) (ca_info_t*))destroy; + + return &this->public; +} |