/* Dynamic fetching of X.509 CRLs * Copyright (C) 2002 Stephane Laroche * Copyright (C) 2002-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 #include #include #include #include #ifdef THREADS #include #endif #include #include #include #include #include #ifdef THREADS #include #endif #include "constants.h" #include "defs.h" #include "log.h" #include "x509.h" #include "ca.h" #include "whack.h" #include "ocsp.h" #include "crl.h" #include "fetch.h" #include "builder.h" fetch_req_t empty_fetch_req = { NULL , /* next */ 0 , /* trials */ NULL , /* issuer */ { NULL, 0}, /* authKeyID */ NULL /* distributionPoints */ }; /* chained list of crl fetch requests */ static fetch_req_t *crl_fetch_reqs = NULL; /* chained list of ocsp fetch requests */ static ocsp_location_t *ocsp_fetch_reqs = NULL; #ifdef THREADS static thread_t *thread; static pthread_mutex_t certs_and_keys_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t authcert_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t crl_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t ocsp_cache_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t ca_info_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t crl_fetch_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t ocsp_fetch_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t fetch_wake_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t fetch_wake_cond = PTHREAD_COND_INITIALIZER; /** * lock access to my certs and keys */ void lock_certs_and_keys(const char *who) { pthread_mutex_lock(&certs_and_keys_mutex); DBG(DBG_CONTROLMORE, DBG_log("certs and keys locked by '%s'", who) ) } /** * Unlock access to my certs and keys */ void unlock_certs_and_keys(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("certs and keys unlocked by '%s'", who) ) pthread_mutex_unlock(&certs_and_keys_mutex); } /** * Lock access to the chained authcert list */ void lock_authcert_list(const char *who) { pthread_mutex_lock(&authcert_list_mutex); DBG(DBG_CONTROLMORE, DBG_log("authcert list locked by '%s'", who) ) } /** * Unlock access to the chained authcert list */ void unlock_authcert_list(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("authcert list unlocked by '%s'", who) ) pthread_mutex_unlock(&authcert_list_mutex); } /** * Lock access to the chained crl list */ void lock_crl_list(const char *who) { pthread_mutex_lock(&crl_list_mutex); DBG(DBG_CONTROLMORE, DBG_log("crl list locked by '%s'", who) ) } /** * Unlock access to the chained crl list */ void unlock_crl_list(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("crl list unlocked by '%s'", who) ) pthread_mutex_unlock(&crl_list_mutex); } /** * Lock access to the ocsp cache */ extern void lock_ocsp_cache(const char *who) { pthread_mutex_lock(&ocsp_cache_mutex); DBG(DBG_CONTROLMORE, DBG_log("ocsp cache locked by '%s'", who) ) } /** * Unlock access to the ocsp cache */ extern void unlock_ocsp_cache(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("ocsp cache unlocked by '%s'", who) ) pthread_mutex_unlock(&ocsp_cache_mutex); } /** * Lock access to the ca info list */ extern void lock_ca_info_list(const char *who) { pthread_mutex_lock(&ca_info_list_mutex); DBG(DBG_CONTROLMORE, DBG_log("ca info list locked by '%s'", who) ) } /** * Unlock access to the ca info list */ extern void unlock_ca_info_list(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("ca info list unlocked by '%s'", who) ) pthread_mutex_unlock(&ca_info_list_mutex); } /** * Lock access to the chained crl fetch request list */ static void lock_crl_fetch_list(const char *who) { pthread_mutex_lock(&crl_fetch_list_mutex); DBG(DBG_CONTROLMORE, DBG_log("crl fetch request list locked by '%s'", who) ) } /** * Unlock access to the chained crl fetch request list */ static void unlock_crl_fetch_list(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("crl fetch request list unlocked by '%s'", who) ) pthread_mutex_unlock(&crl_fetch_list_mutex); } /** * Lock access to the chained ocsp fetch request list */ static void lock_ocsp_fetch_list(const char *who) { pthread_mutex_lock(&ocsp_fetch_list_mutex); DBG(DBG_CONTROLMORE, DBG_log("ocsp fetch request list locked by '%s'", who) ) } /** * Unlock access to the chained ocsp fetch request list */ static void unlock_ocsp_fetch_list(const char *who) { DBG(DBG_CONTROLMORE, DBG_log("ocsp fetch request list unlocked by '%s'", who) ) pthread_mutex_unlock(&ocsp_fetch_list_mutex); } /** * Wakes up the sleeping fetch thread */ void wake_fetch_thread(const char *who) { if (crl_check_interval > 0) { DBG(DBG_CONTROLMORE, DBG_log("fetch thread wake call by '%s'", who) ) pthread_mutex_lock(&fetch_wake_mutex); pthread_cond_signal(&fetch_wake_cond); pthread_mutex_unlock(&fetch_wake_mutex); } } #else /* !THREADS */ #define lock_crl_fetch_list(who) /* do nothing */ #define unlock_crl_fetch_list(who) /* do nothing */ #define lock_ocsp_fetch_list(who) /* do nothing */ #define unlock_ocsp_fetch_list(who) /* do nothing */ #endif /* !THREADS */ /** * Free the dynamic memory used to store fetch requests */ static void free_fetch_request(fetch_req_t *req) { req->distributionPoints->destroy_function(req->distributionPoints, free); DESTROY_IF(req->issuer); free(req->authKeyID.ptr); free(req); } #ifdef THREADS /** * Fetch an ASN.1 blob coded in PEM or DER format from a URL */ x509crl_t* fetch_crl(char *url) { x509crl_t *crl; chunk_t blob; DBG1(DBG_LIB, " fetching crl from '%s' ...", url); if (lib->fetcher->fetch(lib->fetcher, url, &blob, FETCH_END) != SUCCESS) { DBG1(DBG_LIB, "crl fetching failed"); return FALSE; } crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PLUTO_CRL, BUILD_BLOB_PEM, blob, BUILD_END); free(blob.ptr); if (!crl) { DBG1(DBG_LIB, "crl fetched successfully but data coded in unknown " "format"); } return crl; } /** * Complete a distributionPoint URI with ca information */ static char* complete_uri(char *distPoint, const char *ldaphost) { char *symbol = strchr(distPoint, ':'); if (symbol) { int type_len = symbol - distPoint; if (type_len >= 4 && strncasecmp(distPoint, "ldap", 4) == 0) { char *ptr = symbol + 1; int len = strlen(distPoint) - (type_len + 1); if (len > 2 && *ptr++ == '/' && *ptr++ == '/') { len -= 2; symbol = strchr(ptr, '/'); if (symbol && symbol - ptr == 0 && ldaphost) { char uri[BUF_LEN]; /* insert the ldaphost into the uri */ snprintf(uri, BUF_LEN, "%.*s%s%.*s", (int)strlen(distPoint) - len, distPoint, ldaphost, len, symbol); return strdup(uri); } } } } /* default action: copy distributionPoint without change */ return strdup(distPoint); } /** * Try to fetch the crls defined by the fetch requests */ static void fetch_crls(bool cache_crls) { fetch_req_t *req; fetch_req_t **reqp; lock_crl_fetch_list("fetch_crls"); req = crl_fetch_reqs; reqp = &crl_fetch_reqs; while (req != NULL) { enumerator_t *enumerator; char *point; bool valid_crl = FALSE; const char *ldaphost; ca_info_t *ca; lock_ca_info_list("fetch_crls"); ca = get_ca_info(req->issuer, req->authKeyID); ldaphost = (ca == NULL)? NULL : ca->ldaphost; enumerator = req->distributionPoints->create_enumerator(req->distributionPoints); while (enumerator->enumerate(enumerator, &point)) { x509crl_t *crl; char *uri; uri = complete_uri(point, ldaphost); crl = fetch_crl(uri); free(uri); if (crl) { if (insert_crl(crl, point, cache_crls)) { DBG(DBG_CONTROL, DBG_log("we have a valid crl") ) valid_crl = TRUE; break; } } } enumerator->destroy(enumerator); unlock_ca_info_list("fetch_crls"); if (valid_crl) { /* delete fetch request */ fetch_req_t *req_free = req; req = req->next; *reqp = req; free_fetch_request(req_free); } else { /* try again next time */ req->trials++; reqp = &req->next; req = req->next; } } unlock_crl_fetch_list("fetch_crls"); } static void fetch_ocsp_status(ocsp_location_t* location) { chunk_t request = build_ocsp_request(location); chunk_t response = chunk_empty; DBG1(DBG_LIB, " requesting ocsp status from '%s' ...", location->uri); if (lib->fetcher->fetch(lib->fetcher, location->uri, &response, FETCH_REQUEST_DATA, request, FETCH_REQUEST_TYPE, "application/ocsp-request", FETCH_END) == SUCCESS) { parse_ocsp(location, response); } else { DBG1(DBG_LIB, "ocsp request to %s failed", location->uri); } free(request.ptr); chunk_free(&location->nonce); /* increment the trial counter of the unresolved fetch requests */ { ocsp_certinfo_t *certinfo = location->certinfo; while (certinfo != NULL) { certinfo->trials++; certinfo = certinfo->next; } } } /** * Try to fetch the necessary ocsp information */ static void fetch_ocsp(void) { ocsp_location_t *location; lock_ocsp_fetch_list("fetch_ocsp"); location = ocsp_fetch_reqs; /* fetch the ocps status for all locations */ while (location != NULL) { if (location->certinfo != NULL) { fetch_ocsp_status(location); } location = location->next; } unlock_ocsp_fetch_list("fetch_ocsp"); } static void* fetch_thread(void *arg) { struct timespec wait_interval; /* the fetching thread is only cancellable while waiting for new events */ thread_cancelability(FALSE); DBG(DBG_CONTROL, DBG_log("fetch thread started") ) pthread_mutex_lock(&fetch_wake_mutex); while(1) { int status; wait_interval.tv_nsec = 0; wait_interval.tv_sec = time(NULL) + crl_check_interval; DBG(DBG_CONTROL, DBG_log("next regular crl check in %ld seconds", crl_check_interval) ) thread_cancelability(TRUE); status = pthread_cond_timedwait(&fetch_wake_cond, &fetch_wake_mutex , &wait_interval); thread_cancelability(FALSE); if (status == ETIMEDOUT) { DBG(DBG_CONTROL, DBG_log(" "); DBG_log("*time to check crls and the ocsp cache") ) check_ocsp(); check_crls(); } else { DBG(DBG_CONTROL, DBG_log("fetch thread was woken up") ) } fetch_ocsp(); fetch_crls(cache_crls); } return NULL; } #endif /* THREADS*/ /** * Initializes curl and starts the fetching thread */ void fetch_initialize(void) { if (crl_check_interval > 0) { #ifdef THREADS thread = thread_create((thread_main_t)fetch_thread, NULL); if (thread == NULL) { plog("fetching thread could not be started"); } #else /* !THREADS */ plog("warning: not compiled with pthread support"); #endif /* !THREADS */ } } /** * Terminates the fetching thread */ void fetch_finalize(void) { if (crl_check_interval > 0) { #ifdef THREADS if (thread) { thread->cancel(thread); thread->join(thread); } #endif } } void free_crl_fetch(void) { lock_crl_fetch_list("free_crl_fetch"); while (crl_fetch_reqs != NULL) { fetch_req_t *req = crl_fetch_reqs; crl_fetch_reqs = req->next; free_fetch_request(req); } unlock_crl_fetch_list("free_crl_fetch"); } /** * Free the chained list of ocsp requests */ void free_ocsp_fetch(void) { lock_ocsp_fetch_list("free_ocsp_fetch"); free_ocsp_locations(&ocsp_fetch_reqs); unlock_ocsp_fetch_list("free_ocsp_fetch"); } /** * Add an additional distribution point */ void add_distribution_point(linked_list_t *points, char *new_point) { char *point; bool add = TRUE; enumerator_t *enumerator; if (new_point == NULL || *new_point == '\0') { return; } enumerator = points->create_enumerator(points); while (enumerator->enumerate(enumerator, &point)) { if (streq(point, new_point)) { add = FALSE; break; } } enumerator->destroy(enumerator); if (add) { points->insert_last(points, strdup(new_point)); } } /** * Add additional distribution points */ void add_distribution_points(linked_list_t *points, linked_list_t *new_points) { char *new_point; enumerator_t *enumerator; enumerator = new_points->create_enumerator(new_points); while (enumerator->enumerate(enumerator, &new_point)) { bool add = TRUE; char *point; enumerator_t *enumerator; enumerator = points->create_enumerator(points); while (enumerator->enumerate(enumerator, &point)) { if (streq(point, new_point)) { add = FALSE; break; } } enumerator->destroy(enumerator); if (add) { points->insert_last(points, strdup(new_point)); } } enumerator->destroy(enumerator); } fetch_req_t* build_crl_fetch_request(identification_t *issuer, chunk_t authKeyID, linked_list_t *distributionPoints) { char *point; enumerator_t *enumerator; fetch_req_t *req = malloc_thing(fetch_req_t); memset(req, 0, sizeof(fetch_req_t)); req->distributionPoints = linked_list_create(); /* clone fields */ req->issuer = issuer->clone(issuer); req->authKeyID = chunk_clone(authKeyID); /* copy distribution points */ enumerator = distributionPoints->create_enumerator(distributionPoints); while (enumerator->enumerate(enumerator, &point)) { req->distributionPoints->insert_last(req->distributionPoints, strdup(point)); } enumerator->destroy(enumerator); return req; } /** * Add a crl fetch request to the chained list */ void add_crl_fetch_request(fetch_req_t *req) { fetch_req_t *r; lock_crl_fetch_list("add_crl_fetch_request"); r = crl_fetch_reqs; while (r != NULL) { if (req->authKeyID.ptr ? same_keyid(req->authKeyID, r->authKeyID) : req->issuer->equals(req->issuer, r->issuer)) { /* there is already a fetch request */ DBG(DBG_CONTROL, DBG_log("crl fetch request already exists") ) /* there might be new distribution points */ add_distribution_points(r->distributionPoints, req->distributionPoints); unlock_crl_fetch_list("add_crl_fetch_request"); free_fetch_request(req); return; } r = r->next; } /* insert new fetch request at the head of the queue */ req->next = crl_fetch_reqs; crl_fetch_reqs = req; DBG(DBG_CONTROL, DBG_log("crl fetch request added") ) unlock_crl_fetch_list("add_crl_fetch_request"); } /** * Add an ocsp fetch request to the chained list */ void add_ocsp_fetch_request(ocsp_location_t *location, chunk_t serialNumber) { ocsp_certinfo_t certinfo; certinfo.serialNumber = serialNumber; lock_ocsp_fetch_list("add_ocsp_fetch_request"); add_certinfo(location, &certinfo, &ocsp_fetch_reqs, TRUE); unlock_ocsp_fetch_list("add_ocsp_fetch_request"); } /** * List all distribution points */ void list_distribution_points(linked_list_t *distributionPoints) { char *point; bool first_point = TRUE; enumerator_t *enumerator; enumerator = distributionPoints->create_enumerator(distributionPoints); while (enumerator->enumerate(enumerator, &point)) { whack_log(RC_COMMENT, " %s '%s'", (first_point)? "distPts: " : " ", point); first_point = FALSE; } enumerator->destroy(enumerator); } /** * List all fetch requests in the chained list */ void list_crl_fetch_requests(bool utc) { fetch_req_t *req; lock_crl_fetch_list("list_crl_fetch_requests"); req = crl_fetch_reqs; if (req != NULL) { whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, "List of CRL Fetch Requests:"); } while (req != NULL) { whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, " trials: %d", req->trials); whack_log(RC_COMMENT, " issuer: \"%Y\"", req->issuer); if (req->authKeyID.ptr) { whack_log(RC_COMMENT, " authkey: %#B", &req->authKeyID); } list_distribution_points(req->distributionPoints); req = req->next; } unlock_crl_fetch_list("list_crl_fetch_requests"); } void list_ocsp_fetch_requests(bool utc) { lock_ocsp_fetch_list("list_ocsp_fetch_requests"); list_ocsp_locations(ocsp_fetch_reqs, TRUE, utc, FALSE); unlock_ocsp_fetch_list("list_ocsp_fetch_requests"); }