/* * Copyright (C) 2008 Martin Willi * 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. * * $Id: x509_crl.c 4936 2009-03-12 18:07:32Z tobias $ */ #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; /** * Authority Key Identifier */ identification_t *authKeyIdentifier; /** * Authority Key Serial Number */ chunk_t authKeySerialNumber; /** * Signature algorithm */ int algorithm; /** * Signature */ chunk_t signature; /** * reference counter */ refcount_t ref; }; /** * from x509_cert */ extern identification_t* x509_parse_authorityKeyIdentifier( chunk_t blob, int level0, chunk_t *authKeySerialNumber); /** * 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_t userCertificate = chunk_empty; int objectID; int sig_alg = OID_UNKNOWN; bool success = FALSE; bool critical; 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(" v%d", this->version); break; case CRL_OBJ_SIG_ALG: sig_alg = asn1_parse_algorithmIdentifier(object, level, NULL); break; case CRL_OBJ_ISSUER: this->issuer = identification_create_from_encoding(ID_DER_ASN1_DN, object); DBG2(" '%D'", 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 = userCertificate; revoked->date = asn1_parse_time(object, level); revoked->reason = CRL_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(" %s", critical ? "TRUE" : "FALSE"); break; case CRL_OBJ_CRL_ENTRY_EXTN_VALUE: case CRL_OBJ_EXTN_VALUE: { int extn_oid = asn1_known_oid(extnID); if (revoked && extn_oid == OID_CRL_REASON_CODE) { if (*object.ptr == ASN1_ENUMERATED && asn1_length(&object) == 1) { revoked->reason = *object.ptr; } DBG2(" '%N'", crl_reason_names, revoked->reason); } else if (extn_oid == OID_AUTHORITY_KEY_ID) { this->authKeyIdentifier = x509_parse_authorityKeyIdentifier(object, level, &this->authKeySerialNumber); } else if (extn_oid == OID_CRL_NUMBER) { if (!asn1_parse_simple_object(&object, ASN1_INTEGER, level, "crlNumber")) { goto end; } this->crlNumber = object; } } break; case CRL_OBJ_ALGORITHM: { this->algorithm = asn1_parse_algorithmIdentifier(object, level, NULL); if (this->algorithm != sig_alg) { DBG1(" signature algorithms do not agree"); goto end; } break; } case CRL_OBJ_SIGNATURE: this->signature = object; break; default: break; } } success = parser->success(parser); end: parser->destroy(parser); return success; } /** * enumerator filter callback for create_enumerator */ static bool filter(void *data, revoked_t **revoked, chunk_t *serial, void *p2, time_t *date, void *p3, crl_reason_t *reason) { if (serial) { *serial = (*revoked)->serial; } if (date) { *date = (*revoked)->date; } if (reason) { *reason = (*revoked)->reason; } return TRUE; } /** * Implementation of crl_t.get_serial. */ static chunk_t get_serial(private_x509_crl_t *this) { return this->crlNumber; } /** * Implementation of crl_t.get_authKeyIdentifier. */ static identification_t* get_authKeyIdentifier(private_x509_crl_t *this) { return this->authKeyIdentifier; } /** * Implementation of crl_t.create_enumerator. */ static enumerator_t* create_enumerator(private_x509_crl_t *this) { return enumerator_create_filter( this->revoked->create_enumerator(this->revoked), (void*)filter, NULL, NULL); } /** * Implementation of certificate_t.get_type */ static certificate_type_t get_type(private_x509_crl_t *this) { return CERT_X509_CRL; } /** * Implementation of certificate_t.get_issuer and get_subject */ static identification_t* get_issuer(private_x509_crl_t *this) { return this->issuer; } /** * Implementation of certificate_t.has_subject and has_issuer. */ static id_match_t has_issuer(private_x509_crl_t *this, identification_t *issuer) { id_match_t match; if (issuer->get_type(issuer) == ID_PUBKEY_SHA1) { if (this->authKeyIdentifier) { match = issuer->matches(issuer, this->authKeyIdentifier); } else { match = ID_MATCH_NONE; } } else { match = this->issuer->matches(this->issuer, issuer); } return match; } /** * Implementation of certificate_t.issued_by */ static bool issued_by(private_x509_crl_t *this, certificate_t *issuer) { public_key_t *key; signature_scheme_t scheme; bool valid; x509_t *x509 = (x509_t*)issuer; /* 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)) { return FALSE; } /* get the public key of the issuer */ key = issuer->get_public_key(issuer); /* compare keyIdentifiers if available, otherwise use DNs */ if (this->authKeyIdentifier && key) { identification_t *subjectKeyIdentifier = key->get_id(key, ID_PUBKEY_SHA1); if (!subjectKeyIdentifier->equals(subjectKeyIdentifier, this->authKeyIdentifier)) { return FALSE; } } else { if (!this->issuer->equals(this->issuer, issuer->get_subject(issuer))) { return FALSE; } } /* TODO: generic OID to scheme mapper? */ switch (this->algorithm) { case OID_MD5_WITH_RSA: scheme = SIGN_RSA_EMSA_PKCS1_MD5; break; case OID_SHA1_WITH_RSA: scheme = SIGN_RSA_EMSA_PKCS1_SHA1; break; case OID_SHA256_WITH_RSA: scheme = SIGN_RSA_EMSA_PKCS1_SHA256; break; case OID_SHA384_WITH_RSA: scheme = SIGN_RSA_EMSA_PKCS1_SHA384; break; case OID_SHA512_WITH_RSA: scheme = SIGN_RSA_EMSA_PKCS1_SHA512; break; case OID_ECDSA_WITH_SHA1: scheme = SIGN_ECDSA_WITH_SHA1; break; default: return FALSE; } if (key == NULL) { return FALSE; } valid = key->verify(key, scheme, this->tbsCertList, this->signature); key->destroy(key); return valid; } /** * Implementation of certificate_t.get_public_key */ static public_key_t* get_public_key(private_x509_crl_t *this) { return NULL; } /** * Implementation of certificate_t.asdf */ static private_x509_crl_t* get_ref(private_x509_crl_t *this) { ref_get(&this->ref); return this; } /** * Implementation of certificate_t.get_validity. */ static bool get_validity(private_x509_crl_t *this, time_t *when, time_t *not_before, time_t *not_after) { time_t t; if (when) { t = *when; } else { t = time(NULL); } if (not_before) { *not_before = this->thisUpdate; } if (not_after) { *not_after = this->nextUpdate; } return (t <= this->nextUpdate); } /** * Implementation of certificate_t.is_newer. */ static bool is_newer(private_x509_crl_t *this, crl_t *that) { chunk_t that_crlNumber = that->get_serial(that); bool new; /* compare crlNumbers if available - otherwise use thisUpdate */ if (this->crlNumber.ptr != NULL && that_crlNumber.ptr != NULL) { new = chunk_compare(this->crlNumber, that_crlNumber) > 0; DBG1(" crl #%#B is %s - existing crl #%#B %s", &this->crlNumber, new ? "newer":"not newer", &that_crlNumber, new ? "replaced":"retained"); } else { certificate_t *this_cert = &this->public.crl.certificate; certificate_t *that_cert = &that->certificate; time_t this_update, that_update, now = time(NULL); this_cert->get_validity(this_cert, &now, &this_update, NULL); that_cert->get_validity(that_cert, &now, &that_update, NULL); new = this_update > that_update; DBG1(" crl from %T is %s - existing crl from %T %s", &this_update, FALSE, new ? "newer":"not newer", &that_update, FALSE, new ? "replaced":"retained"); } return new; } /** * Implementation of certificate_t.get_encoding. */ static chunk_t get_encoding(private_x509_crl_t *this) { return chunk_clone(this->encoding); } /** * Implementation of certificate_t.equals. */ static bool equals(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); } encoding = other->get_encoding(other); equal = chunk_equals(this->encoding, encoding); free(encoding.ptr); return equal; } /** * Implementation of certificate_t.destroy */ static void destroy(private_x509_crl_t *this) { if (ref_put(&this->ref)) { this->revoked->destroy_function(this->revoked, free); DESTROY_IF(this->issuer); DESTROY_IF(this->authKeyIdentifier); free(this->encoding.ptr); free(this); } } /** * create an empty but initialized X.509 crl */ static private_x509_crl_t* create_empty(void) { private_x509_crl_t *this = malloc_thing(private_x509_crl_t); this->public.crl.get_serial = (chunk_t (*)(crl_t*))get_serial; this->public.crl.get_authKeyIdentifier = (identification_t* (*)(crl_t*))get_authKeyIdentifier; this->public.crl.create_enumerator = (enumerator_t* (*)(crl_t*))create_enumerator; this->public.crl.certificate.get_type = (certificate_type_t (*)(certificate_t *this))get_type; this->public.crl.certificate.get_subject = (identification_t* (*)(certificate_t *this))get_issuer; this->public.crl.certificate.get_issuer = (identification_t* (*)(certificate_t *this))get_issuer; this->public.crl.certificate.has_subject = (id_match_t (*)(certificate_t*, identification_t *subject))has_issuer; this->public.crl.certificate.has_issuer = (id_match_t (*)(certificate_t*, identification_t *issuer))has_issuer; this->public.crl.certificate.issued_by = (bool (*)(certificate_t *this, certificate_t *issuer))issued_by; this->public.crl.certificate.get_public_key = (public_key_t* (*)(certificate_t *this))get_public_key; this->public.crl.certificate.get_validity = (bool (*)(certificate_t*, time_t *when, time_t *, time_t*))get_validity; this->public.crl.certificate.is_newer = (bool (*)(certificate_t*,certificate_t*))is_newer; this->public.crl.certificate.get_encoding = (chunk_t (*)(certificate_t*))get_encoding; this->public.crl.certificate.equals = (bool (*)(certificate_t*, certificate_t *other))equals; this->public.crl.certificate.get_ref = (certificate_t* (*)(certificate_t *this))get_ref; this->public.crl.certificate.destroy = (void (*)(certificate_t *this))destroy; this->encoding = chunk_empty; this->tbsCertList = chunk_empty; this->issuer = NULL; this->crlNumber = chunk_empty; this->revoked = linked_list_create(); this->authKeyIdentifier = NULL; this->authKeySerialNumber = chunk_empty; this->ref = 1; return this; } /** * create an X.509 crl from a chunk */ static private_x509_crl_t* create_from_chunk(chunk_t chunk) { private_x509_crl_t *this = create_empty(); this->encoding = chunk; if (!parse(this)) { destroy(this); return NULL; } return this; } /** * create an X.509 crl from a file */ static private_x509_crl_t* create_from_file(char *path) { bool pgp = FALSE; chunk_t chunk; private_x509_crl_t *this; if (!pem_asn1_load_file(path, NULL, &chunk, &pgp)) { return NULL; } this = create_from_chunk(chunk); if (this == NULL) { DBG1(" could not parse loaded crl file '%s'",path); return NULL; } DBG1(" loaded crl file '%s'", path); return this; } typedef struct private_builder_t private_builder_t; /** * Builder implementation for certificate loading */ struct private_builder_t { /** implements the builder interface */ builder_t public; /** loaded CRL */ private_x509_crl_t *crl; }; /** * Implementation of builder_t.build */ static private_x509_crl_t *build(private_builder_t *this) { private_x509_crl_t *crl = this->crl; free(this); return crl; } /** * Implementation of builder_t.add */ static void add(private_builder_t *this, builder_part_t part, ...) { if (!this->crl) { va_list args; chunk_t chunk; switch (part) { case BUILD_FROM_FILE: { va_start(args, part); this->crl = create_from_file(va_arg(args, char*)); va_end(args); return; } case BUILD_BLOB_ASN1_DER: { va_start(args, part); chunk = va_arg(args, chunk_t); this->crl = create_from_chunk(chunk_clone(chunk)); va_end(args); return; } default: break; } } if (this->crl) { destroy(this->crl); } builder_cancel(&this->public); } /** * Builder construction function */ builder_t *x509_crl_builder(certificate_type_t type) { private_builder_t *this; if (type != CERT_X509_CRL) { return NULL; } this = malloc_thing(private_builder_t); this->crl = NULL; this->public.add = (void(*)(builder_t *this, builder_part_t part, ...))add; this->public.build = (void*(*)(builder_t *this))build; return &this->public; }