strongswan/src/pluto/crl.c

539 lines
12 KiB
C

/* Support of X.509 certificate revocation lists (CRLs)
* Copyright (C) 2000-2009 Andreas Steffen
*
* HSR 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>
#include <freeswan.h>
#include "constants.h"
#include "defs.h"
#include "log.h"
#include "x509.h"
#include "crl.h"
#include "ca.h"
#include "certs.h"
#include "keys.h"
#include "whack.h"
#include "fetch.h"
#include "builder.h"
/* chained lists of X.509 crls */
static x509crl_t *x509crls = NULL;
/**
* Get the X.509 CRL with a given issuer
*/
static x509crl_t* get_x509crl(identification_t *issuer, chunk_t keyid)
{
x509crl_t *x509crl = x509crls;
x509crl_t *prev_crl = NULL;
while (x509crl != NULL)
{
certificate_t *cert_crl = x509crl->crl;
crl_t *crl = (crl_t*)cert_crl;
identification_t *crl_issuer = cert_crl->get_issuer(cert_crl);
chunk_t authKeyID = crl->get_authKeyIdentifier(crl);
if ((keyid.ptr && authKeyID.ptr)? same_keyid(keyid, authKeyID) :
issuer->equals(issuer, crl_issuer))
{
if (x509crl != x509crls)
{
/* bring the CRL up front */
prev_crl->next = x509crl->next;
x509crl->next = x509crls;
x509crls = x509crl;
}
return x509crl;
}
prev_crl = x509crl;
x509crl = x509crl->next;
}
return NULL;
}
/**
* Free the dynamic memory used to store CRLs
*/
void free_crl(x509crl_t *crl)
{
DESTROY_IF(crl->crl);
crl->distributionPoints->destroy_function(crl->distributionPoints, free);
free(crl);
}
static void free_first_crl(void)
{
x509crl_t *crl = x509crls;
x509crls = crl->next;
free_crl(crl);
}
void free_crls(void)
{
lock_crl_list("free_crls");
while (x509crls != NULL)
{
free_first_crl();
}
unlock_crl_list("free_crls");
}
/**
* Insert X.509 CRL into chained list
*/
bool insert_crl(x509crl_t *x509crl, char *crl_uri, bool cache_crl)
{
certificate_t *cert_crl = x509crl->crl;
crl_t *crl = (crl_t*)cert_crl;
identification_t *issuer = cert_crl->get_issuer(cert_crl);
chunk_t authKeyID = crl->get_authKeyIdentifier(crl);
cert_t *issuer_cert;
x509crl_t *oldcrl;
time_t now, nextUpdate;
bool valid_sig;
/* add distribution point */
add_distribution_point(x509crl->distributionPoints, crl_uri);
lock_authcert_list("insert_crl");
/* get the issuer cacert */
issuer_cert = get_authcert(issuer, authKeyID, X509_CA);
if (issuer_cert == NULL)
{
plog("crl issuer cacert not found");
free_crl(x509crl);
unlock_authcert_list("insert_crl");
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("crl issuer cacert found")
)
/* check the issuer's signature of the crl */
valid_sig = cert_crl->issued_by(cert_crl, issuer_cert->cert);
unlock_authcert_list("insert_crl");
if (!valid_sig)
{
free_crl(x509crl);
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("crl signature is valid")
)
/* note the current time */
time(&now);
lock_crl_list("insert_crl");
oldcrl = get_x509crl(issuer, authKeyID);
if (oldcrl != NULL)
{
certificate_t *old_cert_crl = oldcrl->crl;
if (cert_crl->is_newer(cert_crl, old_cert_crl))
{
/* keep any known CRL distribution points */
add_distribution_points(x509crl->distributionPoints,
oldcrl->distributionPoints);
/* now delete the old CRL */
free_first_crl();
DBG(DBG_CONTROL,
DBG_log("thisUpdate is newer - existing crl deleted")
)
}
else
{
unlock_crl_list("insert_crls");
DBG(DBG_CONTROL,
DBG_log("thisUpdate is not newer - existing crl not replaced");
)
free_crl(x509crl);
old_cert_crl->get_validity(old_cert_crl, &now, NULL, &nextUpdate);
return nextUpdate - now > 2*crl_check_interval;
}
}
/* insert new CRL */
x509crl->next = x509crls;
x509crls = x509crl;
unlock_crl_list("insert_crl");
/* If crl caching is enabled then the crl is saved locally.
* Only http or ldap URIs are cached but not local file URIs.
* The CRL's authorityKeyIdentifier is used as a unique filename
*/
if (cache_crl && strncasecmp(crl_uri, "file", 4) != 0)
{
char buf[BUF_LEN];
chunk_t hex, encoding;
hex = chunk_to_hex(crl->get_authKeyIdentifier(crl), NULL, FALSE);
snprintf(buf, sizeof(buf), "%s/%s.crl", CRL_PATH, hex);
free(hex.ptr);
encoding = cert_crl->get_encoding(cert_crl);
chunk_write(encoding, buf, "crl", 022, TRUE);
free(encoding.ptr);
}
/* is the fetched crl valid? */
cert_crl->get_validity(cert_crl, &now, NULL, &nextUpdate);
return nextUpdate - now > 2*crl_check_interval;
}
/**
* Loads CRLs
*/
void load_crls(void)
{
struct dirent **filelist;
u_char buf[BUF_LEN];
u_char *save_dir;
int n;
/* change directory to specified path */
save_dir = getcwd(buf, BUF_LEN);
if (chdir(CRL_PATH))
{
plog("Could not change to directory '%s'", CRL_PATH);
}
else
{
plog("Changing to directory '%s'", CRL_PATH);
n = scandir(CRL_PATH, &filelist, file_select, alphasort);
if (n < 0)
plog(" scandir() error");
else
{
while (n--)
{
char *filename = filelist[n]->d_name;
x509crl_t *x509crl;
x509crl = lib->creds->create(lib->creds, CRED_CERTIFICATE,
CERT_PLUTO_CRL,
BUILD_FROM_FILE, filename, BUILD_END);
if (x509crl)
{
char crl_uri[BUF_LEN];
plog(" loaded crl from '%s'", filename);
snprintf(crl_uri, BUF_LEN, "file://%s/%s", CRL_PATH, filename);
insert_crl(x509crl, crl_uri, FALSE);
}
free(filelist[n]);
}
free(filelist);
}
}
/* restore directory path */
ignore_result(chdir(save_dir));
}
/* Checks if the current certificate is revoked. It goes through the
* list of revoked certificates of the corresponding crl. Either the
* status CERT_GOOD or CERT_REVOKED is returned
*/
static cert_status_t check_revocation(crl_t *crl, chunk_t cert_serial,
time_t *revocationDate,
crl_reason_t *revocationReason)
{
enumerator_t *enumerator;
cert_status_t status;
chunk_t serial;
DBG(DBG_CONTROL,
DBG_log("serial number: %#B", &cert_serial)
)
*revocationDate = UNDEFINED_TIME;
*revocationReason = CRL_REASON_UNSPECIFIED;
status = CERT_GOOD;
enumerator = crl->create_enumerator(crl);
while (enumerator->enumerate(enumerator, &serial,
revocationDate, revocationReason))
{
if (chunk_equals(serial, cert_serial))
{
status = CERT_REVOKED;
break;
}
}
enumerator->destroy(enumerator);
return status;
}
/*
* check if any crls are about to expire
*/
void check_crls(void)
{
x509crl_t *x509crl;
time_t now, nextUpdate, time_left;
lock_crl_list("check_crls");
time(&now);
x509crl = x509crls;
while (x509crl != NULL)
{
certificate_t *cert_crl = x509crl->crl;
crl_t *crl = (crl_t*)cert_crl;
identification_t *issuer = cert_crl->get_issuer(cert_crl);
chunk_t authKeyID = crl->get_authKeyIdentifier(crl);
cert_crl->get_validity(cert_crl, &now, NULL, &nextUpdate);
time_left = nextUpdate - now;
DBG(DBG_CONTROL,
DBG_log("issuer: '%Y'", issuer);
if (authKeyID.ptr)
{
DBG_log("authkey: %#B", &authKeyID);
}
DBG_log("%ld seconds left", time_left)
)
if (time_left < 2*crl_check_interval)
{
fetch_req_t *req = build_crl_fetch_request(issuer, authKeyID,
x509crl->distributionPoints);
add_crl_fetch_request(req);
}
x509crl = x509crl->next;
}
unlock_crl_list("check_crls");
}
/*
* verify if a cert hasn't been revoked by a crl
*/
cert_status_t verify_by_crl(cert_t *cert, time_t *until, time_t *revocationDate,
crl_reason_t *revocationReason)
{
certificate_t *certificate = cert->cert;
x509_t *x509 = (x509_t*)certificate;
identification_t *issuer = certificate->get_issuer(certificate);
chunk_t authKeyID = x509->get_authKeyIdentifier(x509);
x509crl_t *x509crl;
ca_info_t *ca;
enumerator_t *enumerator;
char *point;
ca = get_ca_info(issuer, authKeyID);
*revocationDate = UNDEFINED_TIME;
*revocationReason = CRL_REASON_UNSPECIFIED;
lock_crl_list("verify_by_crl");
x509crl = get_x509crl(issuer, authKeyID);
if (x509crl == NULL)
{
linked_list_t *crluris;
unlock_crl_list("verify_by_crl");
plog("crl not found");
crluris = linked_list_create();
if (ca)
{
add_distribution_points(crluris, ca->crluris);
}
enumerator = x509->create_crl_uri_enumerator(x509);
while (enumerator->enumerate(enumerator, &point))
{
add_distribution_point(crluris, point);
}
enumerator->destroy(enumerator);
if (crluris->get_count(crluris) > 0)
{
fetch_req_t *req;
req = build_crl_fetch_request(issuer, authKeyID, crluris);
crluris->destroy_function(crluris, free);
add_crl_fetch_request(req);
wake_fetch_thread("verify_by_crl");
return CERT_UNKNOWN;
}
else
{
crluris->destroy(crluris);
return CERT_UNDEFINED;
}
}
else
{
certificate_t *cert_crl = x509crl->crl;
crl_t *crl = (crl_t*)cert_crl;
chunk_t authKeyID = crl->get_authKeyIdentifier(crl);
cert_t *issuer_cert;
bool trusted, valid;
DBG(DBG_CONTROL,
DBG_log("crl found")
)
if (ca)
{
add_distribution_points(x509crl->distributionPoints, ca->crluris);
}
enumerator = x509->create_crl_uri_enumerator(x509);
while (enumerator->enumerate(enumerator, &point))
{
add_distribution_point(x509crl->distributionPoints, point);
}
enumerator->destroy(enumerator);
lock_authcert_list("verify_by_crl");
issuer_cert = get_authcert(issuer, authKeyID, X509_CA);
trusted = cert_crl->issued_by(cert_crl, issuer_cert->cert);
unlock_authcert_list("verify_by_crl");
if (trusted)
{
cert_status_t status;
DBG(DBG_CONTROL,
DBG_log("crl signature is valid")
)
/* return the expiration date */
valid = cert_crl->get_validity(cert_crl, NULL, NULL, until);
/* has the certificate been revoked? */
status = check_revocation(crl, x509->get_serial(x509), revocationDate
, revocationReason);
if (valid)
{
unlock_crl_list("verify_by_crl");
DBG(DBG_CONTROL,
DBG_log("crl is valid: until %T", until, FALSE)
)
}
else
{
fetch_req_t *req;
DBG(DBG_CONTROL,
DBG_log("crl is stale: since %T", until, FALSE)
)
/* try to fetch a crl update */
req = build_crl_fetch_request(issuer, authKeyID,
x509crl->distributionPoints);
unlock_crl_list("verify_by_crl");
add_crl_fetch_request(req);
wake_fetch_thread("verify_by_crl");
}
return status;
}
else
{
unlock_crl_list("verify_by_crl");
plog("crl signature is invalid");
return CERT_UNKNOWN;
}
}
}
/*
* list all X.509 crls in the chained list
*/
void list_crls(bool utc, bool strict)
{
x509crl_t *x509crl;
lock_crl_list("list_crls");
x509crl = x509crls;
if (x509crl)
{
whack_log(RC_COMMENT, " ");
whack_log(RC_COMMENT, "List of X.509 CRLs:");
}
while (x509crl)
{
certificate_t *cert_crl = x509crl->crl;
crl_t *crl = (crl_t*)cert_crl;
chunk_t serial, authKeyID;
time_t thisUpdate, nextUpdate;
u_int revoked = 0;
enumerator_t *enumerator;
whack_log(RC_COMMENT, " ");
whack_log(RC_COMMENT, " issuer: \"%Y\"",
cert_crl->get_issuer(cert_crl));
serial = crl->get_serial(crl);
if (serial.ptr)
{
whack_log(RC_COMMENT, " serial: %#B", &serial);
}
/* count number of revoked certificates in CRL */
enumerator = crl->create_enumerator(crl);
while (enumerator->enumerate(enumerator, NULL, NULL, NULL))
{
revoked++;
}
enumerator->destroy(enumerator);
whack_log(RC_COMMENT, " revoked: %d certificates", revoked);
list_distribution_points(x509crl->distributionPoints);
cert_crl->get_validity(cert_crl, NULL, &thisUpdate, &nextUpdate);
whack_log(RC_COMMENT, " updates: this %T", &thisUpdate, utc);
whack_log(RC_COMMENT, " next %T %s", &nextUpdate, utc,
check_expiry(nextUpdate, CRL_WARNING_INTERVAL, strict));
authKeyID = crl->get_authKeyIdentifier(crl);
if (authKeyID.ptr)
{
whack_log(RC_COMMENT, " authkey: %#B", &authKeyID);
}
x509crl = x509crl->next;
}
unlock_crl_list("list_crls");
}