Moved CRL/OCSP checking to a dedicated plugin called revocation
This commit is contained 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([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([random], [disable RNG implementation on top of /dev/(u)random.])
|
||||||
ARG_DISBL_SET([x509], [disable X509 certificate implementation plugin.])
|
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([pubkey], [disable RAW public key support plugin.])
|
||||||
ARG_DISBL_SET([pkcs1], [disable PKCS1 key decoding plugin.])
|
ARG_DISBL_SET([pkcs1], [disable PKCS1 key decoding plugin.])
|
||||||
ARG_DISBL_SET([pgp], [disable PGP 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"
|
libstrongswan_plugins=${libstrongswan_plugins}" x509"
|
||||||
pluto_plugins=${pluto_plugins}" x509"
|
pluto_plugins=${pluto_plugins}" x509"
|
||||||
fi
|
fi
|
||||||
|
if test x$revocation = xtrue; then
|
||||||
|
libstrongswan_plugins=${libstrongswan_plugins}" revocation"
|
||||||
|
fi
|
||||||
if test x$pubkey = xtrue; then
|
if test x$pubkey = xtrue; then
|
||||||
libstrongswan_plugins=${libstrongswan_plugins}" pubkey"
|
libstrongswan_plugins=${libstrongswan_plugins}" pubkey"
|
||||||
pluto_plugins=${pluto_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_GMP, test x$gmp = xtrue)
|
||||||
AM_CONDITIONAL(USE_RANDOM, test x$random = xtrue)
|
AM_CONDITIONAL(USE_RANDOM, test x$random = xtrue)
|
||||||
AM_CONDITIONAL(USE_X509, test x$x509 = 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_PUBKEY, test x$pubkey = xtrue)
|
||||||
AM_CONDITIONAL(USE_PKCS1, test x$pkcs1 = xtrue)
|
AM_CONDITIONAL(USE_PKCS1, test x$pkcs1 = xtrue)
|
||||||
AM_CONDITIONAL(USE_PGP, test x$pgp = xtrue)
|
AM_CONDITIONAL(USE_PGP, test x$pgp = xtrue)
|
||||||
@@ -928,6 +933,7 @@ AC_OUTPUT(
|
|||||||
src/libstrongswan/plugins/hmac/Makefile
|
src/libstrongswan/plugins/hmac/Makefile
|
||||||
src/libstrongswan/plugins/xcbc/Makefile
|
src/libstrongswan/plugins/xcbc/Makefile
|
||||||
src/libstrongswan/plugins/x509/Makefile
|
src/libstrongswan/plugins/x509/Makefile
|
||||||
|
src/libstrongswan/plugins/revocation/Makefile
|
||||||
src/libstrongswan/plugins/pubkey/Makefile
|
src/libstrongswan/plugins/pubkey/Makefile
|
||||||
src/libstrongswan/plugins/pkcs1/Makefile
|
src/libstrongswan/plugins/pkcs1/Makefile
|
||||||
src/libstrongswan/plugins/pgp/Makefile
|
src/libstrongswan/plugins/pgp/Makefile
|
||||||
|
|||||||
@@ -209,6 +209,13 @@ if MONOLITHIC
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if USE_REVOCATION
|
||||||
|
SUBDIRS += plugins/revocation
|
||||||
|
if MONOLITHIC
|
||||||
|
libstrongswan_la_LIBADD += plugins/revocation/libstrongswan-revocation.la
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
if USE_PUBKEY
|
if USE_PUBKEY
|
||||||
SUBDIRS += plugins/pubkey
|
SUBDIRS += plugins/pubkey
|
||||||
if MONOLITHIC
|
if MONOLITHIC
|
||||||
|
|||||||
@@ -20,15 +20,10 @@
|
|||||||
#include <threading/thread_value.h>
|
#include <threading/thread_value.h>
|
||||||
#include <threading/mutex.h>
|
#include <threading/mutex.h>
|
||||||
#include <threading/rwlock.h>
|
#include <threading/rwlock.h>
|
||||||
#include <selectors/traffic_selector.h>
|
|
||||||
#include <utils/linked_list.h>
|
#include <utils/linked_list.h>
|
||||||
#include <credentials/sets/cert_cache.h>
|
#include <credentials/sets/cert_cache.h>
|
||||||
#include <credentials/sets/auth_cfg_wrapper.h>
|
#include <credentials/sets/auth_cfg_wrapper.h>
|
||||||
#include <credentials/sets/ocsp_response_wrapper.h>
|
|
||||||
#include <credentials/certificates/x509.h>
|
#include <credentials/certificates/x509.h>
|
||||||
#include <credentials/certificates/crl.h>
|
|
||||||
#include <credentials/certificates/ocsp_request.h>
|
|
||||||
#include <credentials/certificates/ocsp_response.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum length of a certificate trust chain
|
* 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);
|
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
|
* check a certificate for its lifetime
|
||||||
*/
|
*/
|
||||||
@@ -976,46 +485,6 @@ static bool check_certificate(private_credential_manager_t *this,
|
|||||||
pathlen, pathlen_constraint);
|
pathlen, pathlen_constraint);
|
||||||
return FALSE;
|
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);
|
enumerator = this->validators->create_enumerator(this->validators);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 <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 "revocation_plugin.h"
|
||||||
|
|
||||||
|
#include <library.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -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 <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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup revocation revocation
|
||||||
|
* @ingroup plugins
|
||||||
|
*
|
||||||
|
* @defgroup revocation_plugin revocation_plugin
|
||||||
|
* @{ @ingroup revocation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef REVOCATION_PLUGIN_H_
|
||||||
|
#define REVOCATION_PLUGIN_H_
|
||||||
|
|
||||||
|
#include <plugins/plugin.h>
|
||||||
|
|
||||||
|
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_ @}*/
|
||||||
@@ -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 <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 "revocation_validator.h"
|
||||||
|
|
||||||
|
#include <debug.h>
|
||||||
|
#include <credentials/certificates/x509.h>
|
||||||
|
#include <credentials/certificates/crl.h>
|
||||||
|
#include <credentials/certificates/ocsp_request.h>
|
||||||
|
#include <credentials/certificates/ocsp_response.h>
|
||||||
|
#include <credentials/sets/ocsp_response_wrapper.h>
|
||||||
|
#include <selectors/traffic_selector.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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 <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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup revocation_validator revocation_validator
|
||||||
|
* @{ @ingroup revocation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef REVOCATION_VALIDATOR_H_
|
||||||
|
#define REVOCATION_VALIDATOR_H_
|
||||||
|
|
||||||
|
#include <credentials/cert_validator.h>
|
||||||
|
|
||||||
|
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_ @}*/
|
||||||
Reference in New Issue
Block a user