From c2e5cee413f5a6c5f9b7ab173b56e73232581e6d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 5 Jul 2010 15:26:35 +0200 Subject: [PATCH] Moved CRL/OCSP checking to a dedicated plugin called revocation --- configure.in | 6 + src/libstrongswan/Makefile.am | 7 + .../credentials/credential_manager.c | 531 ---------------- .../plugins/revocation/Makefile.am | 16 + .../plugins/revocation/revocation_plugin.c | 61 ++ .../plugins/revocation/revocation_plugin.h | 42 ++ .../plugins/revocation/revocation_validator.c | 582 ++++++++++++++++++ .../plugins/revocation/revocation_validator.h | 49 ++ 8 files changed, 763 insertions(+), 531 deletions(-) create mode 100644 src/libstrongswan/plugins/revocation/Makefile.am create mode 100644 src/libstrongswan/plugins/revocation/revocation_plugin.c create mode 100644 src/libstrongswan/plugins/revocation/revocation_plugin.h create mode 100644 src/libstrongswan/plugins/revocation/revocation_validator.c create mode 100644 src/libstrongswan/plugins/revocation/revocation_validator.h diff --git a/configure.in b/configure.in index 5ddd062e4..a7a3171cd 100644 --- a/configure.in +++ b/configure.in @@ -78,6 +78,7 @@ ARG_DISBL_SET([fips-prf], [disable FIPS PRF software implementation plugin ARG_DISBL_SET([gmp], [disable GNU MP (libgmp) based crypto implementation plugin.]) ARG_DISBL_SET([random], [disable RNG implementation on top of /dev/(u)random.]) ARG_DISBL_SET([x509], [disable X509 certificate implementation plugin.]) +ARG_DISBL_SET([revocation], [disable X509 CRL/OCSP revocation check plugin.]) ARG_DISBL_SET([pubkey], [disable RAW public key support plugin.]) ARG_DISBL_SET([pkcs1], [disable PKCS1 key decoding plugin.]) ARG_DISBL_SET([pgp], [disable PGP key decoding plugin.]) @@ -707,6 +708,9 @@ if test x$x509 = xtrue; then libstrongswan_plugins=${libstrongswan_plugins}" x509" pluto_plugins=${pluto_plugins}" x509" fi +if test x$revocation = xtrue; then + libstrongswan_plugins=${libstrongswan_plugins}" revocation" +fi if test x$pubkey = xtrue; then libstrongswan_plugins=${libstrongswan_plugins}" pubkey" pluto_plugins=${pluto_plugins}" pubkey" @@ -803,6 +807,7 @@ AM_CONDITIONAL(USE_FIPS_PRF, test x$fips_prf = xtrue) AM_CONDITIONAL(USE_GMP, test x$gmp = xtrue) AM_CONDITIONAL(USE_RANDOM, test x$random = xtrue) AM_CONDITIONAL(USE_X509, test x$x509 = xtrue) +AM_CONDITIONAL(USE_REVOCATION, test x$revocation = xtrue) AM_CONDITIONAL(USE_PUBKEY, test x$pubkey = xtrue) AM_CONDITIONAL(USE_PKCS1, test x$pkcs1 = xtrue) AM_CONDITIONAL(USE_PGP, test x$pgp = xtrue) @@ -928,6 +933,7 @@ AC_OUTPUT( src/libstrongswan/plugins/hmac/Makefile src/libstrongswan/plugins/xcbc/Makefile src/libstrongswan/plugins/x509/Makefile + src/libstrongswan/plugins/revocation/Makefile src/libstrongswan/plugins/pubkey/Makefile src/libstrongswan/plugins/pkcs1/Makefile src/libstrongswan/plugins/pgp/Makefile diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index d90773db7..0ef26c831 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -209,6 +209,13 @@ if MONOLITHIC endif endif +if USE_REVOCATION + SUBDIRS += plugins/revocation +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/revocation/libstrongswan-revocation.la +endif +endif + if USE_PUBKEY SUBDIRS += plugins/pubkey if MONOLITHIC diff --git a/src/libstrongswan/credentials/credential_manager.c b/src/libstrongswan/credentials/credential_manager.c index 8df232b30..3b671c71f 100644 --- a/src/libstrongswan/credentials/credential_manager.c +++ b/src/libstrongswan/credentials/credential_manager.c @@ -20,15 +20,10 @@ #include #include #include -#include #include #include #include -#include #include -#include -#include -#include /** * Maximum length of a certificate trust chain @@ -451,492 +446,6 @@ static void cache_queue(private_credential_manager_t *this) this->queue_mutex->unlock(this->queue_mutex); } -/** - * forward declaration - */ -static enumerator_t *create_trusted_enumerator(private_credential_manager_t *this, - key_type_t type, identification_t *id, bool online); - -/** - * 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); - while (enumerator->enumerate(enumerator, &issuer, NULL)) - { - if (this->cache->issued_by(this->cache, subject, issuer)) - { - DBG1(DBG_CFG, " ocsp response correctly signed by \"%Y\"", - 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 || certificate_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; - chunk_t chunk; - 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 && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk)) - { - keyid = identification_create_from_encoding(ID_KEY_ID, chunk); - } - /** 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); - DESTROY_IF(keyid); - - /* 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); - while (enumerator->enumerate(enumerator, &issuer, NULL)) - { - if (this->cache->issued_by(this->cache, crl, issuer)) - { - DBG1(DBG_CFG, " crl correctly signed by \"%Y\"", - 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 || crl_is_newer(crl, (crl_t*)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; - chunk_t chunk; - char *uri = NULL; - - /* derive the authorityKeyIdentifier from the issuer's public key */ - current = &issuer->interface; - public = current->get_public_key(current); - if (public && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk)) - { - keyid = identification_create_from_encoding(ID_KEY_ID, chunk); - - /* find a cached crl by authorityKeyIdentifier */ - 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 (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); - } - keyid->destroy(keyid); - } - 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 */ @@ -976,46 +485,6 @@ static bool check_certificate(private_credential_manager_t *this, pathlen, pathlen_constraint); return FALSE; } - - if (online) - { - DBG1(DBG_CFG, "checking certificate status of \"%Y\"", - subject->get_subject(subject)); - 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; - } - 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; - } - } } enumerator = this->validators->create_enumerator(this->validators); diff --git a/src/libstrongswan/plugins/revocation/Makefile.am b/src/libstrongswan/plugins/revocation/Makefile.am new file mode 100644 index 000000000..fb6d01926 --- /dev/null +++ b/src/libstrongswan/plugins/revocation/Makefile.am @@ -0,0 +1,16 @@ + +INCLUDES = -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = -rdynamic + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-revocation.la +else +plugin_LTLIBRARIES = libstrongswan-revocation.la +endif + +libstrongswan_revocation_la_SOURCES = \ + revocation_plugin.h revocation_plugin.c \ + revocation_validator.h revocation_validator.c + +libstrongswan_revocation_la_LDFLAGS = -module -avoid-version diff --git a/src/libstrongswan/plugins/revocation/revocation_plugin.c b/src/libstrongswan/plugins/revocation/revocation_plugin.c new file mode 100644 index 000000000..d352a9583 --- /dev/null +++ b/src/libstrongswan/plugins/revocation/revocation_plugin.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * 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. + */ + +#include "revocation_plugin.h" + +#include +#include "revocation_validator.h" + +typedef struct private_revocation_plugin_t private_revocation_plugin_t; + +/** + * private data of revocation_plugin + */ +struct private_revocation_plugin_t { + + /** + * public functions + */ + revocation_plugin_t public; + + /** + * Validator implementation instance. + */ + revocation_validator_t *validator; +}; + +METHOD(plugin_t, destroy, void, + private_revocation_plugin_t *this) +{ + lib->credmgr->remove_validator(lib->credmgr, &this->validator->validator); + this->validator->destroy(this->validator); + free(this); +} + +/* + * see header file + */ +plugin_t *revocation_plugin_create() +{ + private_revocation_plugin_t *this; + + INIT(this, + .public.plugin.destroy = _destroy, + .validator = revocation_validator_create(), + ); + lib->credmgr->add_validator(lib->credmgr, &this->validator->validator); + + return &this->public.plugin; +} diff --git a/src/libstrongswan/plugins/revocation/revocation_plugin.h b/src/libstrongswan/plugins/revocation/revocation_plugin.h new file mode 100644 index 000000000..fb886d53c --- /dev/null +++ b/src/libstrongswan/plugins/revocation/revocation_plugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * 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. + */ + +/** + * @defgroup revocation revocation + * @ingroup plugins + * + * @defgroup revocation_plugin revocation_plugin + * @{ @ingroup revocation + */ + +#ifndef REVOCATION_PLUGIN_H_ +#define REVOCATION_PLUGIN_H_ + +#include + +typedef struct revocation_plugin_t revocation_plugin_t; + +/** + * X509 certificate revocation support using CRL and OCSP. + */ +struct revocation_plugin_t { + + /** + * Implements plugin_t. interface. + */ + plugin_t plugin; +}; + +#endif /** REVOCATION_PLUGIN_H_ @}*/ diff --git a/src/libstrongswan/plugins/revocation/revocation_validator.c b/src/libstrongswan/plugins/revocation/revocation_validator.c new file mode 100644 index 000000000..e7802d428 --- /dev/null +++ b/src/libstrongswan/plugins/revocation/revocation_validator.c @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * Copyright (C) 2009 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 . + * + * 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 "revocation_validator.h" + +#include +#include +#include +#include +#include +#include +#include + +typedef struct private_revocation_validator_t private_revocation_validator_t; + +/** + * Private data of an revocation_validator_t object. + */ +struct private_revocation_validator_t { + + /** + * Public revocation_validator_t interface. + */ + revocation_validator_t public; +}; + +/** + * Do an OCSP request + */ +static certificate_t *fetch_ocsp(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(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); + lib->credmgr->add_local_set(lib->credmgr, &wrapper->set); + + subject = &response->certificate; + responder = subject->get_issuer(subject); + enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr, + KEY_ANY, responder, FALSE); + while (enumerator->enumerate(enumerator, &issuer, NULL)) + { + if (lib->credmgr->issued_by(lib->credmgr, subject, issuer)) + { + DBG1(DBG_CFG, " ocsp response correctly signed by \"%Y\"", + issuer->get_subject(issuer)); + verified = TRUE; + break; + } + } + enumerator->destroy(enumerator); + + lib->credmgr->remove_local_set(lib->credmgr, &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(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(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 || certificate_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 */ + lib->credmgr->cache_cert(lib->credmgr, 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(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; + chunk_t chunk; + char *uri = NULL; + + /** lookup cache for valid OCSP responses */ + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + CERT_X509_OCSP_RESPONSE, KEY_ANY, NULL, FALSE); + while (enumerator->enumerate(enumerator, ¤t)) + { + current->get_ref(current); + best = get_better_ocsp(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 && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk)) + { + keyid = identification_create_from_encoding(ID_KEY_ID, chunk); + } + /** fetch from configured OCSP responder URLs */ + if (keyid && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED) + { + enumerator = lib->credmgr->create_cdp_enumerator(lib->credmgr, + CERT_X509_OCSP_RESPONSE, keyid); + while (enumerator->enumerate(enumerator, &uri)) + { + current = fetch_ocsp(uri, &subject->interface, &issuer->interface); + if (current) + { + best = get_better_ocsp(current, best, subject, issuer, + &valid, TRUE); + if (best && valid != VALIDATION_STALE) + { + break; + } + } + } + enumerator->destroy(enumerator); + } + DESTROY_IF(public); + DESTROY_IF(keyid); + + /* 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(uri, &subject->interface, &issuer->interface); + if (current) + { + best = get_better_ocsp(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(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(certificate_t *crl) +{ + certificate_t *issuer; + enumerator_t *enumerator; + bool verified = FALSE; + + enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr, + KEY_ANY, crl->get_issuer(crl), FALSE); + while (enumerator->enumerate(enumerator, &issuer, NULL)) + { + if (lib->credmgr->issued_by(lib->credmgr, crl, issuer)) + { + DBG1(DBG_CFG, " crl correctly signed by \"%Y\"", + 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(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(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 || crl_is_newer(crl, (crl_t*)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 */ + lib->credmgr->cache_cert(lib->credmgr, 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(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; + chunk_t chunk; + char *uri = NULL; + + /* derive the authorityKeyIdentifier from the issuer's public key */ + current = &issuer->interface; + public = current->get_public_key(current); + if (public && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk)) + { + keyid = identification_create_from_encoding(ID_KEY_ID, chunk); + + /* find a cached crl by authorityKeyIdentifier */ + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + CERT_X509_CRL, KEY_ANY, keyid, FALSE); + while (enumerator->enumerate(enumerator, ¤t)) + { + current->get_ref(current); + best = get_better_crl(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 (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED) + { + enumerator = lib->credmgr->create_cdp_enumerator(lib->credmgr, + CERT_X509_CRL, keyid); + while (enumerator->enumerate(enumerator, &uri)) + { + current = fetch_crl(uri); + if (current) + { + best = get_better_crl(current, best, subject, issuer, + &valid, TRUE); + if (best && valid != VALIDATION_STALE) + { + break; + } + } + } + enumerator->destroy(enumerator); + } + keyid->destroy(keyid); + } + 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(uri); + if (current) + { + best = get_better_crl(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; +} + +METHOD(cert_validator_t, validate, bool, + private_revocation_validator_t *this, certificate_t *subject, + certificate_t *issuer, bool online, int pathlen, auth_cfg_t *auth) +{ + if (subject->get_type(subject) == CERT_X509 && + issuer->get_type(issuer) == CERT_X509 && + online) + { + DBG1(DBG_CFG, "checking certificate status of \"%Y\"", + subject->get_subject(subject)); + switch (check_ocsp((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; + } + switch (check_crl((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; +} + +METHOD(revocation_validator_t, destroy, void, + private_revocation_validator_t *this) +{ + free(this); +} + +/** + * See header + */ +revocation_validator_t *revocation_validator_create() +{ + private_revocation_validator_t *this; + + INIT(this, + .public = { + .validator.validate = _validate, + .destroy = _destroy, + }, + ); + + return &this->public; +} diff --git a/src/libstrongswan/plugins/revocation/revocation_validator.h b/src/libstrongswan/plugins/revocation/revocation_validator.h new file mode 100644 index 000000000..82cbde26b --- /dev/null +++ b/src/libstrongswan/plugins/revocation/revocation_validator.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * 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. + */ + +/** + * @defgroup revocation_validator revocation_validator + * @{ @ingroup revocation + */ + +#ifndef REVOCATION_VALIDATOR_H_ +#define REVOCATION_VALIDATOR_H_ + +#include + +typedef struct revocation_validator_t revocation_validator_t; + +/** + * Certificate validator doing CRL/OCSP checking of X509 certificates. + */ +struct revocation_validator_t { + + /** + * Implements cert_validator_t interface. + */ + cert_validator_t validator; + + /** + * Destroy a revocation_validator_t. + */ + void (*destroy)(revocation_validator_t *this); +}; + +/** + * Create a revocation_validator instance. + */ +revocation_validator_t *revocation_validator_create(); + +#endif /** REVOCATION_VALIDATOR_H_ @}*/