diff options
Diffstat (limited to 'src/pluto/ocsp.c')
-rw-r--r-- | src/pluto/ocsp.c | 1568 |
1 files changed, 1568 insertions, 0 deletions
diff --git a/src/pluto/ocsp.c b/src/pluto/ocsp.c new file mode 100644 index 000000000..a338be446 --- /dev/null +++ b/src/pluto/ocsp.c @@ -0,0 +1,1568 @@ +/* Support of the Online Certificate Status Protocol (OCSP) + * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <freeswan.h> +#include <ipsec_policy.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "x509.h" +#include "crl.h" +#include "ca.h" +#include "rnd.h" +#include "asn1.h" +#include "certs.h" +#include "smartcard.h" +#include "oid.h" +#include "whack.h" +#include "pkcs1.h" +#include "keys.h" +#include "fetch.h" +#include "ocsp.h" + +#define NONCE_LENGTH 16 + +static const char *const cert_status_names[] = { + "good", + "revoked", + "unknown", + "undefined" +}; + + +static const char *const response_status_names[] = { + "successful", + "malformed request", + "internal error", + "try later", + "signature required", + "unauthorized" +}; + +/* response container */ +typedef struct response response_t; + +struct response { + chunk_t tbs; + chunk_t responder_id_name; + chunk_t responder_id_key; + time_t produced_at; + chunk_t responses; + chunk_t nonce; + int algorithm; + chunk_t signature; +}; + +const response_t empty_response = { + { NULL, 0 } , /* tbs */ + { NULL, 0 } , /* responder_id_name */ + { NULL, 0 } , /* responder_id_key */ + UNDEFINED_TIME, /* produced_at */ + { NULL, 0 } , /* single_response */ + { NULL, 0 } , /* nonce */ + OID_UNKNOWN , /* signature_algorithm */ + { NULL, 0 } /* signature */ +}; + +/* single response container */ +typedef struct single_response single_response_t; + +struct single_response { + single_response_t *next; + int hash_algorithm; + chunk_t issuer_name_hash; + chunk_t issuer_key_hash; + chunk_t serialNumber; + cert_status_t status; + time_t revocationTime; + crl_reason_t revocationReason; + time_t thisUpdate; + time_t nextUpdate; +}; + +const single_response_t empty_single_response = { + NULL , /* *next */ + OID_UNKNOWN , /* hash_algorithm */ + { NULL, 0 } , /* issuer_name_hash */ + { NULL, 0 } , /* issuer_key_hash */ + { NULL, 0 } , /* serial_number */ + CERT_UNDEFINED , /* status */ + UNDEFINED_TIME , /* revocationTime */ + REASON_UNSPECIFIED, /* revocationReason */ + UNDEFINED_TIME , /* this_update */ + UNDEFINED_TIME /* next_update */ +}; + + +/* list of single requests */ +typedef struct request_list request_list_t; +struct request_list { + chunk_t request; + request_list_t *next; +}; + +/* some OCSP specific prefabricated ASN.1 constants */ + +static u_char ASN1_nonce_oid_str[] = { + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x02 +}; + +static const chunk_t ASN1_nonce_oid = strchunk(ASN1_nonce_oid_str); + +static u_char ASN1_response_oid_str[] = { + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x04 +}; + +static const chunk_t ASN1_response_oid = strchunk(ASN1_response_oid_str); + +static u_char ASN1_response_content_str[] = { + 0x04, 0x0D, + 0x30, 0x0B, + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 +}; + +static const chunk_t ASN1_response_content = strchunk(ASN1_response_content_str); + +/* default OCSP uri */ +static chunk_t ocsp_default_uri; + +/* ocsp cache: pointer to first element */ +static ocsp_location_t *ocsp_cache = NULL; + +/* static temporary storage for ocsp requestor information */ +static x509cert_t *ocsp_requestor_cert = NULL; + +static smartcard_t *ocsp_requestor_sc = NULL; + +static const struct RSA_private_key *ocsp_requestor_pri = NULL; + +/* asn.1 definitions for parsing */ + +static const asn1Object_t ocspResponseObjects[] = { + { 0, "OCSPResponse", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "responseStatus", ASN1_ENUMERATED, ASN1_BODY }, /* 1 */ + { 1, "responseBytesContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 2 */ + { 2, "responseBytes", ASN1_SEQUENCE, ASN1_NONE }, /* 3 */ + { 3, "responseType", ASN1_OID, ASN1_BODY }, /* 4 */ + { 3, "response", ASN1_OCTET_STRING, ASN1_BODY }, /* 5 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 6 */ +}; + +#define OCSP_RESPONSE_STATUS 1 +#define OCSP_RESPONSE_TYPE 4 +#define OCSP_RESPONSE 5 +#define OCSP_RESPONSE_ROOF 7 + +static const asn1Object_t basicResponseObjects[] = { + { 0, "BasicOCSPResponse", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "tbsResponseData", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ + { 2, "versionContext", ASN1_CONTEXT_C_0, ASN1_NONE | + ASN1_DEF }, /* 2 */ + { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 3 */ + { 2, "responderIdContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 4 */ + { 3, "responderIdByName", ASN1_SEQUENCE, ASN1_OBJ }, /* 5 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 6 */ + { 2, "responderIdContext", ASN1_CONTEXT_C_2, ASN1_OPT }, /* 7 */ + { 3, "responderIdByKey", ASN1_OCTET_STRING, ASN1_BODY }, /* 8 */ + { 2, "end choice", ASN1_EOC, ASN1_END }, /* 9 */ + { 2, "producedAt", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 10 */ + { 2, "responses", ASN1_SEQUENCE, ASN1_OBJ }, /* 11 */ + { 2, "responseExtensionsContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 12 */ + { 3, "responseExtensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 13 */ + { 4, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 14 */ + { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 15 */ + { 5, "critical", ASN1_BOOLEAN, ASN1_BODY | + ASN1_DEF }, /* 16 */ + { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 17 */ + { 4, "end loop", ASN1_EOC, ASN1_END }, /* 18 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 19 */ + { 1, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 20 */ + { 1, "signature", ASN1_BIT_STRING, ASN1_BODY }, /* 21 */ + { 1, "certsContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 22 */ + { 2, "certs", ASN1_SEQUENCE, ASN1_LOOP }, /* 23 */ + { 3, "certificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 24 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 25 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 26 */ +}; + +#define BASIC_RESPONSE_TBS_DATA 1 +#define BASIC_RESPONSE_VERSION 3 +#define BASIC_RESPONSE_ID_BY_NAME 5 +#define BASIC_RESPONSE_ID_BY_KEY 8 +#define BASIC_RESPONSE_PRODUCED_AT 10 +#define BASIC_RESPONSE_RESPONSES 11 +#define BASIC_RESPONSE_EXT_ID 15 +#define BASIC_RESPONSE_CRITICAL 16 +#define BASIC_RESPONSE_EXT_VALUE 17 +#define BASIC_RESPONSE_ALGORITHM 20 +#define BASIC_RESPONSE_SIGNATURE 21 +#define BASIC_RESPONSE_CERTIFICATE 24 +#define BASIC_RESPONSE_ROOF 27 + +static const asn1Object_t responsesObjects[] = { + { 0, "responses", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 1, "singleResponse", ASN1_EOC, ASN1_RAW }, /* 1 */ + { 0, "end loop", ASN1_EOC, ASN1_END } /* 2 */ +}; + +#define RESPONSES_SINGLE_RESPONSE 1 +#define RESPONSES_ROOF 3 + +static const asn1Object_t singleResponseObjects[] = { + { 0, "singleResponse", ASN1_SEQUENCE, ASN1_BODY }, /* 0 */ + { 1, "certID", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */ + { 2, "algorithm", ASN1_EOC, ASN1_RAW }, /* 2 */ + { 2, "issuerNameHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 3 */ + { 2, "issuerKeyHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 4 */ + { 2, "serialNumber", ASN1_INTEGER, ASN1_BODY }, /* 5 */ + { 1, "certStatusGood", ASN1_CONTEXT_S_0, ASN1_OPT }, /* 6 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 7 */ + { 1, "certStatusRevoked", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 8 */ + { 2, "revocationTime", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 9 */ + { 2, "revocationReason", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 10 */ + { 3, "crlReason", ASN1_ENUMERATED, ASN1_BODY }, /* 11 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 12 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 13 */ + { 1, "certStatusUnknown", ASN1_CONTEXT_S_2, ASN1_OPT }, /* 14 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 15 */ + { 1, "thisUpdate", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 16 */ + { 1, "nextUpdateContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 17 */ + { 2, "nextUpdate", ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 18 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 19 */ + { 1, "singleExtensionsContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 20 */ + { 2, "singleExtensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 21 */ + { 3, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 22 */ + { 4, "extnID", ASN1_OID, ASN1_BODY }, /* 23 */ + { 4, "critical", ASN1_BOOLEAN, ASN1_BODY | + ASN1_DEF }, /* 24 */ + { 4, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 25 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 26 */ + { 1, "end opt", ASN1_EOC, ASN1_END } /* 27 */ +}; + +#define SINGLE_RESPONSE_ALGORITHM 2 +#define SINGLE_RESPONSE_ISSUER_NAME_HASH 3 +#define SINGLE_RESPONSE_ISSUER_KEY_HASH 4 +#define SINGLE_RESPONSE_SERIAL_NUMBER 5 +#define SINGLE_RESPONSE_CERT_STATUS_GOOD 6 +#define SINGLE_RESPONSE_CERT_STATUS_REVOKED 8 +#define SINGLE_RESPONSE_CERT_STATUS_REVOCATION_TIME 9 +#define SINGLE_RESPONSE_CERT_STATUS_CRL_REASON 11 +#define SINGLE_RESPONSE_CERT_STATUS_UNKNOWN 14 +#define SINGLE_RESPONSE_THIS_UPDATE 16 +#define SINGLE_RESPONSE_NEXT_UPDATE 18 +#define SINGLE_RESPONSE_EXT_ID 23 +#define SINGLE_RESPONSE_CRITICAL 24 +#define SINGLE_RESPONSE_EXT_VALUE 25 +#define SINGLE_RESPONSE_ROOF 28 + +/* build an ocsp location from certificate information + * without unsharing its contents + */ +static bool +build_ocsp_location(const x509cert_t *cert, ocsp_location_t *location) +{ + static u_char digest[SHA1_DIGEST_SIZE]; /* temporary storage */ + + location->uri = cert->accessLocation; + + if (location->uri.ptr == NULL) + { + ca_info_t *ca = get_ca_info(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID); + if (ca != NULL && ca->ocspuri != NULL) + setchunk(location->uri, ca->ocspuri, strlen(ca->ocspuri)) + else + /* abort if no ocsp location uri is defined */ + return FALSE; + } + + setchunk(location->authNameID, digest, SHA1_DIGEST_SIZE); + compute_digest(cert->issuer, OID_SHA1, &location->authNameID); + + location->next = NULL; + location->issuer = cert->issuer; + location->authKeyID = cert->authKeyID; + location->authKeySerialNumber = cert->authKeySerialNumber; + + if (cert->authKeyID.ptr == NULL) + { + x509cert_t *authcert = get_authcert(cert->issuer + , cert->authKeySerialNumber, cert->authKeyID, AUTH_CA); + + if (authcert != NULL) + { + location->authKeyID = authcert->subjectKeyID; + location->authKeySerialNumber = authcert->serialNumber; + } + } + + location->nonce = empty_chunk; + location->certinfo = NULL; + + return TRUE; +} + +/* + * compare two ocsp locations for equality + */ +static bool +same_ocsp_location(const ocsp_location_t *a, const ocsp_location_t *b) +{ + return ((a->authKeyID.ptr != NULL) + ? same_keyid(a->authKeyID, b->authKeyID) + : (same_dn(a->issuer, b->issuer) + && same_serial(a->authKeySerialNumber, b->authKeySerialNumber))) + && same_chunk(a->uri, b->uri); +} + +/* + * find an existing ocsp location in a chained list + */ +ocsp_location_t* +get_ocsp_location(const ocsp_location_t * loc, ocsp_location_t *chain) +{ + + while (chain != NULL) + { + if (same_ocsp_location(loc, chain)) + return chain; + chain = chain->next; + } + return NULL; +} + +/* retrieves the status of a cert from the ocsp cache + * returns CERT_UNDEFINED if no status is found + */ +static cert_status_t +get_ocsp_status(const ocsp_location_t *loc, chunk_t serialNumber + ,time_t *nextUpdate, time_t *revocationTime, crl_reason_t *revocationReason) +{ + ocsp_certinfo_t *certinfo, **certinfop; + int cmp = -1; + + /* find location */ + ocsp_location_t *location = get_ocsp_location(loc, ocsp_cache); + + if (location == NULL) + return CERT_UNDEFINED; + + /* traverse list of certinfos in increasing order */ + certinfop = &location->certinfo; + certinfo = *certinfop; + + while (certinfo != NULL) + { + cmp = cmp_chunk(serialNumber, certinfo->serialNumber); + if (cmp <= 0) + break; + certinfop = &certinfo->next; + certinfo = *certinfop; + } + + if (cmp == 0) + { + *nextUpdate = certinfo->nextUpdate; + *revocationTime = certinfo->revocationTime; + *revocationReason = certinfo->revocationReason; + return certinfo->status; + } + + return CERT_UNDEFINED; +} + +/* + * verify the ocsp status of a certificate + */ +cert_status_t +verify_by_ocsp(const x509cert_t *cert, time_t *until +, time_t *revocationDate, crl_reason_t *revocationReason) +{ + cert_status_t status; + ocsp_location_t location; + time_t nextUpdate = 0; + + *revocationDate = UNDEFINED_TIME; + *revocationReason = REASON_UNSPECIFIED; + + /* is an ocsp location defined? */ + if (!build_ocsp_location(cert, &location)) + return CERT_UNDEFINED; + + lock_ocsp_cache("verify_by_ocsp"); + status = get_ocsp_status(&location, cert->serialNumber, &nextUpdate + , revocationDate, revocationReason); + unlock_ocsp_cache("verify_by_ocsp"); + + if (status == CERT_UNDEFINED || nextUpdate < time(NULL)) + { + plog("ocsp status is stale or not in cache"); + add_ocsp_fetch_request(&location, cert->serialNumber); + + /* inititate fetching of ocsp status */ + wake_fetch_thread("verify_by_ocsp"); + } + *until = nextUpdate; + return status; +} + +/* + * check if an ocsp status is about to expire + */ +void +check_ocsp(void) +{ + ocsp_location_t *location; + + lock_ocsp_cache("check_ocsp"); + location = ocsp_cache; + + while (location != NULL) + { + char buf[BUF_LEN]; + bool first = TRUE; + ocsp_certinfo_t *certinfo = location->certinfo; + + while (certinfo != NULL) + { + if (!certinfo->once) + { + time_t time_left = certinfo->nextUpdate - time(NULL); + + DBG(DBG_CONTROL, + if (first) + { + dntoa(buf, BUF_LEN, location->issuer); + DBG_log("issuer: '%s'", buf); + if (location->authKeyID.ptr != NULL) + { + datatot(location->authKeyID.ptr, location->authKeyID.len + , ':', buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + first = FALSE; + } + datatot(certinfo->serialNumber.ptr, certinfo->serialNumber.len + , ':', buf, BUF_LEN); + DBG_log("serial: %s, %ld seconds left", buf, time_left) + ) + + if (time_left < 2*crl_check_interval) + add_ocsp_fetch_request(location, certinfo->serialNumber); + } + certinfo = certinfo->next; + } + location = location->next; + } + unlock_ocsp_cache("check_ocsp"); +} + +/* + * frees the allocated memory of a certinfo struct + */ +static void +free_certinfo(ocsp_certinfo_t *certinfo) +{ + freeanychunk(certinfo->serialNumber); + pfree(certinfo); +} + +/* + * frees all certinfos in a chained list + */ +static void +free_certinfos(ocsp_certinfo_t *chain) +{ + ocsp_certinfo_t *certinfo; + + while (chain != NULL) + { + certinfo = chain; + chain = chain->next; + free_certinfo(certinfo); + } +} + +/* + * frees the memory allocated to an ocsp location including all certinfos + */ +static void +free_ocsp_location(ocsp_location_t* location) +{ + freeanychunk(location->issuer); + freeanychunk(location->authNameID); + freeanychunk(location->authKeyID); + freeanychunk(location->authKeySerialNumber); + freeanychunk(location->uri); + free_certinfos(location->certinfo); + pfree(location); +} + +/* + * free a chained list of ocsp locations + */ +void +free_ocsp_locations(ocsp_location_t **chain) +{ + while (*chain != NULL) + { + ocsp_location_t *location = *chain; + *chain = location->next; + free_ocsp_location(location); + } +} + +/* + * free the ocsp cache + */ +void +free_ocsp_cache(void) +{ + lock_ocsp_cache("free_ocsp_cache"); + free_ocsp_locations(&ocsp_cache); + unlock_ocsp_cache("free_ocsp_cache"); +} + +/* + * frees the ocsp cache and global variables + */ +void +free_ocsp(void) +{ + pfreeany(ocsp_default_uri.ptr); + free_ocsp_cache(); +} + +/* + * list a chained list of ocsp_locations + */ +void +list_ocsp_locations(ocsp_location_t *location, bool requests, bool utc +, bool strict) +{ + bool first = TRUE; + + while (location != NULL) + { + ocsp_certinfo_t *certinfo = location->certinfo; + + if (certinfo != NULL) + { + u_char buf[BUF_LEN]; + + if (first) + { + whack_log(RC_COMMENT, " "); + whack_log(RC_COMMENT, "List of OCSP %s:", requests? + "fetch requests":"responses"); + first = FALSE; + } + whack_log(RC_COMMENT, " "); + if (location->issuer.ptr != NULL) + { + dntoa(buf, BUF_LEN, location->issuer); + whack_log(RC_COMMENT, " issuer: '%s'", buf); + } + whack_log(RC_COMMENT, " uri: '%.*s'", (int)location->uri.len + , location->uri.ptr); + if (location->authNameID.ptr != NULL) + { + datatot(location->authNameID.ptr, location->authNameID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authname: %s", buf); + } + if (location->authKeyID.ptr != NULL) + { + datatot(location->authKeyID.ptr, location->authKeyID.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " authkey: %s", buf); + } + if (location->authKeySerialNumber.ptr != NULL) + { + datatot(location->authKeySerialNumber.ptr + , location->authKeySerialNumber.len, ':', buf, BUF_LEN); + whack_log(RC_COMMENT, " aserial: %s", buf); + } + while (certinfo != NULL) + { + char thisUpdate[TIMETOA_BUF]; + + strcpy(thisUpdate, timetoa(&certinfo->thisUpdate, utc)); + + if (requests) + { + whack_log(RC_COMMENT, "%s, trials: %d", thisUpdate + , certinfo->trials); + } + else if (certinfo->once) + { + whack_log(RC_COMMENT, "%s, onetime use%s", thisUpdate + , (certinfo->nextUpdate < time(NULL))? " (expired)": ""); + } + else + { + whack_log(RC_COMMENT, "%s, until %s %s", thisUpdate + , timetoa(&certinfo->nextUpdate, utc) + , check_expiry(certinfo->nextUpdate, OCSP_WARNING_INTERVAL, strict)); + } + datatot(certinfo->serialNumber.ptr, certinfo->serialNumber.len, ':' + , buf, BUF_LEN); + whack_log(RC_COMMENT, " serial: %s, %s", buf + , cert_status_names[certinfo->status]); + certinfo = certinfo->next; + } + } + location = location->next; + } +} + +/* + * list the ocsp cache + */ +void +list_ocsp_cache(bool utc, bool strict) +{ + lock_ocsp_cache("list_ocsp_cache"); + list_ocsp_locations(ocsp_cache, FALSE, utc, strict); + unlock_ocsp_cache("list_ocsp_cache"); +} + +static bool +get_ocsp_requestor_cert(ocsp_location_t *location) +{ + x509cert_t *cert = NULL; + + /* initialize temporary static storage */ + ocsp_requestor_cert = NULL; + ocsp_requestor_sc = NULL; + ocsp_requestor_pri = NULL; + + for (;;) + { + char buf[BUF_LEN]; + + /* looking for a certificate from the same issuer */ + cert = get_x509cert(location->issuer, location->authKeySerialNumber + ,location->authKeyID, cert); + if (cert == NULL) + break; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, cert->subject); + DBG_log("candidate: '%s'", buf); + ) + + if (cert->smartcard) + { + /* look for a matching private key on a smartcard */ + smartcard_t *sc = scx_get(cert); + + if (sc != NULL) + { + DBG(DBG_CONTROL, + DBG_log("matching smartcard found") + ) + if (sc->valid) + { + ocsp_requestor_cert = cert; + ocsp_requestor_sc = sc; + return TRUE; + } + plog("unable to sign ocsp request without PIN"); + } + } + else + { + /* look for a matching private key in the chained list */ + const struct RSA_private_key *pri = get_x509_private_key(cert); + + if (pri != NULL) + { + DBG(DBG_CONTROL, + DBG_log("matching private key found") + ) + ocsp_requestor_cert = cert; + ocsp_requestor_pri = pri; + return TRUE; + } + } + } + return FALSE; +} + +static chunk_t +generate_signature(chunk_t digest, smartcard_t *sc + , const RSA_private_key_t *pri) +{ + chunk_t sigdata; + u_char *pos; + size_t siglen = 0; + + if (sc != NULL) + { + /* RSA signature is done on smartcard */ + + if (!scx_establish_context(sc) || !scx_login(sc)) + { + scx_release_context(sc); + return empty_chunk; + } + + siglen = scx_get_keylength(sc); + + if (siglen == 0) + { + plog("failed to get keylength from smartcard"); + scx_release_context(sc); + return empty_chunk; + } + + DBG(DBG_CONTROL | DBG_CRYPT, + DBG_log("signing hash with RSA key from smartcard (slot: %d, id: %s)" + , (int)sc->slot, sc->id) + ) + + pos = build_asn1_object(&sigdata, ASN1_BIT_STRING, 1 + siglen); + *pos++ = 0x00; + scx_sign_hash(sc, digest.ptr, digest.len, pos, siglen); + if (!pkcs11_keep_state) + scx_release_context(sc); + } + else + { + /* RSA signature is done in software */ + siglen = pri->pub.k; + pos = build_asn1_object(&sigdata, ASN1_BIT_STRING, 1 + siglen); + *pos++ = 0x00; + sign_hash(pri, digest.ptr, digest.len, pos, siglen); + } + return sigdata; +} + +/* + * build signature into ocsp request + * gets built only if a request cert with + * a corresponding private key is found + */ +static chunk_t +build_signature(chunk_t tbsRequest) +{ + chunk_t sigdata, certs; + chunk_t digest_info; + + u_char digest_buf[MAX_DIGEST_LEN]; + chunk_t digest_raw = { digest_buf, MAX_DIGEST_LEN }; + + if (!compute_digest(tbsRequest, OID_SHA1, &digest_raw)) + return empty_chunk; + + /* according to PKCS#1 v2.1 digest must be packaged into + * an ASN.1 structure for encryption + */ + digest_info = asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_sha1_id + , asn1_simple_object(ASN1_OCTET_STRING, digest_raw)); + + /* generate the RSA signature */ + sigdata = generate_signature(digest_info + , ocsp_requestor_sc + , ocsp_requestor_pri); + freeanychunk(digest_info); + + /* has the RSA signature generation been successful? */ + if (sigdata.ptr == NULL) + return empty_chunk; + + /* include our certificate */ + certs = asn1_wrap(ASN1_CONTEXT_C_0, "m" + , asn1_simple_object(ASN1_SEQUENCE + , ocsp_requestor_cert->certificate + ) + ); + + /* build signature comprising algorithm, signature and cert */ + return asn1_wrap(ASN1_CONTEXT_C_0, "m" + , asn1_wrap(ASN1_SEQUENCE, "cmm" + , ASN1_sha1WithRSA_id + , sigdata + , certs + ) + ); +} + +/* build request (into requestList) + * no singleRequestExtensions used + */ +static chunk_t +build_request(ocsp_location_t *location, ocsp_certinfo_t *certinfo) +{ + chunk_t reqCert = asn1_wrap(ASN1_SEQUENCE, "cmmm" + , ASN1_sha1_id + , asn1_simple_object(ASN1_OCTET_STRING, location->authNameID) + , asn1_simple_object(ASN1_OCTET_STRING, location->authKeyID) + , asn1_simple_object(ASN1_INTEGER, certinfo->serialNumber)); + + return asn1_wrap(ASN1_SEQUENCE, "m", reqCert); +} + +/* + * build requestList (into TBSRequest) + */ +static chunk_t +build_request_list(ocsp_location_t *location) +{ + chunk_t requestList; + request_list_t *reqs = NULL; + ocsp_certinfo_t *certinfo = location->certinfo; + u_char *pos; + + size_t datalen = 0; + + /* build content */ + while (certinfo != NULL) + { + /* build request for every certificate in list + * and store them in a chained list + */ + request_list_t *req = alloc_thing(request_list_t, "ocsp request"); + + req->request = build_request(location, certinfo); + req->next = reqs; + reqs = req; + + datalen += req->request.len; + certinfo = certinfo->next; + } + + pos = build_asn1_object(&requestList, ASN1_SEQUENCE + , datalen); + + /* copy all in chained list, free list afterwards */ + while (reqs != NULL) + { + request_list_t *req = reqs; + + mv_chunk(&pos, req->request); + reqs = reqs->next; + pfree(req); + } + + return requestList; +} + +/* + * build requestorName (into TBSRequest) + */ +static chunk_t +build_requestor_name(void) +{ + return asn1_wrap(ASN1_CONTEXT_C_1, "m" + , asn1_simple_object(ASN1_CONTEXT_C_4 + , ocsp_requestor_cert->subject)); +} + +/* + * build nonce extension (into requestExtensions) + */ +static chunk_t +build_nonce_extension(ocsp_location_t *location) +{ + /* generate a random nonce */ + location->nonce.ptr = alloc_bytes(NONCE_LENGTH, "ocsp nonce"), + location->nonce.len = NONCE_LENGTH; + get_rnd_bytes(location->nonce.ptr, NONCE_LENGTH); + + return asn1_wrap(ASN1_SEQUENCE, "cm" + , ASN1_nonce_oid + , asn1_simple_object(ASN1_OCTET_STRING, location->nonce)); +} + +/* + * build requestExtensions (into TBSRequest) + */ +static chunk_t +build_request_ext(ocsp_location_t *location) +{ + return asn1_wrap(ASN1_CONTEXT_C_2, "m" + , asn1_wrap(ASN1_SEQUENCE, "mm" + , build_nonce_extension(location) + , asn1_wrap(ASN1_SEQUENCE, "cc" + , ASN1_response_oid + , ASN1_response_content + ) + ) + ); +} + +/* + * build TBSRequest (into OCSPRequest) + */ +static chunk_t +build_tbs_request(ocsp_location_t *location, bool has_requestor_cert) +{ + /* version is skipped since the default is ok */ + return asn1_wrap(ASN1_SEQUENCE, "mmm" + , (has_requestor_cert) + ? build_requestor_name() + : empty_chunk + , build_request_list(location) + , build_request_ext(location)); +} + +/* assembles an ocsp request to given location + * and sets nonce field in location to the sent nonce + */ +chunk_t +build_ocsp_request(ocsp_location_t *location) +{ + bool has_requestor_cert; + chunk_t tbsRequest, signature; + char buf[BUF_LEN]; + + DBG(DBG_CONTROL, + DBG_log("assembling ocsp request"); + dntoa(buf, BUF_LEN, location->issuer); + DBG_log("issuer: '%s'", buf); + if (location->authKeyID.ptr != NULL) + { + datatot(location->authKeyID.ptr, location->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + ) + lock_certs_and_keys("build_ocsp_request"); + + /* looks for requestor cert and matching private key */ + has_requestor_cert = get_ocsp_requestor_cert(location); + + /* build content */ + tbsRequest = build_tbs_request(location, has_requestor_cert); + + /* sign tbsReuqest */ + signature = (has_requestor_cert)? build_signature(tbsRequest) + : empty_chunk; + + unlock_certs_and_keys("build_ocsp_request"); + + return asn1_wrap(ASN1_SEQUENCE, "mm" + , tbsRequest + , signature); +} + +/* + * check if the OCSP response has a valid signature + */ +static bool +valid_ocsp_response(response_t *res) +{ + int pathlen; + x509cert_t *authcert; + + lock_authcert_list("valid_ocsp_response"); + + authcert = get_authcert(res->responder_id_name, empty_chunk + , res->responder_id_key, AUTH_OCSP | AUTH_CA); + + if (authcert == NULL) + { + plog("no matching ocsp signer cert found"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("ocsp signer cert found") + ) + + if (!check_signature(res->tbs, res->signature, res->algorithm + , res->algorithm, authcert)) + { + plog("signature of ocsp response is invalid"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("signature of ocsp response is valid") + ) + + + for (pathlen = 0; pathlen < MAX_CA_PATH_LEN; pathlen++) + { + u_char buf[BUF_LEN]; + err_t ugh = NULL; + time_t until; + + x509cert_t *cert = authcert; + + DBG(DBG_CONTROL, + dntoa(buf, BUF_LEN, cert->subject); + DBG_log("subject: '%s'",buf); + dntoa(buf, BUF_LEN, cert->issuer); + DBG_log("issuer: '%s'",buf); + if (cert->authKeyID.ptr != NULL) + { + datatot(cert->authKeyID.ptr, cert->authKeyID.len, ':' + , buf, BUF_LEN); + DBG_log("authkey: %s", buf); + } + ) + + ugh = check_validity(authcert, &until); + + if (ugh != NULL) + { + plog("%s", ugh); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + + DBG(DBG_CONTROL, + DBG_log("certificate is valid") + ) + + authcert = get_authcert(cert->issuer, cert->authKeySerialNumber + , cert->authKeyID, AUTH_CA); + + if (authcert == NULL) + { + plog("issuer cacert not found"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("issuer cacert found") + ) + + if (!check_signature(cert->tbsCertificate, cert->signature + , cert->algorithm, cert->algorithm, authcert)) + { + plog("certificate signature is invalid"); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; + } + DBG(DBG_CONTROL, + DBG_log("certificate signature is valid") + ) + + /* check if cert is self-signed */ + if (same_dn(cert->issuer, cert->subject)) + { + DBG(DBG_CONTROL, + DBG_log("reached self-signed root ca") + ) + unlock_authcert_list("valid_ocsp_response"); + return TRUE; + } + } + plog("maximum ca path length of %d levels exceeded", MAX_CA_PATH_LEN); + unlock_authcert_list("valid_ocsp_response"); + return FALSE; +} + +/* + * parse a basic OCSP response + */ +static bool +parse_basic_ocsp_response(chunk_t blob, int level0, response_t *res) +{ + u_int level, version; + u_int extn_oid = OID_UNKNOWN; + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + bool critical; + chunk_t object; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < BASIC_RESPONSE_ROOF) + { + if (!extract_object(basicResponseObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case BASIC_RESPONSE_TBS_DATA: + res->tbs = object; + break; + case BASIC_RESPONSE_VERSION: + version = (object.len)? (1 + (u_int)*object.ptr) : 1; + if (version != OCSP_BASIC_RESPONSE_VERSION) + { + plog("wrong ocsp basic response version (version= %i)", version); + return FALSE; + } + break; + case BASIC_RESPONSE_ID_BY_NAME: + res->responder_id_name = object; + DBG(DBG_PARSING, + dntoa(buf, BUF_LEN, object); + DBG_log(" '%s'",buf) + ) + break; + case BASIC_RESPONSE_ID_BY_KEY: + res->responder_id_key = object; + break; + case BASIC_RESPONSE_PRODUCED_AT: + res->produced_at = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case BASIC_RESPONSE_RESPONSES: + res->responses = object; + break; + case BASIC_RESPONSE_EXT_ID: + extn_oid = known_oid(object); + break; + case BASIC_RESPONSE_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + break; + case BASIC_RESPONSE_EXT_VALUE: + if (extn_oid == OID_NONCE) + res->nonce = object; + break; + case BASIC_RESPONSE_ALGORITHM: + res->algorithm = parse_algorithmIdentifier(object, level+1, NULL); + break; + case BASIC_RESPONSE_SIGNATURE: + res->signature = object; + break; + case BASIC_RESPONSE_CERTIFICATE: + { + chunk_t blob; + x509cert_t *cert = alloc_thing(x509cert_t, "ocspcert"); + + clonetochunk(blob, object.ptr, object.len, "ocspcert blob"); + *cert = empty_x509cert; + + if (parse_x509cert(blob, level+1, cert) + && cert->isOcspSigner + && trust_authcert_candidate(cert, NULL)) + { + add_authcert(cert, AUTH_OCSP); + } + else + { + DBG(DBG_CONTROL | DBG_PARSING, + DBG_log("embedded ocsp certificate rejected") + ) + free_x509cert(cert); + } + } + break; + } + objectID++; + } + return TRUE; +} + + +/* + * parse an ocsp response and return the result as a response_t struct + */ +static response_status +parse_ocsp_response(chunk_t blob, response_t * res) +{ + asn1_ctx_t ctx; + chunk_t object; + u_int level; + int objectID = 0; + + response_status rStatus = STATUS_INTERNALERROR; + u_int ocspResponseType = OID_UNKNOWN; + + asn1_init(&ctx, blob, 0, FALSE, DBG_RAW); + + while (objectID < OCSP_RESPONSE_ROOF) + { + if (!extract_object(ocspResponseObjects, &objectID, &object, &level, &ctx)) + return STATUS_INTERNALERROR; + + switch (objectID) { + case OCSP_RESPONSE_STATUS: + rStatus = (response_status) *object.ptr; + + switch (rStatus) + { + case STATUS_SUCCESSFUL: + break; + case STATUS_MALFORMEDREQUEST: + case STATUS_INTERNALERROR: + case STATUS_TRYLATER: + case STATUS_SIGREQUIRED: + case STATUS_UNAUTHORIZED: + plog("ocsp response: server said '%s'" + , response_status_names[rStatus]); + return rStatus; + default: + return STATUS_INTERNALERROR; + } + break; + case OCSP_RESPONSE_TYPE: + ocspResponseType = known_oid(object); + break; + case OCSP_RESPONSE: + { + switch (ocspResponseType) { + case OID_BASIC: + if (!parse_basic_ocsp_response(object, level+1, res)) + return STATUS_INTERNALERROR; + break; + default: + DBG(DBG_CONTROL, + DBG_log("ocsp response is not of type BASIC"); + DBG_dump_chunk("ocsp response OID: ", object); + ) + return STATUS_INTERNALERROR; + } + } + break; + } + objectID++; + } + return rStatus; +} + +/* + * parse a basic OCSP response + */ +static bool +parse_ocsp_single_response(chunk_t blob, int level0, single_response_t *sres) +{ + u_int level, extn_oid; + asn1_ctx_t ctx; + bool critical; + chunk_t object; + int objectID = 0; + + asn1_init(&ctx, blob, level0, FALSE, DBG_RAW); + + while (objectID < SINGLE_RESPONSE_ROOF) + { + if (!extract_object(singleResponseObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case SINGLE_RESPONSE_ALGORITHM: + sres->hash_algorithm = parse_algorithmIdentifier(object, level+1, NULL); + break; + case SINGLE_RESPONSE_ISSUER_NAME_HASH: + sres->issuer_name_hash = object; + break; + case SINGLE_RESPONSE_ISSUER_KEY_HASH: + sres->issuer_key_hash = object; + break; + case SINGLE_RESPONSE_SERIAL_NUMBER: + sres->serialNumber = object; + break; + case SINGLE_RESPONSE_CERT_STATUS_GOOD: + sres->status = CERT_GOOD; + break; + case SINGLE_RESPONSE_CERT_STATUS_REVOKED: + sres->status = CERT_REVOKED; + break; + case SINGLE_RESPONSE_CERT_STATUS_REVOCATION_TIME: + sres->revocationTime = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case SINGLE_RESPONSE_CERT_STATUS_CRL_REASON: + sres->revocationReason = (object.len == 1) + ? *object.ptr : REASON_UNSPECIFIED; + break; + case SINGLE_RESPONSE_CERT_STATUS_UNKNOWN: + sres->status = CERT_UNKNOWN; + break; + case SINGLE_RESPONSE_THIS_UPDATE: + sres->thisUpdate = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case SINGLE_RESPONSE_NEXT_UPDATE: + sres->nextUpdate = asn1totime(&object, ASN1_GENERALIZEDTIME); + break; + case SINGLE_RESPONSE_EXT_ID: + extn_oid = known_oid(object); + break; + case SINGLE_RESPONSE_CRITICAL: + critical = object.len && *object.ptr; + DBG(DBG_PARSING, + DBG_log(" %s",(critical)?"TRUE":"FALSE"); + ) + case SINGLE_RESPONSE_EXT_VALUE: + break; + } + objectID++; + } + return TRUE; +} + +/* + * add an ocsp location to a chained list + */ +ocsp_location_t* +add_ocsp_location(const ocsp_location_t *loc, ocsp_location_t **chain) +{ + ocsp_location_t *location = alloc_thing(ocsp_location_t, "ocsp location"); + + /* unshare location fields */ + clonetochunk(location->issuer + , loc->issuer.ptr, loc->issuer.len + , "ocsp issuer"); + + clonetochunk(location->authNameID + , loc->authNameID.ptr, loc->authNameID.len + , "ocsp authNameID"); + + if (loc->authKeyID.ptr == NULL) + location->authKeyID = empty_chunk; + else + clonetochunk(location->authKeyID + , loc->authKeyID.ptr, loc->authKeyID.len + , "ocsp authKeyID"); + + if (loc->authKeySerialNumber.ptr == NULL) + location->authKeySerialNumber = empty_chunk; + else + clonetochunk(location->authKeySerialNumber + , loc->authKeySerialNumber.ptr, loc->authKeySerialNumber.len + , "ocsp authKeySerialNumber"); + + clonetochunk(location->uri + , loc->uri.ptr, loc->uri.len + , "ocsp uri"); + + location->certinfo = NULL; + + /* insert new ocsp location in front of chain */ + location->next = *chain; + *chain = location; + + DBG(DBG_CONTROL, + DBG_log("new ocsp location added") + ) + + return location; +} + +/* + * add a certinfo struct to a chained list + */ +void +add_certinfo(ocsp_location_t *loc, ocsp_certinfo_t *info, ocsp_location_t **chain + , bool request) +{ + ocsp_location_t *location; + ocsp_certinfo_t *certinfo, **certinfop; + char buf[BUF_LEN]; + time_t now; + int cmp = -1; + + location = get_ocsp_location(loc, *chain); + if (location == NULL) + location = add_ocsp_location(loc, chain); + + /* traverse list of certinfos in increasing order */ + certinfop = &location->certinfo; + certinfo = *certinfop; + + while (certinfo != NULL) + { + cmp = cmp_chunk(info->serialNumber, certinfo->serialNumber); + if (cmp <= 0) + break; + certinfop = &certinfo->next; + certinfo = *certinfop; + } + + if (cmp != 0) + { + /* add a new certinfo entry */ + ocsp_certinfo_t *cnew = alloc_thing(ocsp_certinfo_t, "ocsp certinfo"); + clonetochunk(cnew->serialNumber, info->serialNumber.ptr + , info->serialNumber.len, "serialNumber"); + cnew->next = certinfo; + *certinfop = cnew; + certinfo = cnew; + } + + DBG(DBG_CONTROL, + datatot(info->serialNumber.ptr, info->serialNumber.len, ':' + , buf, BUF_LEN); + DBG_log("ocsp %s for serial %s %s" + , request?"fetch request":"certinfo" + , buf + , (cmp == 0)? (request?"already exists":"updated"):"added") + ) + + time(&now); + + if (request) + { + certinfo->status = CERT_UNDEFINED; + + if (cmp != 0) + certinfo->thisUpdate = now; + + certinfo->nextUpdate = UNDEFINED_TIME; + } + else + { + certinfo->status = info->status; + certinfo->revocationTime = info->revocationTime; + certinfo->revocationReason = info->revocationReason; + + certinfo->thisUpdate = (info->thisUpdate != UNDEFINED_TIME)? + info->thisUpdate : now; + + certinfo->once = (info->nextUpdate == UNDEFINED_TIME); + + certinfo->nextUpdate = (certinfo->once)? + (now + OCSP_DEFAULT_VALID_TIME) : info->nextUpdate; + } +} + +/* + * process received ocsp single response and add it to ocsp cache + */ +static void +process_single_response(ocsp_location_t *location, single_response_t *sres) +{ + ocsp_certinfo_t *certinfo, **certinfop; + int cmp = -1; + + if (sres->hash_algorithm != OID_SHA1) + { + plog("only SHA-1 hash supported in OCSP single response"); + return; + } + if (!(same_chunk(sres->issuer_name_hash, location->authNameID) + && same_chunk(sres->issuer_key_hash, location->authKeyID))) + { + plog("ocsp single response has wrong issuer"); + return; + } + + /* traverse list of certinfos in increasing order */ + certinfop = &location->certinfo; + certinfo = *certinfop; + + while (certinfo != NULL) + { + cmp = cmp_chunk(sres->serialNumber, certinfo->serialNumber); + if (cmp <= 0) + break; + certinfop = &certinfo->next; + certinfo = *certinfop; + } + + if (cmp != 0) + { + plog("received unrequested cert status from ocsp server"); + return; + } + + /* unlink cert from ocsp fetch request list */ + *certinfop = certinfo->next; + + /* update certinfo using the single response information */ + certinfo->thisUpdate = sres->thisUpdate; + certinfo->nextUpdate = sres->nextUpdate; + certinfo->status = sres->status; + certinfo->revocationTime = sres->revocationTime; + certinfo->revocationReason = sres->revocationReason; + + /* add or update certinfo in ocsp cache */ + lock_ocsp_cache("process_single_response"); + add_certinfo(location, certinfo, &ocsp_cache, FALSE); + unlock_ocsp_cache("process_single_response"); + + /* free certinfo unlinked from ocsp fetch request list */ + free_certinfo(certinfo); + +} + +/* + * parse and verify ocsp response and update the ocsp cache + */ +void +parse_ocsp(ocsp_location_t *location, chunk_t blob) +{ + response_t res = empty_response; + + /* parse the ocsp response without looking at the single responses yet */ + response_status status = parse_ocsp_response(blob, &res); + + if (status != STATUS_SUCCESSFUL) + { + plog("error in ocsp response"); + return; + } + /* check if there was a nonce in the request */ + if (location->nonce.ptr != NULL && res.nonce.ptr == NULL) + { + plog("ocsp response contains no nonce, replay attack possible"); + } + /* check if the nonce is identical */ + if (res.nonce.ptr != NULL && !same_chunk(res.nonce, location->nonce)) + { + plog("invalid nonce in ocsp response"); + return; + } + /* check if the response is signed by a trusted key */ + if (!valid_ocsp_response(&res)) + { + plog("invalid ocsp response"); + return; + } + DBG(DBG_CONTROL, + DBG_log("valid ocsp response") + ) + + /* now parse the single responses one at a time */ + { + u_int level; + asn1_ctx_t ctx; + chunk_t object; + int objectID = 0; + + asn1_init(&ctx, res.responses, 0, FALSE, DBG_RAW); + + while (objectID < RESPONSES_ROOF) + { + if (!extract_object(responsesObjects, &objectID, &object, &level, &ctx)) + return; + + if (objectID == RESPONSES_SINGLE_RESPONSE) + { + single_response_t sres = empty_single_response; + + if (parse_ocsp_single_response(object, level+1, &sres)) + { + process_single_response(location, &sres); + } + } + objectID++; + } + } +} |