/* Certification Authority (CA) support for IKE authentication * Copyright (C) 2002-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. */ #include #include #include #include #include #include #include #include #include #include #include "constants.h" #include "defs.h" #include "log.h" #include "x509.h" #include "ca.h" #include "certs.h" #include "whack.h" #include "fetch.h" #include "smartcard.h" /* chained list of X.509 authority certificates (ca, aa, and ocsp) */ static cert_t *x509authcerts = NULL; /* chained list of X.509 certification authority information records */ static ca_info_t *ca_infos = NULL; /* * Checks if CA a is trusted by CA b */ bool trusted_ca(identification_t *a, identification_t *b, int *pathlen) { bool match = FALSE; /* no CA b specified -> any CA a is accepted */ if (b == NULL) { *pathlen = (a == NULL) ? 0 : X509_MAX_PATH_LEN; return TRUE; } /* no CA a specified -> trust cannot be established */ if (a == NULL) { *pathlen = X509_MAX_PATH_LEN; return FALSE; } *pathlen = 0; /* CA a equals CA b -> we have a match */ if (a->equals(a, b)) { return TRUE; } /* CA a might be a subordinate CA of b */ lock_authcert_list("trusted_ca"); while ((*pathlen)++ < X509_MAX_PATH_LEN) { certificate_t *certificate; identification_t *issuer; cert_t *cacert; cacert = get_authcert(a, chunk_empty, X509_CA); if (cacert == NULL) { break; } certificate = cacert->cert; /* is the certificate self-signed? */ { x509_t *x509 = (x509_t*)certificate; if (x509->get_flags(x509) & X509_SELF_SIGNED) { break; } } /* does the issuer of CA a match CA b? */ issuer = certificate->get_issuer(certificate); match = b->equals(b, issuer); /* we have a match and exit the loop */ if (match) { break; } /* go one level up in the CA chain */ a = issuer; } unlock_authcert_list("trusted_ca"); return match; } /* * does our CA match one of the requested CAs? */ bool match_requested_ca(linked_list_t *requested_ca, identification_t *our_ca, int *our_pathlen) { identification_t *ca; enumerator_t *enumerator; /* if no ca is requested than any ca will match */ if (requested_ca == NULL || requested_ca->get_count(requested_ca) == 0) { *our_pathlen = 0; return TRUE; } *our_pathlen = X509_MAX_PATH_LEN + 1; enumerator = requested_ca->create_enumerator(requested_ca); while (enumerator->enumerate(enumerator, &ca)) { int pathlen; if (trusted_ca(our_ca, ca, &pathlen) && pathlen < *our_pathlen) { *our_pathlen = pathlen; } } enumerator->destroy(enumerator); if (*our_pathlen > X509_MAX_PATH_LEN) { *our_pathlen = X509_MAX_PATH_LEN; return FALSE; } else { return TRUE; } } /* * free the first authority certificate in the chain */ static void free_first_authcert(void) { cert_t *first = x509authcerts; x509authcerts = first->next; cert_free(first); } /* * free all CA certificates */ void free_authcerts(void) { lock_authcert_list("free_authcerts"); while (x509authcerts != NULL) { free_first_authcert(); } unlock_authcert_list("free_authcerts"); } /* * get a X.509 authority certificate with a given subject or keyid */ cert_t* get_authcert(identification_t *subject, chunk_t keyid, x509_flag_t auth_flags) { cert_t *cert, *prev_cert = NULL; /* the authority certificate list is empty */ if (x509authcerts == NULL) { return NULL; } for (cert = x509authcerts; cert != NULL; prev_cert = cert, cert = cert->next) { certificate_t *certificate = cert->cert; x509_t *x509 = (x509_t*)certificate; /* skip non-matching types of authority certificates */ if (!(x509->get_flags(x509) & auth_flags)) { continue; } /* compare the keyid with the certificate's subjectKeyIdentifier */ if (keyid.ptr) { chunk_t subjectKeyId; subjectKeyId = x509->get_subjectKeyIdentifier(x509); if (subjectKeyId.ptr && !chunk_equals(keyid, subjectKeyId)) { continue; } } /* compare the subjectDistinguishedNames */ if (!certificate->has_subject(certificate, subject)) { continue; } /* found the authcert */ if (cert != x509authcerts) { /* bring the certificate up front */ prev_cert->next = cert->next; cert->next = x509authcerts; x509authcerts = cert; } return cert; } return NULL; } /* * add an authority certificate to the chained list */ cert_t* add_authcert(cert_t *cert, x509_flag_t auth_flags) { certificate_t *certificate = cert->cert; x509_t *x509 = (x509_t*)certificate; cert_t *old_cert; lock_authcert_list("add_authcert"); old_cert = get_authcert(certificate->get_subject(certificate), x509->get_subjectKeyIdentifier(x509), auth_flags); if (old_cert) { if (certificate->equals(certificate, old_cert->cert)) { DBG(DBG_CONTROL | DBG_PARSING , DBG_log(" authcert is already present and identical") ) unlock_authcert_list("add_authcert"); cert_free(cert); return old_cert; } else { /* cert is already present but will be replaced by new cert */ free_first_authcert(); DBG(DBG_CONTROL | DBG_PARSING , DBG_log(" existing authcert deleted") ) } } /* add new authcert to chained list */ cert->next = x509authcerts; x509authcerts = cert; cert_share(cert); /* set count to one */ DBG(DBG_CONTROL | DBG_PARSING, DBG_log(" authcert inserted") ) unlock_authcert_list("add_authcert"); return cert; } /* * Loads authority certificates */ void load_authcerts(char *type, char *path, x509_flag_t auth_flags) { enumerator_t *enumerator; struct stat st; char *file; DBG1(DBG_LIB, "loading %s certificates from '%s'", type, path); enumerator = enumerator_create_directory(path); if (!enumerator) { DBG1(DBG_LIB, " reading directory '%s' failed", path); return; } while (enumerator->enumerate(enumerator, NULL, &file, &st)) { cert_t *cert; if (!S_ISREG(st.st_mode)) { /* skip special file */ continue; } cert = load_cert(file, type, auth_flags); if (cert) { add_authcert(cert, auth_flags); } } enumerator->destroy(enumerator); } /* * list all X.509 authcerts with given auth flags in a chained list */ void list_authcerts(const char *caption, x509_flag_t auth_flags, bool utc) { lock_authcert_list("list_authcerts"); list_x509cert_chain(caption, x509authcerts, auth_flags, utc); unlock_authcert_list("list_authcerts"); } /* * get a cacert with a given subject or keyid from an alternative list */ static const cert_t* get_alt_cacert(identification_t *subject, chunk_t keyid, const cert_t *cert) { if (cert == NULL) { return NULL; } for (; cert != NULL; cert = cert->next) { certificate_t *certificate = cert->cert; /* compare the keyid with the certificate's subjectKeyIdentifier */ if (keyid.ptr) { x509_t *x509 = (x509_t*)certificate; chunk_t subjectKeyId; subjectKeyId = x509->get_subjectKeyIdentifier(x509); if (subjectKeyId.ptr && !chunk_equals(keyid, subjectKeyId)) { continue; } } /* compare the subjectDistinguishedNames */ if (!certificate->has_subject(certificate, subject)) { continue; } /* we found the cacert */ return cert; } return NULL; } /* establish trust into a candidate authcert by going up the trust chain. * validity and revocation status are not checked. */ bool trust_authcert_candidate(const cert_t *cert, const cert_t *alt_chain) { int pathlen; lock_authcert_list("trust_authcert_candidate"); for (pathlen = 0; pathlen < X509_MAX_PATH_LEN; pathlen++) { certificate_t *certificate = cert->cert; x509_t *x509 = (x509_t*)certificate; identification_t *subject = certificate->get_subject(certificate); identification_t *issuer = certificate->get_issuer(certificate); chunk_t authKeyID = x509->get_authKeyIdentifier(x509); const cert_t *authcert = NULL; DBG(DBG_CONTROL, DBG_log("subject: '%Y'", subject); DBG_log("issuer: '%Y'", issuer); if (authKeyID.ptr != NULL) { DBG_log("authkey: %#B", &authKeyID); } ) /* search in alternative chain first */ authcert = get_alt_cacert(issuer, authKeyID, alt_chain); if (authcert != NULL) { DBG(DBG_CONTROL, DBG_log("issuer cacert found in alternative chain") ) } else { /* search in trusted chain */ authcert = get_authcert(issuer, authKeyID, X509_CA); if (authcert != NULL) { DBG(DBG_CONTROL, DBG_log("issuer cacert found") ) } else { plog("issuer cacert not found"); unlock_authcert_list("trust_authcert_candidate"); return FALSE; } } if (!certificate->issued_by(certificate, authcert->cert)) { plog("certificate signature is invalid"); unlock_authcert_list("trust_authcert_candidate"); return FALSE; } DBG(DBG_CONTROL, DBG_log("certificate signature is valid") ) /* check if cert is a self-signed root ca */ if (pathlen > 0 && (x509->get_flags(x509) & X509_SELF_SIGNED)) { DBG(DBG_CONTROL, DBG_log("reached self-signed root ca") ) unlock_authcert_list("trust_authcert_candidate"); return TRUE; } /* go up one step in the trust chain */ cert = authcert; } plog("maximum ca path length of %d levels exceeded", X509_MAX_PATH_LEN); unlock_authcert_list("trust_authcert_candidate"); return FALSE; } /* * get a CA info record with a given authName or authKeyID */ ca_info_t* get_ca_info(identification_t *name, chunk_t keyid) { ca_info_t *ca= ca_infos; while (ca != NULL) { if ((keyid.ptr) ? same_keyid(keyid, ca->authKeyID) : name->equals(name, ca->authName)) { return ca; } ca = ca->next; } return NULL; } /* * free the dynamic memory used by a ca_info record */ static void free_ca_info(ca_info_t* ca_info) { if (ca_info == NULL) { return; } ca_info->crluris->destroy_function(ca_info->crluris, free); DESTROY_IF(ca_info->authName); free(ca_info->name); free(ca_info->ldaphost); free(ca_info->ldapbase); free(ca_info->ocspuri); free(ca_info->authKeyID.ptr); free(ca_info); } /* * free all CA certificates */ void free_ca_infos(void) { while (ca_infos != NULL) { ca_info_t *ca = ca_infos; ca_infos = ca_infos->next; free_ca_info(ca); } } /* * find a CA information record by name and optionally delete it */ bool find_ca_info_by_name(const char *name, bool delete) { ca_info_t **ca_p = &ca_infos; ca_info_t *ca = *ca_p; while (ca != NULL) { /* is there already an entry? */ if (streq(name, ca->name)) { if (delete) { lock_ca_info_list("find_ca_info_by_name"); *ca_p = ca->next; free_ca_info(ca); plog("deleting ca description \"%s\"", name); unlock_ca_info_list("find_ca_info_by_name"); } return TRUE; } ca_p = &ca->next; ca = *ca_p; } return FALSE; } /* * Create an empty ca_info_t record */ ca_info_t* create_ca_info(void) { ca_info_t *ca_info = malloc_thing(ca_info_t); memset(ca_info, 0, sizeof(ca_info_t)); ca_info->crluris = linked_list_create(); return ca_info; } /** * Adds a CA description to a chained list */ void add_ca_info(const whack_message_t *msg) { smartcard_t *sc = NULL; cert_t *cert = NULL; bool cached_cert = FALSE; if (find_ca_info_by_name(msg->name, FALSE)) { loglog(RC_DUPNAME, "attempt to redefine ca record \"%s\"", msg->name); return; } if (scx_on_smartcard(msg->cacert)) { /* load CA cert from smartcard */ cert = scx_load_cert(msg->cacert, &sc, &cached_cert); } else { /* load CA cert from file */ cert = load_ca_cert(msg->cacert); } if (cert) { certificate_t *certificate = cert->cert; x509_t *x509 = (x509_t*)certificate; identification_t *subject = certificate->get_subject(certificate); chunk_t subjectKeyID = x509->get_subjectKeyIdentifier(x509); ca_info_t *ca = NULL; /* does the authname already exist? */ ca = get_ca_info(subject, subjectKeyID); if (ca != NULL) { /* ca_info is already present */ loglog(RC_DUPNAME, " duplicate ca information in record \"%s\" found," "ignoring \"%s\"", ca->name, msg->name); cert_free(cert); return; } plog("added ca description \"%s\"", msg->name); /* create and initialize new ca_info record */ ca = create_ca_info(); /* name */ ca->name = clone_str(msg->name); /* authName */ ca->authName = subject->clone(subject); DBG(DBG_CONTROL, DBG_log("authname: '%Y'", subject) ) /* authKeyID */ if (subjectKeyID.ptr) { ca->authKeyID = chunk_clone(subjectKeyID); DBG(DBG_CONTROL | DBG_PARSING , DBG_log("authkey: %#B", &subjectKeyID) ) } /* ldaphost */ ca->ldaphost = clone_str(msg->ldaphost); /* ldapbase */ ca->ldapbase = clone_str(msg->ldapbase); /* ocspuri */ if (msg->ocspuri != NULL) { if (strncasecmp(msg->ocspuri, "http", 4) == 0) ca->ocspuri = clone_str(msg->ocspuri); else plog(" ignoring ocspuri with unknown protocol"); } /* add crl uris */ add_distribution_point(ca->crluris, msg->crluri); add_distribution_point(ca->crluris, msg->crluri2); /* strictrlpolicy */ ca->strictcrlpolicy = msg->whack_strict; /* insert ca_info record into the chained list */ lock_ca_info_list("add_ca_info"); ca->next = ca_infos; ca_infos = ca; unlock_ca_info_list("add_ca_info"); /* add cacert to list of authcerts */ cert = add_authcert(cert, X509_CA); if (!cached_cert && sc != NULL) { if (sc->last_cert != NULL) { sc->last_cert->count--; } sc->last_cert = cert; cert_share(sc->last_cert); } if (sc != NULL) time(&sc->last_load); } } /* * list all ca_info records in the chained list */ void list_ca_infos(bool utc) { ca_info_t *ca = ca_infos; if (ca != NULL) { whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, "List of X.509 CA Information Records:"); } while (ca != NULL) { /* strictpolicy per CA not supported yet * whack_log(RC_COMMENT, "%T, \"%s\", strictcrlpolicy: %s" , &ca->installed, utc, ca->name , ca->strictcrlpolicy? "yes":"no"); */ whack_log(RC_COMMENT, " "); whack_log(RC_COMMENT, " authname: \"%Y\"", ca->authName); if (ca->ldaphost) { whack_log(RC_COMMENT, " ldaphost: '%s'", ca->ldaphost); } if (ca->ldapbase) { whack_log(RC_COMMENT, " ldapbase: '%s'", ca->ldapbase); } if (ca->ocspuri) { whack_log(RC_COMMENT, " ocspuri: '%s'", ca->ocspuri); } list_distribution_points(ca->crluris); if (ca->authKeyID.ptr) { whack_log(RC_COMMENT, " authkey: %#B", &ca->authKeyID); } ca = ca->next; } }