strongswan/src/pluto/ocsp.c

1559 lines
40 KiB
C

/* Support of the Online Certificate Status Protocol (OCSP)
* Copyright (C) 2003 Christoph Gysin, Simon Zwahlen
* Copyright (C) 2009 Andreas Steffen - 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 <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 <library.h>
#include <asn1/asn1.h>
#include <asn1/asn1_parser.h>
#include <asn1/oid.h>
#include <crypto/rngs/rng.h>
#include <crypto/hashers/hasher.h>
#include "constants.h"
#include "defs.h"
#include "log.h"
#include "x509.h"
#include "crl.h"
#include "ca.h"
#include "certs.h"
#include "smartcard.h"
#include "whack.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",
"status #4",
"signature required",
"unauthorized"
};
/* response container */
typedef struct response response_t;
struct response {
chunk_t tbs;
identification_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 , /* 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 */
CRL_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 const chunk_t ASN1_nonce_oid = chunk_from_chars(
0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x02
);
static const chunk_t ASN1_response_oid = chunk_from_chars(
0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x04
);
static const chunk_t ASN1_response_content = chunk_from_chars(
0x04, 0x0D,
0x30, 0x0B,
0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
);
/* 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 cert_t *ocsp_requestor_cert = NULL;
static smartcard_t *ocsp_requestor_sc = NULL;
static private_key_t *ocsp_requestor_key = NULL;
/**
* ASN.1 definition of ocspResponse
*/
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 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define OCSP_RESPONSE_STATUS 1
#define OCSP_RESPONSE_TYPE 4
#define OCSP_RESPONSE 5
/**
* ASN.1 definition of basicResponse
*/
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 */
{ 3, "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_RAW }, /* 24 */
{ 2, "end loop", ASN1_EOC, ASN1_END }, /* 25 */
{ 1, "end opt", ASN1_EOC, ASN1_END }, /* 26 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#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
/**
* ASN.1 definition of responses
*/
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 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define RESPONSES_SINGLE_RESPONSE 1
/**
* ASN.1 definition of singleResponse
*/
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 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#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
/*
* Build an ocsp location from certificate information
* without unsharing its contents
*/
static bool build_ocsp_location(const cert_t *cert, ocsp_location_t *location)
{
certificate_t *certificate = cert->cert;
identification_t *issuer = certificate->get_issuer(certificate);
x509_t *x509 = (x509_t*)certificate;
chunk_t issuer_dn = issuer->get_encoding(issuer);
chunk_t authKeyID = x509->get_authKeyIdentifier(x509);
hasher_t *hasher;
static u_char digest[HASH_SIZE_SHA1]; /* temporary storage */
enumerator_t *enumerator = x509->create_ocsp_uri_enumerator(x509);
location->uri = NULL;
while (enumerator->enumerate(enumerator, &location->uri))
{
break;
}
enumerator->destroy(enumerator);
if (location->uri == NULL)
{
ca_info_t *ca = get_ca_info(issuer, authKeyID);
if (ca && ca->ocspuri)
{
location->uri = ca->ocspuri;
}
else
{ /* abort if no ocsp location uri is defined */
return FALSE;
}
}
/* compute authNameID from as SHA-1 hash of issuer DN */
location->authNameID = chunk_create(digest, HASH_SIZE_SHA1);
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
if (hasher == NULL)
{
return FALSE;
}
hasher->get_hash(hasher, issuer_dn, digest);
hasher->destroy(hasher);
location->next = NULL;
location->issuer = issuer;
location->authKeyID = authKeyID;
if (authKeyID.ptr == NULL)
{
cert_t *authcert = get_authcert(issuer, authKeyID, X509_CA);
if (authcert)
{
x509_t *x509 = (x509_t*)authcert->cert;
location->authKeyID = x509->get_subjectKeyIdentifier(x509);
}
}
location->nonce = chunk_empty;
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)
? same_keyid(a->authKeyID, b->authKeyID)
: a->issuer->equals(a->issuer, b->issuer))
&& streq(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)
{
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)
{
cmp = chunk_compare(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 cert_t *cert, time_t *until,
time_t *revocationDate,
crl_reason_t *revocationReason)
{
x509_t *x509 = (x509_t*)cert->cert;
chunk_t serialNumber = x509->get_serial(x509);
cert_status_t status;
ocsp_location_t location;
time_t nextUpdate = UNDEFINED_TIME;
*revocationDate = UNDEFINED_TIME;
*revocationReason = CRL_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, 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, 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)
{
char buf[BUF_LEN];
bool first = TRUE;
ocsp_certinfo_t *certinfo = location->certinfo;
while (certinfo)
{
if (!certinfo->once)
{
time_t time_left = certinfo->nextUpdate - time(NULL);
DBG(DBG_CONTROL,
if (first)
{
DBG_log("issuer: \"%Y\"", location->issuer);
if (location->authKeyID.ptr)
{
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)
{
free(certinfo->serialNumber.ptr);
free(certinfo);
}
/**
* frees all certinfos in a chained list
*/
static void free_certinfos(ocsp_certinfo_t *chain)
{
ocsp_certinfo_t *certinfo;
while (chain)
{
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)
{
DESTROY_IF(location->issuer);
free(location->authNameID.ptr);
free(location->authKeyID.ptr);
free(location->uri);
free_certinfos(location->certinfo);
free(location);
}
/*
* Free a chained list of ocsp locations
*/
void free_ocsp_locations(ocsp_location_t **chain)
{
while (*chain)
{
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)
{
free(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)
{
ocsp_certinfo_t *certinfo = location->certinfo;
if (certinfo)
{
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)
{
whack_log(RC_COMMENT, " issuer: \"%Y\"", location->issuer);
}
whack_log(RC_COMMENT, " uri: '%s'", location->uri);
if (location->authNameID.ptr)
{
whack_log(RC_COMMENT, " authname: %#B", &location->authNameID);
}
if (location->authKeyID.ptr)
{
whack_log(RC_COMMENT, " authkey: %#B", &location->authKeyID);
}
while (certinfo)
{
chunk_t serial = chunk_skip_zero(certinfo->serialNumber);
if (requests)
{
whack_log(RC_COMMENT, " serial: %#B, %d trials",
&serial, certinfo->trials);
}
else if (certinfo->once)
{
whack_log(RC_COMMENT, " serial: %#B, %s, once%s",
&serial, cert_status_names[certinfo->status],
(certinfo->nextUpdate < time(NULL))? " (expired)": "");
}
else
{
whack_log(RC_COMMENT, " serial: %#B, %s, until %T %s",
&serial, cert_status_names[certinfo->status],
&certinfo->nextUpdate, utc,
check_expiry(certinfo->nextUpdate, OCSP_WARNING_INTERVAL, strict));
}
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)
{
cert_t *cert = NULL;
/* initialize temporary static storage */
ocsp_requestor_cert = NULL;
ocsp_requestor_sc = NULL;
ocsp_requestor_key = NULL;
for (;;)
{
certificate_t *certificate;
/* looking for a certificate from the same issuer */
cert = get_x509cert(location->issuer, location->authKeyID, cert);
if (cert == NULL)
{
break;
}
certificate = cert->cert;
DBG(DBG_CONTROL,
DBG_log("candidate: '%Y'", certificate->get_subject(certificate));
)
if (cert->smartcard)
{
/* look for a matching private key on a smartcard */
smartcard_t *sc = scx_get(cert);
if (sc)
{
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 */
private_key_t *private = get_x509_private_key(cert);
if (private)
{
DBG(DBG_CONTROL,
DBG_log("matching private key found")
)
ocsp_requestor_cert = cert;
ocsp_requestor_key = private;
return TRUE;
}
}
}
return FALSE;
}
static chunk_t sc_build_sha1_signature(chunk_t tbs, smartcard_t *sc)
{
hasher_t *hasher;
u_char *pos;
chunk_t digest;
chunk_t digest_info, sigdata;
size_t siglen = 0;
if (!scx_establish_context(sc) || !scx_login(sc))
{
scx_release_context(sc);
return chunk_empty;
}
siglen = scx_get_keylength(sc);
if (siglen == 0)
{
plog("failed to get keylength from smartcard");
scx_release_context(sc);
return chunk_empty;
}
DBG(DBG_CONTROL | DBG_CRYPT,
DBG_log("signing hash with RSA key from smartcard (slot: %d, id: %s)"
, (int)sc->slot, sc->id)
)
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
if (hasher == NULL)
{
return chunk_empty;
}
hasher->allocate_hash(hasher, tbs, &digest);
hasher->destroy(hasher);
/* according to PKCS#1 v2.1 digest must be packaged into
* an ASN.1 structure for encryption
*/
digest_info = asn1_wrap(ASN1_SEQUENCE, "mm"
, asn1_algorithmIdentifier(OID_SHA1)
, asn1_wrap(ASN1_OCTET_STRING, "m", digest));
pos = asn1_build_object(&sigdata, ASN1_BIT_STRING, 1 + siglen);
*pos++ = 0x00;
scx_sign_hash(sc, digest_info.ptr, digest_info.len, pos, siglen);
free(digest_info.ptr);
if (!pkcs11_keep_state)
{
scx_release_context(sc);
}
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, cert, certs = chunk_empty;
if (ocsp_requestor_sc)
{
/* RSA signature is done on smartcard */
sigdata = sc_build_sha1_signature(tbsRequest, ocsp_requestor_sc);
}
else
{
/* RSA signature is done in software */
sigdata = x509_build_signature(tbsRequest, OID_SHA1, ocsp_requestor_key,
TRUE);
}
if (sigdata.ptr == NULL)
{
return chunk_empty;
}
/* include our certificate */
if (ocsp_requestor_cert->cert->get_encoding(ocsp_requestor_cert->cert,
CERT_ASN1_DER, &cert))
{
certs = asn1_wrap(ASN1_CONTEXT_C_0, "m",
asn1_wrap(ASN1_SEQUENCE, "m", cert));
}
/* build signature comprising algorithm, signature and cert */
return asn1_wrap(ASN1_CONTEXT_C_0, "m"
, asn1_wrap(ASN1_SEQUENCE, "mmm"
, asn1_algorithmIdentifier(OID_SHA1_WITH_RSA)
, 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, "mmmm"
, asn1_algorithmIdentifier(OID_SHA1)
, 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)
{
/* build request for every certificate in list
* and store them in a chained list
*/
request_list_t *req = malloc_thing(request_list_t);
req->request = build_request(location, certinfo);
req->next = reqs;
reqs = req;
datalen += req->request.len;
certinfo = certinfo->next;
}
pos = asn1_build_object(&requestList, ASN1_SEQUENCE, datalen);
/* copy all in chained list, free list afterwards */
while (reqs)
{
request_list_t *req = reqs;
mv_chunk(&pos, req->request);
reqs = reqs->next;
free(req);
}
return requestList;
}
/**
* Build requestorName (into TBSRequest)
*/
static chunk_t build_requestor_name(void)
{
certificate_t *certificate = ocsp_requestor_cert->cert;
identification_t *subject = certificate->get_subject(certificate);
return asn1_wrap(ASN1_CONTEXT_C_1, "m"
, asn1_simple_object(ASN1_CONTEXT_C_4
, subject->get_encoding(subject)));
}
/**
* build nonce extension (into requestExtensions)
*/
static chunk_t build_nonce_extension(ocsp_location_t *location)
{
rng_t *rng;
/* generate a random nonce */
location->nonce.ptr = malloc(NONCE_LENGTH),
location->nonce.len = NONCE_LENGTH;
rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
rng->get_bytes(rng, location->nonce.len, location->nonce.ptr);
rng->destroy(rng);
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()
: chunk_empty
, 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;
DBG(DBG_CONTROL,
DBG_log("assembling ocsp request");
DBG_log("issuer: \"%Y\"", location->issuer);
if (location->authKeyID.ptr)
{
DBG_log("authkey: %#B", &location->authKeyID);
}
)
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)
: chunk_empty;
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, pathlen_constraint;
cert_t *authcert;
lock_authcert_list("valid_ocsp_response");
authcert = get_authcert(res->responder_id_name, res->responder_id_key,
X509_OCSP_SIGNER | X509_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 (!x509_check_signature(res->tbs, res->signature, res->algorithm,
authcert->cert))
{
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 = -1; pathlen <= X509_MAX_PATH_LEN; pathlen++)
{
cert_t *cert = authcert;
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);
time_t not_before, not_after;
DBG(DBG_CONTROL,
DBG_log("subject: '%Y'", subject);
DBG_log("issuer: '%Y'", issuer);
if (authKeyID.ptr)
{
DBG_log("authkey: %#B", &authKeyID);
}
)
if (!certificate->get_validity(certificate, NULL, &not_before, &not_after))
{
plog("certificate is invalid (valid from %T to %T)",
&not_before, FALSE, &not_after, FALSE);
unlock_authcert_list("valid_ocsp_response");
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("certificate is valid")
)
authcert = get_authcert(issuer, authKeyID, X509_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 (!certificate->issued_by(certificate, authcert->cert))
{
plog("certificate signature is invalid");
unlock_authcert_list("valid_ocsp_response");
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("certificate signature is valid")
)
/* check path length constraint */
pathlen_constraint = x509->get_constraint(x509, X509_PATH_LEN);
if (pathlen_constraint != X509_NO_CONSTRAINT &&
pathlen > pathlen_constraint)
{
plog("path length of %d violates constraint of %d",
pathlen, pathlen_constraint);
return FALSE;
}
/* check if cert is self-signed */
if (x509->get_flags(x509) & X509_SELF_SIGNED)
{
DBG(DBG_CONTROL,
DBG_log("reached self-signed root ca with a path length of %d",
pathlen)
)
unlock_authcert_list("valid_ocsp_response");
return TRUE;
}
}
plog("maximum path length of %d exceeded", X509_MAX_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)
{
asn1_parser_t *parser;
chunk_t object;
u_int version;
int objectID;
int extn_oid = OID_UNKNOWN;
bool success = FALSE;
bool critical;
parser = asn1_parser_create(basicResponseObjects, blob);
parser->set_top_level(parser, level0);
while (parser->iterate(parser, &objectID, &object))
{
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);
goto end;
}
break;
case BASIC_RESPONSE_ID_BY_NAME:
res->responder_id_name = identification_create_from_encoding(
ID_DER_ASN1_DN, object);
DBG(DBG_PARSING,
DBG_log(" '%Y'", res->responder_id_name)
)
break;
case BASIC_RESPONSE_ID_BY_KEY:
res->responder_id_key = object;
break;
case BASIC_RESPONSE_PRODUCED_AT:
res->produced_at = asn1_to_time(&object, ASN1_GENERALIZEDTIME);
break;
case BASIC_RESPONSE_RESPONSES:
res->responses = object;
break;
case BASIC_RESPONSE_EXT_ID:
extn_oid = asn1_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 = asn1_parse_algorithmIdentifier(object,
parser->get_level(parser)+1, NULL);
break;
case BASIC_RESPONSE_SIGNATURE:
res->signature = object;
break;
case BASIC_RESPONSE_CERTIFICATE:
{
cert_t *cert = malloc_thing(cert_t);
x509_t *x509;
*cert = cert_empty;
cert->cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_BLOB_ASN1_DER, object,
BUILD_END);
if (cert->cert == NULL)
{
DBG(DBG_CONTROL | DBG_PARSING,
DBG_log("parsing of embedded ocsp certificate failed")
)
cert_free(cert);
break;
}
x509 = (x509_t*)cert->cert;
if ((x509->get_flags(x509) & X509_OCSP_SIGNER) &&
trust_authcert_candidate(cert, NULL))
{
add_authcert(cert, X509_OCSP_SIGNER);
}
else
{
DBG(DBG_CONTROL | DBG_PARSING,
DBG_log("embedded ocsp certificate rejected")
)
cert_free(cert);
}
}
break;
}
}
success = parser->success(parser);
end:
parser->destroy(parser);
return success;
}
/**
* 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_parser_t *parser;
chunk_t object;
int objectID;
int ocspResponseType = OID_UNKNOWN;
bool success = FALSE;
response_status rStatus = STATUS_INTERNALERROR;
parser = asn1_parser_create(ocspResponseObjects, blob);
while (parser->iterate(parser, &objectID, &object))
{
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]);
goto end;
default:
goto end;
}
break;
case OCSP_RESPONSE_TYPE:
ocspResponseType = asn1_known_oid(object);
break;
case OCSP_RESPONSE:
{
switch (ocspResponseType) {
case OID_BASIC:
success = parse_basic_ocsp_response(object,
parser->get_level(parser)+1, res);
break;
default:
DBG(DBG_CONTROL,
DBG_log("ocsp response is not of type BASIC");
DBG_dump_chunk("ocsp response OID: ", object);
)
goto end;
}
}
break;
}
}
success &= parser->success(parser);
end:
parser->destroy(parser);
return rStatus;
}
/**
* Parse a basic OCSP response
*/
static bool parse_ocsp_single_response(chunk_t blob, int level0,
single_response_t *sres)
{
asn1_parser_t *parser;
chunk_t object;
u_int extn_oid;
int objectID;
bool critical;
bool success = FALSE;
parser = asn1_parser_create(singleResponseObjects, blob);
parser->set_top_level(parser, level0);
while (parser->iterate(parser, &objectID, &object))
{
switch (objectID)
{
case SINGLE_RESPONSE_ALGORITHM:
sres->hash_algorithm = asn1_parse_algorithmIdentifier(object,
parser->get_level(parser)+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 = asn1_to_time(&object, ASN1_GENERALIZEDTIME);
break;
case SINGLE_RESPONSE_CERT_STATUS_CRL_REASON:
sres->revocationReason = (object.len == 1)
? *object.ptr : CRL_REASON_UNSPECIFIED;
break;
case SINGLE_RESPONSE_CERT_STATUS_UNKNOWN:
sres->status = CERT_UNKNOWN;
break;
case SINGLE_RESPONSE_THIS_UPDATE:
sres->thisUpdate = asn1_to_time(&object, ASN1_GENERALIZEDTIME);
break;
case SINGLE_RESPONSE_NEXT_UPDATE:
sres->nextUpdate = asn1_to_time(&object, ASN1_GENERALIZEDTIME);
break;
case SINGLE_RESPONSE_EXT_ID:
extn_oid = asn1_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;
}
}
success = parser->success(parser);
parser->destroy(parser);
return success;
}
/**
* 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 = malloc_thing(ocsp_location_t);
/* unshare location fields */
location->issuer = loc->issuer->clone(loc->issuer);
location->authNameID = chunk_clone(loc->authNameID);
location->authKeyID = chunk_clone(loc->authKeyID);
location->uri = strdup(loc->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)
{
cmp = chunk_compare(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 = malloc_thing(ocsp_certinfo_t);
cnew->serialNumber = chunk_clone(info->serialNumber);
cnew->next = certinfo;
cnew->trials = 0;
*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 (!(chunk_equals(sres->issuer_name_hash, location->authNameID)
&& chunk_equals(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)
{
cmp = chunk_compare(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);
}
/**
* Destroy a response_t object
*/
static void free_response(response_t *res)
{
DESTROY_IF(res->responder_id_name);
}
/**
* 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");
goto free;
}
/* check if there was a nonce in the request */
if (location->nonce.ptr && res.nonce.ptr == NULL)
{
plog("ocsp response contains no nonce, replay attack possible");
}
/* check if the nonce is identical */
if (res.nonce.ptr && !chunk_equals(res.nonce, location->nonce))
{
plog("invalid nonce in ocsp response");
goto free;
}
/* check if the response is signed by a trusted key */
if (!valid_ocsp_response(&res))
{
plog("invalid ocsp response");
goto free;
}
DBG(DBG_CONTROL,
DBG_log("valid ocsp response")
)
/* now parse the single responses one at a time */
{
asn1_parser_t *parser;
chunk_t object;
int objectID;
parser = asn1_parser_create(responsesObjects, res.responses);
while (parser->iterate(parser, &objectID, &object))
{
if (objectID == RESPONSES_SINGLE_RESPONSE)
{
single_response_t sres = empty_single_response;
if (!parse_ocsp_single_response(object,
parser->get_level(parser)+1, &sres))
{
goto end;
}
process_single_response(location, &sres);
}
}
end:
parser->destroy(parser);
}
free:
free_response(&res);
}