/* * Copyright (C) 2014-2017 Tobias Brunner * Copyright (C) 2008-2009 Martin Willi * Copyright (C) 2017 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 . * * 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 "x509_crl.h" typedef struct private_x509_crl_t private_x509_crl_t; typedef struct revoked_t revoked_t; #include #include #include #include #include #include #include #include #include /** * entry for a revoked certificate */ struct revoked_t { /** * serial of the revoked certificate */ chunk_t serial; /** * date of revocation */ time_t date; /** * reason for revocation */ crl_reason_t reason; }; /** * private data of x509_crl */ struct private_x509_crl_t { /** * public functions */ x509_crl_t public; /** * X.509 crl encoding in ASN.1 DER format */ chunk_t encoding; /** * X.509 crl body over which signature is computed */ chunk_t tbsCertList; /** * Version of the X.509 crl */ u_int version; /** * ID representing the crl issuer */ identification_t *issuer; /** * CRL number */ chunk_t crlNumber; /** * Time when the crl was generated */ time_t thisUpdate; /** * Time when an update crl will be available */ time_t nextUpdate; /** * list of revoked certificates as revoked_t */ linked_list_t *revoked; /** * List of Freshest CRL distribution points */ linked_list_t *crl_uris; /** * Authority Key Identifier */ chunk_t authKeyIdentifier; /** * Authority Key Serial Number */ chunk_t authKeySerialNumber; /** * Optional OID of an [unsupported] critical extension */ chunk_t critical_extension_oid; /** * Number of BaseCRL, if a delta CRL */ chunk_t baseCrlNumber; /** * Signature scheme */ signature_params_t *scheme; /** * Signature */ chunk_t signature; /** * has this CRL been generated */ bool generated; /** * reference counter */ refcount_t ref; }; /** * from x509_cert */ extern chunk_t x509_parse_authorityKeyIdentifier(chunk_t blob, int level0, chunk_t *authKeySerialNumber); /** * from x509_cert */ extern bool x509_parse_crlDistributionPoints(chunk_t blob, int level0, linked_list_t *list); /** * from x509_cert */ extern chunk_t x509_build_crlDistributionPoints(linked_list_t *list, int extn); /** * ASN.1 definition of an X.509 certificate revocation 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 */ { 0, "exit", ASN1_EOC, ASN1_EXIT } }; #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 /** * Parses an X.509 Certificate Revocation List (CRL) */ static bool parse(private_x509_crl_t *this) { asn1_parser_t *parser; chunk_t object; chunk_t extnID = chunk_empty; chunk_t userCertificate = chunk_empty; int objectID; signature_params_t sig_alg = {}; bool success = FALSE; bool critical = FALSE; revoked_t *revoked = NULL; parser = asn1_parser_create(crlObjects, this->encoding); while (parser->iterate(parser, &objectID, &object)) { u_int level = parser->get_level(parser)+1; switch (objectID) { case CRL_OBJ_TBS_CERT_LIST: this->tbsCertList = object; break; case CRL_OBJ_VERSION: this->version = (object.len) ? (1+(u_int)*object.ptr) : 1; DBG2(DBG_ASN, " v%d", this->version); break; case CRL_OBJ_SIG_ALG: if (!signature_params_parse(object, level, &sig_alg)) { DBG1(DBG_ASN, " unable to parse signature algorithm"); goto end; } break; case CRL_OBJ_ISSUER: this->issuer = identification_create_from_encoding(ID_DER_ASN1_DN, object); DBG2(DBG_ASN, " '%Y'", this->issuer); break; case CRL_OBJ_THIS_UPDATE: this->thisUpdate = asn1_parse_time(object, level); break; case CRL_OBJ_NEXT_UPDATE: this->nextUpdate = asn1_parse_time(object, level); break; case CRL_OBJ_USER_CERTIFICATE: userCertificate = object; break; case CRL_OBJ_REVOCATION_DATE: revoked = malloc_thing(revoked_t); revoked->serial = chunk_clone(userCertificate); revoked->date = asn1_parse_time(object, level); revoked->reason = CRL_REASON_UNSPECIFIED; this->revoked->insert_last(this->revoked, (void *)revoked); 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; DBG2(DBG_ASN, " %s", critical ? "TRUE" : "FALSE"); break; case CRL_OBJ_CRL_ENTRY_EXTN_VALUE: case CRL_OBJ_EXTN_VALUE: { int extn_oid = asn1_known_oid(extnID); switch (extn_oid) { case OID_CRL_REASON_CODE: if (revoked) { if (object.len && *object.ptr == ASN1_ENUMERATED && asn1_length(&object) == 1) { revoked->reason = *object.ptr; } DBG2(DBG_ASN, " '%N'", crl_reason_names, revoked->reason); } break; case OID_AUTHORITY_KEY_ID: chunk_free(&this->authKeyIdentifier); this->authKeyIdentifier = x509_parse_authorityKeyIdentifier( object, level, &this->authKeySerialNumber); break; case OID_CRL_NUMBER: if (!asn1_parse_simple_object(&object, ASN1_INTEGER, level, "crlNumber")) { goto end; } this->crlNumber = object; break; case OID_FRESHEST_CRL: if (!x509_parse_crlDistributionPoints(object, level, this->crl_uris)) { goto end; } break; case OID_DELTA_CRL_INDICATOR: if (!asn1_parse_simple_object(&object, ASN1_INTEGER, level, "deltaCrlIndicator")) { goto end; } this->baseCrlNumber = object; break; case OID_ISSUING_DIST_POINT: /* TODO support of IssuingDistributionPoints */ break; default: if (critical && lib->settings->get_bool(lib->settings, "%s.x509.enforce_critical", TRUE, lib->ns)) { DBG1(DBG_ASN, "critical '%s' extension not supported", (extn_oid == OID_UNKNOWN) ? "unknown" : (char*)oid_names[extn_oid].name); goto end; } break; } break; } case CRL_OBJ_ALGORITHM: { INIT(this->scheme); if (!signature_params_parse(object, level, this->scheme)) { DBG1(DBG_ASN, " unable to parse signature algorithm"); goto end; } if (!signature_params_equal(this->scheme, &sig_alg)) { DBG1(DBG_ASN, " signature algorithms do not agree"); goto end; } break; } case CRL_OBJ_SIGNATURE: this->signature = chunk_skip(object, 1); break; default: break; } } success = parser->success(parser); end: parser->destroy(parser); signature_params_clear(&sig_alg); return success; } CALLBACK(filter, bool, void *data, enumerator_t *orig, va_list args) { revoked_t *revoked; crl_reason_t *reason; chunk_t *serial; time_t *date; VA_ARGS_VGET(args, serial, date, reason); if (orig->enumerate(orig, &revoked)) { if (serial) { *serial = revoked->serial; } if (date) { *date = revoked->date; } if (reason) { *reason = revoked->reason; } return TRUE; } return FALSE; } METHOD(crl_t, get_serial, chunk_t, private_x509_crl_t *this) { return this->crlNumber; } METHOD(crl_t, get_authKeyIdentifier, chunk_t, private_x509_crl_t *this) { return this->authKeyIdentifier; } METHOD(crl_t, is_delta_crl, bool, private_x509_crl_t *this, chunk_t *base_crl) { if (this->baseCrlNumber.len) { if (base_crl) { *base_crl = this->baseCrlNumber; } return TRUE; } return FALSE; } METHOD(crl_t, create_delta_crl_uri_enumerator, enumerator_t*, private_x509_crl_t *this) { return this->crl_uris->create_enumerator(this->crl_uris); } METHOD(crl_t, create_enumerator, enumerator_t*, private_x509_crl_t *this) { return enumerator_create_filter( this->revoked->create_enumerator(this->revoked), filter, NULL, NULL); } METHOD(certificate_t, get_type, certificate_type_t, private_x509_crl_t *this) { return CERT_X509_CRL; } METHOD(certificate_t, get_issuer, identification_t*, private_x509_crl_t *this) { return this->issuer; } METHOD(certificate_t, has_issuer, id_match_t, private_x509_crl_t *this, identification_t *issuer) { if (issuer->get_type(issuer) == ID_KEY_ID && this->authKeyIdentifier.ptr && chunk_equals(this->authKeyIdentifier, issuer->get_encoding(issuer))) { return ID_MATCH_PERFECT; } return this->issuer->matches(this->issuer, issuer); } METHOD(certificate_t, issued_by, bool, private_x509_crl_t *this, certificate_t *issuer, signature_params_t **scheme) { public_key_t *key; bool valid; x509_t *x509 = (x509_t*)issuer; chunk_t keyid = chunk_empty; /* check if issuer is an X.509 CA certificate */ if (issuer->get_type(issuer) != CERT_X509) { return FALSE; } if (!(x509->get_flags(x509) & (X509_CA | X509_CRL_SIGN))) { return FALSE; } /* compare keyIdentifiers if available, otherwise use DNs */ if (this->authKeyIdentifier.ptr) { keyid = x509->get_subjectKeyIdentifier(x509); if (keyid.len && !chunk_equals(keyid, this->authKeyIdentifier)) { return FALSE; } } if (!keyid.len) { if (!this->issuer->equals(this->issuer, issuer->get_subject(issuer))) { return FALSE; } } key = issuer->get_public_key(issuer); if (!key) { return FALSE; } valid = key->verify(key, this->scheme->scheme, this->scheme->params, this->tbsCertList, this->signature); key->destroy(key); if (valid && scheme) { *scheme = signature_params_clone(this->scheme); } return valid; } METHOD(certificate_t, get_public_key, public_key_t*, private_x509_crl_t *this) { return NULL; } METHOD(certificate_t, get_ref, certificate_t*, private_x509_crl_t *this) { ref_get(&this->ref); return &this->public.crl.certificate; } METHOD(certificate_t, get_validity, bool, private_x509_crl_t *this, time_t *when, time_t *not_before, time_t *not_after) { time_t t = when ? *when : time(NULL); if (not_before) { *not_before = this->thisUpdate; } if (not_after) { *not_after = this->nextUpdate; } return (t >= this->thisUpdate && t <= this->nextUpdate); } METHOD(certificate_t, get_encoding, bool, private_x509_crl_t *this, cred_encoding_type_t type, chunk_t *encoding) { if (type == CERT_ASN1_DER) { *encoding = chunk_clone(this->encoding); return TRUE; } return lib->encoding->encode(lib->encoding, type, NULL, encoding, CRED_PART_X509_CRL_ASN1_DER, this->encoding, CRED_PART_END); } METHOD(certificate_t, equals, bool, private_x509_crl_t *this, certificate_t *other) { chunk_t encoding; bool equal; if ((certificate_t*)this == other) { return TRUE; } if (other->equals == (void*)equals) { /* skip allocation if we have the same implementation */ return chunk_equals(this->encoding, ((private_x509_crl_t*)other)->encoding); } if (!other->get_encoding(other, CERT_ASN1_DER, &encoding)) { return FALSE; } equal = chunk_equals(this->encoding, encoding); free(encoding.ptr); return equal; } /** * Destroy a revoked_t entry */ static void revoked_destroy(revoked_t *revoked) { free(revoked->serial.ptr); free(revoked); } METHOD(certificate_t, destroy, void, private_x509_crl_t *this) { if (ref_put(&this->ref)) { this->revoked->destroy_function(this->revoked, (void*)revoked_destroy); this->crl_uris->destroy_function(this->crl_uris, (void*)x509_cdp_destroy); signature_params_destroy(this->scheme); DESTROY_IF(this->issuer); free(this->authKeyIdentifier.ptr); free(this->encoding.ptr); free(this->critical_extension_oid.ptr); if (this->generated) { free(this->crlNumber.ptr); free(this->baseCrlNumber.ptr); free(this->signature.ptr); free(this->tbsCertList.ptr); } free(this); } } /** * create an empty but initialized X.509 crl */ static private_x509_crl_t* create_empty(void) { private_x509_crl_t *this; INIT(this, .public = { .crl = { .certificate = { .get_type = _get_type, .get_subject = _get_issuer, .get_issuer = _get_issuer, .has_subject = _has_issuer, .has_issuer = _has_issuer, .issued_by = _issued_by, .get_public_key = _get_public_key, .get_validity = _get_validity, .get_encoding = _get_encoding, .equals = _equals, .get_ref = _get_ref, .destroy = _destroy, }, .get_serial = _get_serial, .get_authKeyIdentifier = _get_authKeyIdentifier, .is_delta_crl = _is_delta_crl, .create_delta_crl_uri_enumerator = _create_delta_crl_uri_enumerator, .create_enumerator = _create_enumerator, }, }, .revoked = linked_list_create(), .crl_uris = linked_list_create(), .ref = 1, ); return this; } /** * See header. */ x509_crl_t *x509_crl_load(certificate_type_t type, va_list args) { chunk_t blob = chunk_empty; while (TRUE) { switch (va_arg(args, builder_part_t)) { case BUILD_BLOB_ASN1_DER: blob = va_arg(args, chunk_t); continue; case BUILD_END: break; default: return NULL; } break; } if (blob.ptr) { private_x509_crl_t *crl = create_empty(); crl->encoding = chunk_clone(blob); if (parse(crl)) { return &crl->public; } destroy(crl); } return NULL; }; /** * Read certificate status from enumerator, copy to crl */ static void read_revoked(private_x509_crl_t *crl, enumerator_t *enumerator) { revoked_t *revoked; chunk_t serial; time_t date; crl_reason_t reason; while (enumerator->enumerate(enumerator, &serial, &date, &reason)) { INIT(revoked, .serial = chunk_clone(serial), .date = date, .reason = reason, ); crl->revoked->insert_last(crl->revoked, revoked); } } /** * Generate CRL encoding, sign CRL */ static bool generate(private_x509_crl_t *this, certificate_t *cert, private_key_t *key, hash_algorithm_t digest_alg) { chunk_t extensions = chunk_empty, certList = chunk_empty, serial; chunk_t crlDistributionPoints = chunk_empty, baseCrlNumber = chunk_empty; chunk_t sig_scheme = chunk_empty, criticalExtension = chunk_empty; enumerator_t *enumerator; crl_reason_t reason; time_t date; x509_t *x509; x509 = (x509_t*)cert; this->issuer = cert->get_subject(cert); this->issuer = this->issuer->clone(this->issuer); this->authKeyIdentifier = chunk_clone(x509->get_subjectKeyIdentifier(x509)); /* select signature scheme, if not already specified */ if (!this->scheme) { INIT(this->scheme, .scheme = signature_scheme_from_oid( hasher_signature_algorithm_to_oid(digest_alg, key->get_type(key))), ); } if (this->scheme->scheme == SIGN_UNKNOWN) { return FALSE; } if (!signature_params_build(this->scheme, &sig_scheme)) { return FALSE; } enumerator = create_enumerator(this); while (enumerator->enumerate(enumerator, &serial, &date, &reason)) { chunk_t revoked, entry_ext = chunk_empty; if (reason != CRL_REASON_UNSPECIFIED) { entry_ext = asn1_wrap(ASN1_SEQUENCE, "m", asn1_wrap(ASN1_SEQUENCE, "mm", asn1_build_known_oid(OID_CRL_REASON_CODE), asn1_wrap(ASN1_OCTET_STRING, "m", asn1_wrap(ASN1_ENUMERATED, "c", chunk_from_chars(reason))))); } revoked = asn1_wrap(ASN1_SEQUENCE, "mmm", asn1_integer("c", serial), asn1_from_time(&date, ASN1_UTCTIME), entry_ext); certList = chunk_cat("mm", certList, revoked); } enumerator->destroy(enumerator); crlDistributionPoints = x509_build_crlDistributionPoints(this->crl_uris, OID_FRESHEST_CRL); if (this->baseCrlNumber.len) { baseCrlNumber = asn1_wrap(ASN1_SEQUENCE, "mmm", asn1_build_known_oid(OID_DELTA_CRL_INDICATOR), asn1_wrap(ASN1_BOOLEAN, "c", chunk_from_chars(0xFF)), asn1_wrap(ASN1_OCTET_STRING, "m", asn1_integer("c", this->baseCrlNumber))); } if (this->critical_extension_oid.len > 0) { criticalExtension = asn1_wrap(ASN1_SEQUENCE, "mmm", asn1_simple_object(ASN1_OID, this->critical_extension_oid), asn1_simple_object(ASN1_BOOLEAN, chunk_from_chars(0xFF)), asn1_simple_object(ASN1_OCTET_STRING, chunk_empty)); } extensions = asn1_wrap(ASN1_CONTEXT_C_0, "m", asn1_wrap(ASN1_SEQUENCE, "mmmmm", asn1_wrap(ASN1_SEQUENCE, "mm", asn1_build_known_oid(OID_AUTHORITY_KEY_ID), asn1_wrap(ASN1_OCTET_STRING, "m", asn1_wrap(ASN1_SEQUENCE, "m", asn1_wrap(ASN1_CONTEXT_S_0, "c", this->authKeyIdentifier)))), asn1_wrap(ASN1_SEQUENCE, "mm", asn1_build_known_oid(OID_CRL_NUMBER), asn1_wrap(ASN1_OCTET_STRING, "m", asn1_integer("c", this->crlNumber))), crlDistributionPoints, baseCrlNumber, criticalExtension)); this->tbsCertList = asn1_wrap(ASN1_SEQUENCE, "cccmmmm", ASN1_INTEGER_1, sig_scheme, this->issuer->get_encoding(this->issuer), asn1_from_time(&this->thisUpdate, ASN1_UTCTIME), asn1_from_time(&this->nextUpdate, ASN1_UTCTIME), asn1_wrap(ASN1_SEQUENCE, "m", certList), extensions); if (!key->sign(key, this->scheme->scheme, this->scheme->params, this->tbsCertList, &this->signature)) { chunk_free(&sig_scheme); return FALSE; } this->encoding = asn1_wrap(ASN1_SEQUENCE, "cmm", this->tbsCertList, sig_scheme, asn1_bitstring("c", this->signature)); return TRUE; } /** * See header. */ x509_crl_t *x509_crl_gen(certificate_type_t type, va_list args) { hash_algorithm_t digest_alg = HASH_SHA1; private_x509_crl_t *crl; certificate_t *cert = NULL; private_key_t *key = NULL; crl = create_empty(); crl->generated = TRUE; while (TRUE) { builder_part_t part = va_arg(args, builder_part_t); switch (part) { case BUILD_SIGNING_KEY: key = va_arg(args, private_key_t*); continue; case BUILD_SIGNING_CERT: cert = va_arg(args, certificate_t*); continue; case BUILD_NOT_BEFORE_TIME: crl->thisUpdate = va_arg(args, time_t); continue; case BUILD_NOT_AFTER_TIME: crl->nextUpdate = va_arg(args, time_t); continue; case BUILD_SERIAL: crl->crlNumber = va_arg(args, chunk_t); crl->crlNumber = chunk_clone(crl->crlNumber); continue; case BUILD_SIGNATURE_SCHEME: crl->scheme = va_arg(args, signature_params_t*); crl->scheme = signature_params_clone(crl->scheme); continue; case BUILD_DIGEST_ALG: digest_alg = va_arg(args, int); continue; case BUILD_REVOKED_ENUMERATOR: read_revoked(crl, va_arg(args, enumerator_t*)); continue; case BUILD_BASE_CRL: crl->baseCrlNumber = va_arg(args, chunk_t); crl->baseCrlNumber = chunk_clone(crl->baseCrlNumber); break; case BUILD_CRL_DISTRIBUTION_POINTS: { enumerator_t *enumerator; linked_list_t *list; x509_cdp_t *in, *cdp; list = va_arg(args, linked_list_t*); enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, &in)) { INIT(cdp, .uri = strdup(in->uri), .issuer = in->issuer ? in->issuer->clone(in->issuer) : NULL, ); crl->crl_uris->insert_last(crl->crl_uris, cdp); } enumerator->destroy(enumerator); continue; } case BUILD_CRITICAL_EXTENSION: crl->critical_extension_oid = chunk_clone(va_arg(args, chunk_t)); continue; case BUILD_END: break; default: destroy(crl); return NULL; } break; } if (key && cert && cert->get_type(cert) == CERT_X509 && generate(crl, cert, key, digest_alg)) { return &crl->public; } destroy(crl); return NULL; }