/* Support of X.509 certificate revocation lists (CRLs) * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur * * 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. * * RCSID $Id: crl.c,v 1.12 2005/12/06 22:49:57 as Exp $ */ #include #include #include #include #include #include #include #include #include #include "constants.h" #include "defs.h" #include "log.h" #include "asn1.h" #include "oid.h" #include "x509.h" #include "crl.h" #include "ca.h" #include "certs.h" #include "keys.h" #include "whack.h" #include "fetch.h" #include "sha1.h" /* chained lists of X.509 crls */ static x509crl_t *x509crls = NULL; /* ASN.1 definition of an X.509 certificate list */ static const asn1Object_t crlObjects[] = { { 0, "certificateList", ASN1_SEQUENCE, ASN1_OBJ }, /* 0 */ { 1, "tbsCertList", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ { 2, "version", ASN1_INTEGER, ASN1_OPT | ASN1_BODY }, /* 2 */ { 2, "end opt", ASN1_EOC, ASN1_END }, /* 3 */ { 2, "signature", ASN1_EOC, ASN1_RAW }, /* 4 */ { 2, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 5 */ { 2, "thisUpdate", ASN1_EOC, ASN1_RAW }, /* 6 */ { 2, "nextUpdate", ASN1_EOC, ASN1_RAW }, /* 7 */ { 2, "revokedCertificates", ASN1_SEQUENCE, ASN1_OPT | ASN1_LOOP }, /* 8 */ { 3, "certList", ASN1_SEQUENCE, ASN1_NONE }, /* 9 */ { 4, "userCertificate", ASN1_INTEGER, ASN1_BODY }, /* 10 */ { 4, "revocationDate", ASN1_EOC, ASN1_RAW }, /* 11 */ { 4, "crlEntryExtensions", ASN1_SEQUENCE, ASN1_OPT | ASN1_LOOP }, /* 12 */ { 5, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 13 */ { 6, "extnID", ASN1_OID, ASN1_BODY }, /* 14 */ { 6, "critical", ASN1_BOOLEAN, ASN1_DEF | ASN1_BODY }, /* 15 */ { 6, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 16 */ { 4, "end opt or loop", ASN1_EOC, ASN1_END }, /* 17 */ { 2, "end opt or loop", ASN1_EOC, ASN1_END }, /* 18 */ { 2, "optional extensions", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 19 */ { 3, "crlExtensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 20 */ { 4, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 21 */ { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 22 */ { 5, "critical", ASN1_BOOLEAN, ASN1_DEF | ASN1_BODY }, /* 23 */ { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 24 */ { 3, "end loop", ASN1_EOC, ASN1_END }, /* 25 */ { 2, "end opt", ASN1_EOC, ASN1_END }, /* 26 */ { 1, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 27 */ { 1, "signatureValue", ASN1_BIT_STRING, ASN1_BODY } /* 28 */ }; #define CRL_OBJ_CERTIFICATE_LIST 0 #define CRL_OBJ_TBS_CERT_LIST 1 #define CRL_OBJ_VERSION 2 #define CRL_OBJ_SIG_ALG 4 #define CRL_OBJ_ISSUER 5 #define CRL_OBJ_THIS_UPDATE 6 #define CRL_OBJ_NEXT_UPDATE 7 #define CRL_OBJ_USER_CERTIFICATE 10 #define CRL_OBJ_REVOCATION_DATE 11 #define CRL_OBJ_CRL_ENTRY_EXTN_ID 14 #define CRL_OBJ_CRL_ENTRY_CRITICAL 15 #define CRL_OBJ_CRL_ENTRY_EXTN_VALUE 16 #define CRL_OBJ_EXTN_ID 22 #define CRL_OBJ_CRITICAL 23 #define CRL_OBJ_EXTN_VALUE 24 #define CRL_OBJ_ALGORITHM 27 #define CRL_OBJ_SIGNATURE 28 #define CRL_OBJ_ROOF 29 const x509crl_t empty_x509crl = { NULL , /* *next */ UNDEFINED_TIME, /* installed */ NULL , /* distributionPoints */ { NULL, 0 } , /* certificateList */ { NULL, 0 } , /* tbsCertList */ 1 , /* version */ OID_UNKNOWN , /* sigAlg */ { NULL, 0 } , /* issuer */ UNDEFINED_TIME, /* thisUpdate */ UNDEFINED_TIME, /* nextUpdate */ NULL , /* revokedCertificates */ /* crlExtensions */ /* extension */ /* extnID */ /* critical */ /* extnValue */ { NULL, 0 } , /* authKeyID */ { NULL, 0 } , /* authKeySerialNumber */ OID_UNKNOWN , /* algorithm */ { NULL, 0 } /* signature */ }; /* * get the X.509 CRL with a given issuer */ static x509crl_t* get_x509crl(chunk_t issuer, chunk_t serial, chunk_t keyid) { x509crl_t *crl = x509crls; x509crl_t *prev_crl = NULL; while (crl != NULL) { if ((keyid.ptr != NULL && crl->authKeyID.ptr != NULL) ? same_keyid(keyid, crl->authKeyID) : (same_dn(crl->issuer, issuer) && same_serial(serial, crl->authKeySerialNumber))) { if (crl != x509crls) { /* bring the CRL up front */ prev_crl->next = crl->next; crl->next = x509crls; x509crls = crl; } return crl; } prev_crl = crl; crl = crl->next; } return NULL; } /* * free the dynamic memory used to store revoked certificates */ static void free_revoked_certs(revokedCert_t* revokedCerts) { while (revokedCerts != NULL) { revokedCert_t * revokedCert = revokedCerts; revokedCerts = revokedCert->next; pfree(revokedCert); } } /* * free the dynamic memory used to store CRLs */ void free_crl(x509crl_t *crl) { free_revoked_certs(crl->revokedCertificates); free_generalNames(crl->distributionPoints, TRUE); pfree(crl->certificateList.ptr); pfree(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(chunk_t blob, chunk_t crl_uri, bool cache_crl) { x509crl_t *crl = alloc_thing(x509crl_t, "x509crl"); *crl = empty_x509crl; if (parse_x509crl(blob, 0, crl)) { x509cert_t *issuer_cert; x509crl_t *oldcrl; bool valid_sig; generalName_t *gn; /* add distribution point */ gn = alloc_thing(generalName_t, "generalName"); gn->kind = GN_URI; gn->name = crl_uri; gn->next = crl->distributionPoints; crl->distributionPoints = gn; lock_authcert_list("insert_crl"); /* get the issuer cacert */ issuer_cert = get_authcert(crl->issuer, crl->authKeySerialNumber, crl->authKeyID, AUTH_CA); if (issuer_cert == NULL) { plog("crl issuer cacert not found"); free_crl(crl); 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 = check_signature(crl->tbsCertList, crl->signature , crl->algorithm, crl->algorithm, issuer_cert); unlock_authcert_list("insert_crl"); if (!valid_sig) { free_crl(crl); return FALSE; } DBG(DBG_CONTROL, DBG_log("crl signature is valid") ) lock_crl_list("insert_crl"); oldcrl = get_x509crl(crl->issuer, crl->authKeySerialNumber , crl->authKeyID); if (oldcrl != NULL) { if (crl->thisUpdate > oldcrl->thisUpdate) { /* keep any known CRL distribution points */ add_distribution_points(oldcrl->distributionPoints , &crl->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(crl); return oldcrl->nextUpdate - time(NULL) > 2*crl_check_interval; } } /* insert new CRL */ crl->next = x509crls; x509crls = crl; 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 issuer's subjectKeyID is used as a unique filename */ if (cache_crl && strncasecmp(crl_uri.ptr, "file", 4) != 0) { char path[BUF_LEN]; char buf[BUF_LEN]; char digest_buf[SHA1_DIGEST_SIZE]; chunk_t subjectKeyID = { digest_buf, SHA1_DIGEST_SIZE }; if (issuer_cert->subjectKeyID.ptr == NULL) compute_subjectKeyID(issuer_cert, subjectKeyID); else subjectKeyID = issuer_cert->subjectKeyID; datatot(subjectKeyID.ptr, subjectKeyID.len, 16, buf, BUF_LEN); snprintf(path, BUF_LEN, "%s/%s.crl", CRL_PATH, buf); write_chunk(path, "crl", crl->certificateList, 0022, TRUE); } /* is the fetched crl valid? */ return crl->nextUpdate - time(NULL) > 2*crl_check_interval; } else { plog(" error in X.509 crl"); free_crl(crl); return FALSE; } } /* * 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--) { bool pgp = FALSE; chunk_t blob = empty_chunk; char *filename = filelist[n]->d_name; if (load_coded_file(filename, NULL, "crl", &blob, &pgp)) { chunk_t crl_uri; crl_uri.len = 7 + sizeof(CRL_PATH) + strlen(filename); crl_uri.ptr = alloc_bytes(crl_uri.len + 1, "crl uri"); /* build CRL file URI */ snprintf(crl_uri.ptr, crl_uri.len + 1, "file://%s/%s" , CRL_PATH, filename); insert_crl(blob, crl_uri, FALSE); } free(filelist[n]); } free(filelist); } } /* restore directory path */ chdir(save_dir); } /* * Parses a CRL revocation reason code */ static crl_reason_t parse_crl_reasonCode(chunk_t object) { crl_reason_t reason = REASON_UNSPECIFIED; if (*object.ptr == ASN1_ENUMERATED && asn1_length(&object) == 1) { reason = *object.ptr; } DBG(DBG_PARSING, DBG_log(" '%s'", enum_name(&crl_reason_names, reason)) ) return reason; } /* * Parses an X.509 CRL */ bool parse_x509crl(chunk_t blob, u_int level0, x509crl_t *crl) { u_char buf[BUF_LEN]; asn1_ctx_t ctx; bool critical; chunk_t extnID; chunk_t userCertificate; chunk_t object; u_int level; int objectID = 0; asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); while (objectID < CRL_OBJ_ROOF) { if (!extract_object(crlObjects, &objectID, &object, &level, &ctx)) return FALSE; /* those objects which will parsed further need the next higher level */ level++; switch (objectID) { case CRL_OBJ_CERTIFICATE_LIST: crl->certificateList = object; break; case CRL_OBJ_TBS_CERT_LIST: crl->tbsCertList = object; break; case CRL_OBJ_VERSION: crl->version = (object.len) ? (1+(u_int)*object.ptr) : 1; DBG(DBG_PARSING, DBG_log(" v%d", crl->version); ) break; case CRL_OBJ_SIG_ALG: crl->sigAlg = parse_algorithmIdentifier(object, level, NULL); break; case CRL_OBJ_ISSUER: crl->issuer = object; DBG(DBG_PARSING, dntoa(buf, BUF_LEN, object); DBG_log(" '%s'",buf) ) break; case CRL_OBJ_THIS_UPDATE: crl->thisUpdate = parse_time(object, level); break; case CRL_OBJ_NEXT_UPDATE: crl->nextUpdate = parse_time(object, level); break; case CRL_OBJ_USER_CERTIFICATE: userCertificate = object; break; case CRL_OBJ_REVOCATION_DATE: { /* put all the serial numbers and the revocation date in a chained list with revocedCertificates pointing to the first revoked certificate */ revokedCert_t *revokedCert = alloc_thing(revokedCert_t, "revokedCert"); revokedCert->userCertificate = userCertificate; revokedCert->revocationDate = parse_time(object, level); revokedCert->revocationReason = REASON_UNSPECIFIED; revokedCert->next = crl->revokedCertificates; crl->revokedCertificates = revokedCert; } break; case CRL_OBJ_CRL_ENTRY_EXTN_ID: case CRL_OBJ_EXTN_ID: extnID = object; break; case CRL_OBJ_CRL_ENTRY_CRITICAL: case CRL_OBJ_CRITICAL: critical = object.len && *object.ptr; DBG(DBG_PARSING, DBG_log(" %s",(critical)?"TRUE":"FALSE"); ) break; case CRL_OBJ_CRL_ENTRY_EXTN_VALUE: case CRL_OBJ_EXTN_VALUE: { u_int extn_oid = known_oid(extnID); if (extn_oid == OID_CRL_REASON_CODE) { crl->revokedCertificates->revocationReason = parse_crl_reasonCode(object); } else if (extn_oid == OID_AUTHORITY_KEY_ID) { parse_authorityKeyIdentifier(object, level , &crl->authKeyID, &crl->authKeySerialNumber); } } break; case CRL_OBJ_ALGORITHM: crl->algorithm = parse_algorithmIdentifier(object, level, NULL); break; case CRL_OBJ_SIGNATURE: crl->signature = object; break; default: break; } objectID++; } time(&crl->installed); return TRUE; } /* 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(const x509crl_t *crl, chunk_t serial , time_t *revocationDate, crl_reason_t * revocationReason) { revokedCert_t *revokedCert = crl->revokedCertificates; *revocationDate = UNDEFINED_TIME; *revocationReason = REASON_UNSPECIFIED; DBG(DBG_CONTROL, DBG_dump_chunk("serial number:", serial) ) while(revokedCert != NULL) { /* compare serial numbers */ if (revokedCert->userCertificate.len == serial.len && memcmp(revokedCert->userCertificate.ptr, serial.ptr, serial.len) == 0) { *revocationDate = revokedCert->revocationDate; *revocationReason = revokedCert->revocationReason; return CERT_REVOKED; } revokedCert = revokedCert->next; } return CERT_GOOD; } /* * check if any crls are about to expire */ void check_crls(void) { x509crl_t *crl; lock_crl_list("check_crls"); crl = x509crls; while (crl != NULL) { time_t time_left = crl->nextUpdate - time(NULL); u_char buf[BUF_LEN]; DBG(DBG_CONTROL, dntoa(buf, BUF_LEN, crl->issuer); DBG_log("issuer: '%s'",buf); if (crl->authKeyID.ptr != NULL) { datatot(crl->authKeyID.ptr, crl->authKeyID.len, ':' , buf, BUF_LEN); DBG_log("authkey: %s", buf); } DBG_log("%ld seconds left", time_left) ) if (time_left < 2*crl_check_interval) { fetch_req_t *req = build_crl_fetch_request(crl->issuer , crl->authKeySerialNumber , crl->authKeyID, crl->distributionPoints); add_crl_fetch_request(req); } crl = crl->next; } unlock_crl_list("check_crls"); } /* * verify if a cert hasn't been revoked by a crl */ cert_status_t verify_by_crl(const x509cert_t *cert, time_t *until, time_t *revocationDate , crl_reason_t *revocationReason) { x509crl_t *crl; ca_info_t *ca = get_ca_info(cert->issuer, cert->authKeySerialNumber , cert->authKeyID); generalName_t *crluri = (ca == NULL)? NULL : ca->crluri; *revocationDate = UNDEFINED_TIME; *revocationReason = REASON_UNSPECIFIED; lock_crl_list("verify_by_crl"); crl = get_x509crl(cert->issuer, cert->authKeySerialNumber, cert->authKeyID); if (crl == NULL) { unlock_crl_list("verify_by_crl"); plog("crl not found"); if (cert->crlDistributionPoints != NULL) { fetch_req_t *req = build_crl_fetch_request(cert->issuer , cert->authKeySerialNumber , cert->authKeyID, cert->crlDistributionPoints); add_crl_fetch_request(req); } if (crluri != NULL) { fetch_req_t *req = build_crl_fetch_request(cert->issuer , cert->authKeySerialNumber , cert->authKeyID, crluri); add_crl_fetch_request(req); } if (cert->crlDistributionPoints != 0 || crluri != NULL) { wake_fetch_thread("verify_by_crl"); return CERT_UNKNOWN; } else return CERT_UNDEFINED; } else { x509cert_t *issuer_cert; bool valid; DBG(DBG_CONTROL, DBG_log("crl found") ) add_distribution_points(cert->crlDistributionPoints , &crl->distributionPoints); add_distribution_points(crluri , &crl->distributionPoints); lock_authcert_list("verify_by_crl"); issuer_cert = get_authcert(crl->issuer, crl->authKeySerialNumber , crl->authKeyID, AUTH_CA); valid = check_signature(crl->tbsCertList, crl->signature , crl->algorithm, crl->algorithm, issuer_cert); unlock_authcert_list("verify_by_crl"); if (valid) { cert_status_t status; DBG(DBG_CONTROL, DBG_log("crl signature is valid") ) /* return the expiration date */ *until = crl->nextUpdate; /* has the certificate been revoked? */ status = check_revocation(crl, cert->serialNumber, revocationDate , revocationReason); if (*until < time(NULL)) { fetch_req_t *req; plog("crl update is overdue since %s" , timetoa(until, TRUE)); /* try to fetch a crl update */ req = build_crl_fetch_request(crl->issuer , crl->authKeySerialNumber , crl->authKeyID, crl->distributionPoints); unlock_crl_list("verify_by_crl"); add_crl_fetch_request(req); wake_fetch_thread("verify_by_crl"); } else { unlock_crl_list("verify_by_crl"); DBG(DBG_CONTROL, DBG_log("crl is valid") ) } 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 *crl; lock_crl_list("list_crls"); crl = x509crls; if (crl != NULL) { whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, "List of X.509 CRLs:"); whack_log(RC_COMMENT, " "); } while (crl != NULL) { u_char buf[BUF_LEN]; u_int revoked = 0; revokedCert_t *revokedCert = crl->revokedCertificates; /* count number of revoked certificates in CRL */ while (revokedCert != NULL) { revoked++; revokedCert = revokedCert->next; } whack_log(RC_COMMENT, "%s, revoked certs: %d", timetoa(&crl->installed, utc), revoked); dntoa(buf, BUF_LEN, crl->issuer); whack_log(RC_COMMENT, " issuer: '%s'", buf); list_distribution_points(crl->distributionPoints); whack_log(RC_COMMENT, " updates: this %s", timetoa(&crl->thisUpdate, utc)); whack_log(RC_COMMENT, " next %s %s", timetoa(&crl->nextUpdate, utc), check_expiry(crl->nextUpdate, CRL_WARNING_INTERVAL, strict)); if (crl->authKeyID.ptr != NULL) { datatot(crl->authKeyID.ptr, crl->authKeyID.len, ':' , buf, BUF_LEN); whack_log(RC_COMMENT, " authkey: %s", buf); } if (crl->authKeySerialNumber.ptr != NULL) { datatot(crl->authKeySerialNumber.ptr, crl->authKeySerialNumber.len, ':' , buf, BUF_LEN); whack_log(RC_COMMENT, " aserial: %s", buf); } crl = crl->next; } unlock_crl_list("list_crls"); }