/* * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * * 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. */ /* * Copyright (C) 2010 secunet Security Networks AG * Copyright (C) 2010 Thomas Egerer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define _GNU_SOURCE #include #include #include #include "openssl_x509.h" #include "openssl_util.h" #include #include #include typedef struct private_openssl_x509_t private_openssl_x509_t; /** * Private data of an openssl_x509_t object. */ struct private_openssl_x509_t { /** * Public openssl_x509_t interface. */ openssl_x509_t public; /** * OpenSSL certificate representation */ X509 *x509; /** * DER encoded certificate */ chunk_t encoding; /** * SHA1 hash of the certificate */ chunk_t hash; /** * X509 flags */ x509_flag_t flags; /** * Pathlen constraint */ int pathlen; /** * certificate subject */ identification_t *subject; /** * certificate issuer */ identification_t *issuer; /** * Certificates public key */ public_key_t *pubkey; /** * subjectKeyIdentifier as read from cert */ chunk_t subjectKeyIdentifier; /** * authorityKeyIdentifier as read from cert */ chunk_t authKeyIdentifier; /** * Start time of certificate validity */ time_t notBefore; /** * End time of certificate validity */ time_t notAfter; /** * Signature scheme of the certificate */ signature_scheme_t scheme; /** * subjectAltNames */ linked_list_t *subjectAltNames; /** * issuerAltNames */ linked_list_t *issuerAltNames; /** * List of CRL URIs */ linked_list_t *crl_uris; /** * List of OCSP URIs */ linked_list_t *ocsp_uris; /** * References to this cert */ refcount_t ref; }; /** * Convert a GeneralName to an identification_t. */ static identification_t *general_name2id(GENERAL_NAME *name) { if (!name) { return NULL; } switch (name->type) { case GEN_EMAIL: return identification_create_from_encoding(ID_RFC822_ADDR, openssl_asn1_str2chunk(name->d.rfc822Name)); case GEN_DNS: return identification_create_from_encoding(ID_FQDN, openssl_asn1_str2chunk(name->d.dNSName)); case GEN_URI: return identification_create_from_encoding(ID_DER_ASN1_GN_URI, openssl_asn1_str2chunk(name->d.uniformResourceIdentifier)); case GEN_IPADD: { chunk_t chunk = openssl_asn1_str2chunk(name->d.iPAddress); if (chunk.len == 4) { return identification_create_from_encoding(ID_IPV4_ADDR, chunk); } if (chunk.len == 16) { return identification_create_from_encoding(ID_IPV6_ADDR, chunk); } return NULL; } case GEN_DIRNAME : return openssl_x509_name2id(name->d.directoryName); case GEN_OTHERNAME: if (OBJ_obj2nid(name->d.otherName->type_id) == NID_ms_upn && name->d.otherName->value->type == V_ASN1_UTF8STRING) { return identification_create_from_encoding(ID_RFC822_ADDR, openssl_asn1_str2chunk( name->d.otherName->value->value.utf8string)); } return NULL; default: return NULL; } } METHOD(x509_t, get_flags, x509_flag_t, private_openssl_x509_t *this) { return this->flags; } METHOD(x509_t, get_serial, chunk_t, private_openssl_x509_t *this) { return openssl_asn1_str2chunk(X509_get_serialNumber(this->x509)); } METHOD(x509_t, get_subjectKeyIdentifier, chunk_t, private_openssl_x509_t *this) { chunk_t fingerprint; if (this->subjectKeyIdentifier.len) { return this->subjectKeyIdentifier; } if (this->pubkey->get_fingerprint(this->pubkey, KEYID_PUBKEY_SHA1, &fingerprint)) { return fingerprint; } return chunk_empty; } METHOD(x509_t, get_authKeyIdentifier, chunk_t, private_openssl_x509_t *this) { if (this->authKeyIdentifier.len) { return this->authKeyIdentifier; } return chunk_empty; } METHOD(x509_t, get_pathLenConstraint, int, private_openssl_x509_t *this) { return this->pathlen; } METHOD(x509_t, create_subjectAltName_enumerator, enumerator_t*, private_openssl_x509_t *this) { return this->subjectAltNames->create_enumerator(this->subjectAltNames); } METHOD(x509_t, create_crl_uri_enumerator, enumerator_t*, private_openssl_x509_t *this) { return this->crl_uris->create_enumerator(this->crl_uris); } METHOD(x509_t, create_ocsp_uri_enumerator, enumerator_t*, private_openssl_x509_t *this) { return this->ocsp_uris->create_enumerator(this->ocsp_uris); } METHOD(x509_t, create_ipAddrBlock_enumerator, enumerator_t*, private_openssl_x509_t *this) { /* TODO */ return enumerator_create_empty(); } METHOD(certificate_t, get_type, certificate_type_t, private_openssl_x509_t *this) { return CERT_X509; } METHOD(certificate_t, get_subject, identification_t*, private_openssl_x509_t *this) { return this->subject; } METHOD(certificate_t, get_issuer, identification_t*, private_openssl_x509_t *this) { return this->issuer; } METHOD(certificate_t, has_subject, id_match_t, private_openssl_x509_t *this, identification_t *subject) { identification_t *current; enumerator_t *enumerator; id_match_t match, best; chunk_t encoding; if (subject->get_type(subject) == ID_KEY_ID) { encoding = subject->get_encoding(subject); if (chunk_equals(this->hash, encoding)) { return ID_MATCH_PERFECT; } if (this->subjectKeyIdentifier.len && chunk_equals(this->subjectKeyIdentifier, encoding)) { return ID_MATCH_PERFECT; } if (this->pubkey && this->pubkey->has_fingerprint(this->pubkey, encoding)) { return ID_MATCH_PERFECT; } } best = this->subject->matches(this->subject, subject); enumerator = create_subjectAltName_enumerator(this); while (enumerator->enumerate(enumerator, ¤t)) { match = current->matches(current, subject); if (match > best) { best = match; } } enumerator->destroy(enumerator); return best; } METHOD(certificate_t, has_issuer, id_match_t, private_openssl_x509_t *this, identification_t *issuer) { /* issuerAltNames currently not supported */ return this->issuer->matches(this->issuer, issuer); } METHOD(certificate_t, issued_by, bool, private_openssl_x509_t *this, certificate_t *issuer) { public_key_t *key; bool valid; x509_t *x509 = (x509_t*)issuer; chunk_t tbs; if (&this->public.x509.interface == issuer) { if (this->flags & X509_SELF_SIGNED) { return TRUE; } } else { if (issuer->get_type(issuer) != CERT_X509) { return FALSE; } if (!(x509->get_flags(x509) & X509_CA)) { return FALSE; } if (!this->issuer->equals(this->issuer, issuer->get_subject(issuer))) { return FALSE; } } if (this->scheme == SIGN_UNKNOWN) { return FALSE; } key = issuer->get_public_key(issuer); if (!key) { return FALSE; } tbs = openssl_i2chunk(X509_CINF, this->x509->cert_info); valid = key->verify(key, this->scheme, tbs, openssl_asn1_str2chunk(this->x509->signature)); free(tbs.ptr); key->destroy(key); return valid; } METHOD(certificate_t, get_public_key, public_key_t*, private_openssl_x509_t *this) { return this->pubkey->get_ref(this->pubkey); } METHOD(certificate_t, get_validity, bool, private_openssl_x509_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->notBefore; } if (not_after) { *not_after = this->notAfter; } return (t >= this->notBefore && t <= this->notAfter); } METHOD(certificate_t, get_encoding, bool, private_openssl_x509_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_ASN1_DER, this->encoding, CRED_PART_END); } METHOD(certificate_t, equals, bool, private_openssl_x509_t *this, certificate_t *other) { chunk_t encoding; bool equal; if (this == (private_openssl_x509_t*)other) { return TRUE; } if (other->get_type(other) != CERT_X509) { return FALSE; } if (other->equals == (void*)equals) { /* skip allocation if we have the same implementation */ encoding = ((private_openssl_x509_t*)other)->encoding; return chunk_equals(this->encoding, encoding); } if (!other->get_encoding(other, CERT_ASN1_DER, &encoding)) { return FALSE; } equal = chunk_equals(this->encoding, encoding); free(encoding.ptr); return equal; } METHOD(certificate_t, get_ref, certificate_t*, private_openssl_x509_t *this) { ref_get(&this->ref); return &this->public.x509.interface; } METHOD(certificate_t, destroy, void, private_openssl_x509_t *this) { if (ref_put(&this->ref)) { if (this->x509) { X509_free(this->x509); } DESTROY_IF(this->subject); DESTROY_IF(this->issuer); DESTROY_IF(this->pubkey); free(this->subjectKeyIdentifier.ptr); free(this->authKeyIdentifier.ptr); free(this->encoding.ptr); free(this->hash.ptr); this->subjectAltNames->destroy_offset(this->subjectAltNames, offsetof(identification_t, destroy)); this->issuerAltNames->destroy_offset(this->issuerAltNames, offsetof(identification_t, destroy)); this->crl_uris->destroy_function(this->crl_uris, free); this->ocsp_uris->destroy_function(this->ocsp_uris, free); free(this); } } /** * Create an empty certificate */ static private_openssl_x509_t *create_empty() { private_openssl_x509_t *this; INIT(this, .public = { .x509 = { .interface = { .get_type = _get_type, .get_subject = _get_subject, .get_issuer = _get_issuer, .has_subject = _has_subject, .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_flags = _get_flags, .get_serial = _get_serial, .get_subjectKeyIdentifier = _get_subjectKeyIdentifier, .get_authKeyIdentifier = _get_authKeyIdentifier, .get_pathLenConstraint = _get_pathLenConstraint, .create_subjectAltName_enumerator = _create_subjectAltName_enumerator, .create_crl_uri_enumerator = _create_crl_uri_enumerator, .create_ocsp_uri_enumerator = _create_ocsp_uri_enumerator, .create_ipAddrBlock_enumerator = _create_ipAddrBlock_enumerator, }, }, .subjectAltNames = linked_list_create(), .issuerAltNames = linked_list_create(), .crl_uris = linked_list_create(), .ocsp_uris = linked_list_create(), .pathlen = X509_NO_PATH_LEN_CONSTRAINT, .ref = 1, ); return this; } /** * parse an extionsion containing GENERAL_NAMES into a list */ static bool parse_generalNames_ext(linked_list_t *list, X509_EXTENSION *ext) { GENERAL_NAMES *names; GENERAL_NAME *name; identification_t *id; int i, num; names = X509V3_EXT_d2i(ext); if (!names) { return FALSE; } num = sk_GENERAL_NAME_num(names); for (i = 0; i < num; i++) { name = sk_GENERAL_NAME_value(names, i); id = general_name2id(name); if (id) { list->insert_last(list, id); } GENERAL_NAME_free(name); } sk_GENERAL_NAME_free(names); return TRUE; } /** * parse basic constraints */ static bool parse_basicConstraints_ext(private_openssl_x509_t *this, X509_EXTENSION *ext) { BASIC_CONSTRAINTS *constraints; constraints = (BASIC_CONSTRAINTS*)X509V3_EXT_d2i(ext); if (constraints) { if (constraints->ca) { this->flags |= X509_CA; } if (constraints->pathlen) { this->pathlen = ASN1_INTEGER_get(constraints->pathlen); } BASIC_CONSTRAINTS_free(constraints); return TRUE; } return FALSE; } /** * Parse CRL distribution points */ static bool parse_crlDistributionPoints_ext(private_openssl_x509_t *this, X509_EXTENSION *ext) { CRL_DIST_POINTS *cdps; DIST_POINT *cdp; identification_t *id; char *uri; int i, j, point_num, name_num; cdps = X509V3_EXT_d2i(ext); if (!cdps) { return FALSE; } point_num = sk_DIST_POINT_num(cdps); for (i = 0; i < point_num; i++) { cdp = sk_DIST_POINT_value(cdps, i); if (cdp) { if (cdp->distpoint && cdp->distpoint->type == 0 && cdp->distpoint->name.fullname) { name_num = sk_GENERAL_NAME_num(cdp->distpoint->name.fullname); for (j = 0; j < name_num; j++) { id = general_name2id(sk_GENERAL_NAME_value( cdp->distpoint->name.fullname, j)); if (id) { if (asprintf(&uri, "%Y", id) > 0) { this->crl_uris->insert_first(this->crl_uris, uri); } id->destroy(id); } } } DIST_POINT_free(cdp); } } sk_DIST_POINT_free(cdps); return TRUE; } /** * Parse authorityInfoAccess with OCSP URIs */ static bool parse_authorityInfoAccess_ext(private_openssl_x509_t *this, X509_EXTENSION *ext) { AUTHORITY_INFO_ACCESS *infos; ACCESS_DESCRIPTION *desc; identification_t *id; int i, num; char *uri; infos = X509V3_EXT_d2i(ext); if (!infos) { return FALSE; } num = sk_ACCESS_DESCRIPTION_num(infos); for (i = 0; i < num; i++) { desc = sk_ACCESS_DESCRIPTION_value(infos, i); if (desc) { if (openssl_asn1_known_oid(desc->method) == OID_OCSP) { id = general_name2id(desc->location); if (id) { if (asprintf(&uri, "%Y", id) > 0) { this->ocsp_uris->insert_first(this->ocsp_uris, uri); } id->destroy(id); } } ACCESS_DESCRIPTION_free(desc); } } sk_ACCESS_DESCRIPTION_free(infos); return TRUE; } /** * Parse authorityKeyIdentifier extension */ static bool parse_authKeyIdentifier_ext(private_openssl_x509_t *this, X509_EXTENSION *ext) { AUTHORITY_KEYID *keyid; keyid = (AUTHORITY_KEYID*)X509V3_EXT_d2i(ext); if (keyid) { free(this->authKeyIdentifier.ptr); this->authKeyIdentifier = chunk_clone( openssl_asn1_str2chunk(keyid->keyid)); AUTHORITY_KEYID_free(keyid); return TRUE; } return FALSE; } /** * Parse subjectKeyIdentifier extension */ static bool parse_subjectKeyIdentifier_ext(private_openssl_x509_t *this, X509_EXTENSION *ext) { chunk_t ostr; ostr = openssl_asn1_str2chunk(X509_EXTENSION_get_data(ext)); /* quick and dirty unwrap of octet string */ if (ostr.len > 2 && ostr.ptr[0] == V_ASN1_OCTET_STRING && ostr.ptr[1] == ostr.len - 2) { free(this->subjectKeyIdentifier.ptr); this->subjectKeyIdentifier = chunk_clone(chunk_skip(ostr, 2)); return TRUE; } return FALSE; } /** * Parse X509 extensions we are interested in */ static bool parse_extensions(private_openssl_x509_t *this) { STACK_OF(X509_EXTENSION) *extensions; int i, num; extensions = this->x509->cert_info->extensions; if (extensions) { num = sk_X509_EXTENSION_num(extensions); for (i = 0; i < num; i++) { X509_EXTENSION *ext; bool ok; ext = sk_X509_EXTENSION_value(extensions, i); switch (OBJ_obj2nid(X509_EXTENSION_get_object(ext))) { case NID_info_access: ok = parse_authorityInfoAccess_ext(this, ext); break; case NID_authority_key_identifier: ok = parse_authKeyIdentifier_ext(this, ext); break; case NID_subject_key_identifier: ok = parse_subjectKeyIdentifier_ext(this, ext); break; case NID_subject_alt_name: ok = parse_generalNames_ext(this->subjectAltNames, ext); break; case NID_issuer_alt_name: ok = parse_generalNames_ext(this->issuerAltNames, ext); break; case NID_basic_constraints: ok = parse_basicConstraints_ext(this, ext); break; case NID_crl_distribution_points: ok = parse_crlDistributionPoints_ext(this, ext); break; default: ok = TRUE; break; } if (!ok) { return FALSE; } } } return TRUE; } /** * Parse ExtendedKeyUsage */ static void parse_extKeyUsage(private_openssl_x509_t *this) { EXTENDED_KEY_USAGE *usage; int i; usage = X509_get_ext_d2i(this->x509, NID_ext_key_usage, NULL, NULL); if (usage) { for (i = 0; i < sk_ASN1_OBJECT_num(usage); i++) { switch (OBJ_obj2nid(sk_ASN1_OBJECT_value(usage, i))) { case NID_server_auth: this->flags |= X509_SERVER_AUTH; break; case NID_client_auth: this->flags |= X509_CLIENT_AUTH; break; case NID_OCSP_sign: this->flags |= X509_OCSP_SIGNER; break; default: break; } } sk_ASN1_OBJECT_pop_free(usage, ASN1_OBJECT_free); } } /** * Parse a DER encoded x509 certificate */ static bool parse_certificate(private_openssl_x509_t *this) { const unsigned char *ptr = this->encoding.ptr; hasher_t *hasher; chunk_t chunk; this->x509 = d2i_X509(NULL, &ptr, this->encoding.len); if (!this->x509) { return FALSE; } this->subject = openssl_x509_name2id(X509_get_subject_name(this->x509)); this->issuer = openssl_x509_name2id(X509_get_issuer_name(this->x509)); switch (openssl_asn1_known_oid(this->x509->cert_info->key->algor->algorithm)) { case OID_RSA_ENCRYPTION: this->pubkey = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA, BUILD_BLOB_ASN1_DER, openssl_asn1_str2chunk(X509_get0_pubkey_bitstr(this->x509)), BUILD_END); break; case OID_EC_PUBLICKEY: /* for ECDSA, we need the full subjectPublicKeyInfo, as it contains * the curve parameters. */ chunk = openssl_i2chunk(X509_PUBKEY, X509_get_X509_PUBKEY(this->x509)); this->pubkey = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ECDSA, BUILD_BLOB_ASN1_DER, chunk, BUILD_END); free(chunk.ptr); break; default: DBG1(DBG_LIB, "unsupported public key algorithm"); break; } if (!this->subject || !this->issuer || !this->pubkey) { return FALSE; } this->notBefore = openssl_asn1_to_time(X509_get_notBefore(this->x509)); this->notAfter = openssl_asn1_to_time(X509_get_notAfter(this->x509)); if (!chunk_equals( openssl_asn1_obj2chunk(this->x509->cert_info->signature->algorithm), openssl_asn1_obj2chunk(this->x509->sig_alg->algorithm))) { return FALSE; } this->scheme = signature_scheme_from_oid(openssl_asn1_known_oid( this->x509->sig_alg->algorithm)); if (!parse_extensions(this)) { return TRUE; } parse_extKeyUsage(this); hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); if (!hasher) { return FALSE; } hasher->allocate_hash(hasher, this->encoding, &this->hash); hasher->destroy(hasher); if (issued_by(this, &this->public.x509.interface)) { this->flags |= X509_SELF_SIGNED; } return TRUE; } openssl_x509_t *openssl_x509_load(certificate_type_t type, va_list args) { chunk_t blob = chunk_empty; x509_flag_t flags = 0; while (TRUE) { switch (va_arg(args, builder_part_t)) { case BUILD_BLOB_ASN1_DER: blob = va_arg(args, chunk_t); continue; case BUILD_X509_FLAG: flags |= va_arg(args, x509_flag_t); continue; case BUILD_END: break; default: return NULL; } break; } if (blob.ptr) { private_openssl_x509_t *this; this = create_empty(); this->encoding = chunk_clone(blob); this->flags |= flags; if (parse_certificate(this)) { return &this->public; } DBG1(DBG_LIB, "OpenSSL X.509 parsing failed"); destroy(this); } return NULL; }