2024-02-21 12:22:33 +01:00

647 lines
18 KiB
C

/*
* Copyright (C) 2023 Andreas Steffen, strongSec GmbH
*
* Copyright (C) secunet Security Networks 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 <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 <errno.h>
#include <time.h>
#include "pki.h"
#include "ocsp/index_responder.h"
#include <collections/array.h>
#include <credentials/sets/mem_cred.h>
#include <credentials/certificates/ocsp_request.h>
#include <credentials/certificates/ocsp_response.h>
#include <credentials/certificates/ocsp_single_response.h>
/*
* Verifies the optional OCSP request signature generated by an OCSP requestor
*/
static bool verify_signature(certificate_t *ocsp_req, mem_cred_t *creds)
{
identification_t *signer;
certificate_t *signer_cert, *cert_found;
ocsp_request_t *ocsp_request;
enumerator_t *certs;
bool trusted = TRUE;
ocsp_request = (ocsp_request_t*)ocsp_req;
signer_cert = ocsp_request->get_signer_cert(ocsp_request);
if (signer_cert)
{
signer = signer_cert->get_subject(signer_cert);
/* establish trust relative to root CA */
creds->add_cert(creds, FALSE, signer_cert->get_ref(signer_cert));
certs = lib->credmgr->create_trusted_enumerator(lib->credmgr,
KEY_ANY, signer, FALSE);
trusted = certs->enumerate(certs, &cert_found, NULL) &&
(cert_found == signer_cert);
certs->destroy(certs);
trusted = trusted && ocsp_req->issued_by(ocsp_req, signer_cert, NULL);
}
DBG1(DBG_APP, "requestor is %strusted", trusted ? "" : "not ");
return trusted;
}
/*
* Find the CA certificate of the certificate issuer based on the issuerKey and
* issuerName hashes contained in the OCSP request
*/
static bool find_issuer_cacert(hash_algorithm_t hashAlgorithm,
chunk_t issuerKeyHash, chunk_t issuerNameHash,
certificate_t **issuer_cacert)
{
bool issuerKeyHash_ok = FALSE, issuerNameHash_ok = FALSE;
certificate_t *candidate;
identification_t *issuer;
public_key_t *public;
x509_t *x509_ca;
chunk_t caKeyHash = chunk_empty, caNameHash = chunk_empty;
hasher_t *hasher;
enumerator_t *certs;
*issuer_cacert = NULL;
if (hashAlgorithm != HASH_SHA1)
{
return FALSE;
}
certs = lib->credmgr->create_cert_enumerator(lib->credmgr, CERT_X509,
KEY_ANY, NULL, TRUE);
while (certs->enumerate(certs, &candidate))
{
x509_ca = (x509_t*)candidate;
if (!(x509_ca->get_flags(x509_ca) & X509_CA))
{
continue;
}
/* retrieve the caKeyHash of the candidate issuer */
public = candidate->get_public_key(candidate);
if (!public)
{
DBG1(DBG_APP, "could not retrieve public key of cacert candidate");
break;
}
if (!public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &caKeyHash))
{
DBG1(DBG_APP, "could not retrieve SHA1 hash of public key");
public->destroy(public);
break;
}
issuerKeyHash_ok = chunk_equals_const(issuerKeyHash, caKeyHash);
public->destroy(public);
if (!issuerKeyHash_ok)
{
continue;
}
/* compute the caNameHash of the candidate issuer */
issuer = candidate->get_subject(candidate);
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
if (!hasher)
{
DBG1(DBG_APP, "failed to create SHA1 hasher");
break;
}
if (!hasher->allocate_hash(hasher, issuer->get_encoding(issuer),
&caNameHash))
{
hasher->destroy(hasher);
DBG1(DBG_APP, "failed to compute SHA1 caNameHash");
break;
}
hasher->destroy(hasher);
issuerNameHash_ok = chunk_equals_const(issuerNameHash, caNameHash);
chunk_free(&caNameHash);
if (!issuerNameHash_ok)
{
continue;
}
/* we found the issuer */
DBG1(DBG_APP, "issuer: \"%Y\"", issuer);
*issuer_cacert = candidate;
break;
}
certs->destroy(certs);
DBG1(DBG_APP, " issuerKeyHash: %#B (%s)", &issuerKeyHash,
issuerKeyHash_ok ? "ok" : "no match");
DBG1(DBG_APP, " issuerNameHash: %#B (%s)", &issuerNameHash,
issuerNameHash_ok ? "ok" : "no match");
return (*issuer_cacert != NULL);
}
/*
* Find an OCSP signer certificate. Either the certificate of the CA itself that
* issued the end-entity certificate, the certificate of an OCSP signer
* delegated by the CA via the standard OCSPSigning Extended Key Usage (EKU)
* flag or a self-signed OCSP signer certificate when multiple issuer OCSP
* requests have to be supported.
*/
static void find_ocsp_signer(certificate_t *first_issuer, bool *self_signed,
certificate_t **cert, private_key_t **key)
{
certificate_t *candidate;
identification_t *keyid;
private_key_t *key_candidate;
chunk_t subKeyId, authKeyId, subKeyId_ca;
x509_t *x509, *x509_ca;
x509_flag_t flags;
enumerator_t *certs;
/* retrieve the subjectKeyIdentifier of the first issuer certificate */
x509_ca = (x509_t*)first_issuer;
subKeyId_ca = x509_ca->get_subjectKeyIdentifier(x509_ca);
/* iterate over all certificates */
certs = lib->credmgr->create_cert_enumerator(lib->credmgr, CERT_X509,
KEY_ANY, NULL, TRUE);
while (certs->enumerate(certs, &candidate))
{
/* get the flags and key identifiers of the candidate certificate */
x509 = (x509_t*)candidate;
flags = x509->get_flags(x509);
subKeyId = x509->get_subjectKeyIdentifier(x509);
authKeyId = x509->get_authKeyIdentifier(x509);
/* get a private key matching the candidate certificate */
keyid = identification_create_from_encoding(ID_KEY_ID, subKeyId);
key_candidate = lib->credmgr->get_private(lib->credmgr,
KEY_ANY, keyid, NULL);
keyid->destroy(keyid);
if (key_candidate)
{
if (((flags & X509_OCSP_SIGNER && chunk_equals(authKeyId, subKeyId_ca)) ||
(flags & X509_CA && chunk_equals(subKeyId, subKeyId_ca))) && !*key)
{
*cert = candidate;
*key = key_candidate;
continue;
}
else if (flags & X509_SELF_SIGNED && !(flags & X509_CA))
{
DESTROY_IF(*key);
*cert = candidate;
*key = key_candidate;
*self_signed = TRUE;
break;
}
key_candidate->destroy(key_candidate);
}
}
certs->destroy(certs);
}
/**
* Show|Respond to OCSP requests
*/
static int ocsp()
{
char *arg, *file = NULL, *error = NULL;
identification_t *requestor;
cred_encoding_type_t form = CERT_ASN1_DER;
private_key_t *key = NULL;
certificate_t *cert = NULL, *ocsp_req = NULL, *ocsp_resp = NULL;
certificate_t *cacert = NULL, *first_issuer = NULL;
ocsp_request_t *ocsp_request;
ocsp_status_t ocsp_status = OCSP_SUCCESSFUL;
ocsp_responder_t *index_responder = NULL;
linked_list_t *responses = NULL;
array_t *index_responders = NULL;
chunk_t encoding = chunk_empty, nonce = chunk_empty, handle = chunk_empty;
chunk_t issuerNameHash, issuerKeyHash, serialNumber;
hash_algorithm_t hashAlgorithm = HASH_SHA1, digest = HASH_UNKNOWN;
signature_params_t *scheme = NULL;
time_t lifetime = 0;
mem_cred_t *creds;
bool multiple_issuers = FALSE, self_signed = FALSE;
enumerator_t *enumerator;
int res = 1;
enum {
OP_SHOW,
OP_RESPOND,
} op = OP_SHOW;
bool pss = lib->settings->get_bool(lib->settings, "%s.rsa_pss", FALSE,
lib->ns);
creds = mem_cred_create();
while (TRUE)
{
switch (command_getopt(&arg))
{
case 'h': /* --help */
goto usage;
case 'i': /* --in */
file = arg;
continue;
case 'r': /* --respond */
op = OP_RESPOND;
continue;
case 'k': /* --key */
key = lib->creds->create(lib->creds,
CRED_PRIVATE_KEY, KEY_ANY,
BUILD_FROM_FILE, arg, BUILD_END);
if (!key)
{
error = "parsing private key failed";
goto usage;
}
creds->add_key(creds, key);
continue;
case 'K': /* --keyid */
handle = chunk_from_hex(chunk_create(arg, strlen(arg)), NULL);
key = lib->creds->create(lib->creds,
CRED_PRIVATE_KEY, KEY_ANY,
BUILD_PKCS11_KEYID, handle, BUILD_END);
chunk_free(&handle);
if (!key)
{
DBG1(DBG_APP, "attaching to private key handle %s failed", arg);
goto usage;
}
creds->add_key(creds, key);
continue;
case 'c': /* --cert */
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, arg, BUILD_END);
if (!cert)
{
error = "parsing certificate failed";
goto usage;
}
creds->add_cert(creds, TRUE, cert);
continue;
case 'X': /* --certid */
handle = chunk_from_hex(chunk_create(arg, strlen(arg)), NULL);
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_PKCS11_KEYID, handle, BUILD_END);
chunk_free(&handle);
if (!cert)
{
DBG1(DBG_APP, "attaching to certificate handle %s failed", arg);
goto usage;
}
creds->add_cert(creds, TRUE, cert);
continue;
case 'C': /* --cacert */
DESTROY_IF(cacert);
cacert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, arg, BUILD_END);
if (!cacert)
{
error = "parsing CA certificate failed";
goto usage;
}
cacert = creds->add_cert_ref(creds, TRUE, cacert);
continue;
case 'l': /* --lifetime */
lifetime = atoi(arg) * 60;
if (!lifetime)
{
error = "invalid --lifetime value";
goto usage;
}
continue;
case 'g': /* --digest */
if (!enum_from_name(hash_algorithm_short_names, arg, &digest))
{
error = "invalid --digest type";
goto usage;
}
continue;
case 'R': /* --rsa-padding */
if (!parse_rsa_padding(arg, &pss))
{
error = "invalid RSA padding";
goto usage;
}
continue;
case 'x': /* --help */
if (!cacert)
{
error = "--index must follow --cacert of corresponding CA";
goto usage;
}
index_responder = index_responder_create(cacert, arg);
if (!index_responder)
{
error = "invalid ---index value";
goto usage;
}
array_insert_create(&index_responders, ARRAY_TAIL,
index_responder);
continue;
case EOF:
break;
default:
error = "invalid --ocsp option";
goto usage;
}
break;
}
lib->credmgr->add_local_set(lib->credmgr, &creds->set, FALSE);
responses = linked_list_create();
if (op == OP_RESPOND && !cacert)
{
error = "respond mode requires a CA certificate";
goto end;
}
if (op == OP_RESPOND && !key)
{
error = "respond mode requires a private signer key";
goto end;
}
/* re-initialize signing certificate and key pointers */
cert = NULL;
key = NULL;
if (file)
{
ocsp_req = lib->creds->create(lib->creds, CRED_CERTIFICATE,
CERT_X509_OCSP_REQUEST,
BUILD_FROM_FILE, file, BUILD_END);
}
else
{
chunk_t chunk;
set_file_mode(stdin, CERT_ASN1_DER);
if (!chunk_from_fd(0, &chunk))
{
fprintf(stderr, "%s: ", strerror(errno));
error = "reading certificate request failed";
goto end;
}
ocsp_req = lib->creds->create(lib->creds, CRED_CERTIFICATE,
CERT_X509_OCSP_REQUEST,
BUILD_BLOB_ASN1_DER, chunk, BUILD_END);
free(chunk.ptr);
}
if (!ocsp_req)
{
if (op == OP_SHOW)
{
error = "malformed OCSP request";
goto end;
}
else
{
DBG1(DBG_APP, "malformed OCSP request");
ocsp_status = OCSP_MALFORMEDREQUEST;
goto gen;
}
}
ocsp_request = (ocsp_request_t*)ocsp_req;
/* does the requestor identify itself? */
requestor = ocsp_req->get_subject(ocsp_req);
if (requestor)
{
DBG1(DBG_APP, "requestor: \"%Y\"", requestor);
/* verify an optional ocsp request signature */
if (!verify_signature(ocsp_req, creds))
{
ocsp_status = OCSP_UNAUTHORIZED;
goto gen;
}
}
/* extract nonce from OCSP request */
nonce = ocsp_request->get_nonce(ocsp_request);
if (nonce.len > 0)
{
DBG1(DBG_APP, "nonce: %#B", &nonce);
}
/* enumerate over the ocsp requests and try to identify the issuers */
enumerator = ocsp_request->create_request_enumerator(ocsp_request);
while (enumerator->enumerate(enumerator, &hashAlgorithm, &issuerNameHash,
&issuerKeyHash, &serialNumber))
{
certificate_t *issuer_cacert = NULL;
cert_validation_t status = VALIDATION_FAILED;
ocsp_single_response_t *response = NULL;
crl_reason_t revocationReason;
time_t revocationTime;
/* search for the matching issuer cacert */
if (find_issuer_cacert(hashAlgorithm, issuerKeyHash, issuerNameHash,
&issuer_cacert))
{
if (!first_issuer)
{
first_issuer = issuer_cacert;
/* search for a signing certificate plus matching private key */
if (op == OP_RESPOND)
{
find_ocsp_signer(first_issuer, &self_signed, &cert, &key);
}
}
else if (first_issuer != issuer_cacert)
{
multiple_issuers = TRUE;
}
}
DBG1(DBG_APP, " serialNumber: %#B", &serialNumber);
if (op == OP_SHOW)
{
continue;
}
/**
* fill in the OCSP single response
*/
response = ocsp_single_response_create();
response->hashAlgorithm = hashAlgorithm;
response->issuerNameHash = chunk_clone(issuerNameHash);
response->issuerKeyHash = chunk_clone(issuerKeyHash);
response->serialNumber = chunk_clone(serialNumber);
response->thisUpdate = time(NULL);
DBG1(DBG_APP, " thisUpdate: %#T", &response->thisUpdate, TRUE);
if (lifetime)
{
response->nextUpdate = response->thisUpdate + lifetime;
DBG1(DBG_APP, " nextUpdate: %#T", &response->nextUpdate, TRUE);
}
if (issuer_cacert && (issuer_cacert == first_issuer || self_signed))
{
status = lib->ocsp->get_status(lib->ocsp,
issuer_cacert, serialNumber,
&revocationTime, &revocationReason);
}
DBG1(DBG_APP, " certValidation: %N", cert_validation_names, status);
response->status = status;
if (status == VALIDATION_REVOKED || status == VALIDATION_ON_HOLD)
{
DBG1(DBG_APP, " revocationTime: %T", &revocationTime, TRUE);
DBG1(DBG_APP, " revocationReason: %N", crl_reason_names,
revocationReason);
response->revocationTime = revocationTime;
response->revocationReason = revocationReason;
}
responses->insert_last(responses, response);
}
enumerator->destroy(enumerator);
if (multiple_issuers)
{
DBG1(DBG_APP, "there are multiple known issuers");
}
gen:
if (op == OP_RESPOND)
{
if (cert)
{
DBG1(DBG_APP, "%s \"%Y\"",
self_signed ? "self-signed signer:" : "trusted signer: ",
cert->get_subject(cert));
scheme = get_signature_scheme(key, digest, pss);
if (scheme)
{
if (digest == HASH_UNKNOWN)
{
digest = hasher_from_signature_scheme(scheme->scheme,
scheme->params);
}
}
else
{
DBG1(DBG_APP, "no signature scheme found");
ocsp_status = OCSP_INTERNALERROR;
}
}
else
{
DBG1(DBG_APP, "no signer certificate found");
ocsp_status = OCSP_INTERNALERROR;
}
DBG1(DBG_APP, "ocspResponseStatus: %N", ocsp_status_names, ocsp_status);
enumerator = responses->create_enumerator(responses);
ocsp_resp = lib->creds->create(lib->creds, CRED_CERTIFICATE,
CERT_X509_OCSP_RESPONSE,
BUILD_OCSP_STATUS, ocsp_status,
BUILD_OCSP_RESPONSES, enumerator,
BUILD_SIGNING_KEY, key,
BUILD_SIGNING_CERT, cert,
BUILD_SIGNATURE_SCHEME, scheme,
BUILD_NONCE, nonce,
BUILD_END);
enumerator->destroy(enumerator);
if (!ocsp_resp)
{
error = "generating OCSP response failed";
goto end;
}
if (!ocsp_resp->get_encoding(ocsp_resp, form, &encoding))
{
error = "encoding OCSP response failed";
goto end;
}
set_file_mode(stdout, form);
if (fwrite(encoding.ptr, encoding.len, 1, stdout) != 1)
{
error = "writing OCSP response failed";
goto end;
}
}
res = 0;
end:
array_destroy_offset(index_responders, offsetof(ocsp_responder_t, destroy));
DESTROY_IF(cacert);
DESTROY_IF(key);
lib->credmgr->remove_local_set(lib->credmgr, &creds->set);
creds->destroy(creds);
responses->destroy_offset(responses,
offsetof(ocsp_single_response_t, destroy));
DESTROY_IF(ocsp_req);
DESTROY_IF(ocsp_resp);
signature_params_destroy(scheme);
free(encoding.ptr);
if (error)
{
fprintf(stderr, "%s\n", error);
}
return res;
usage:
array_destroy_offset(index_responders, offsetof(ocsp_responder_t, destroy));
DESTROY_IF(cacert);
creds->destroy(creds);
return command_usage(error);
}
/**
* Register the command.
*/
static void __attribute__ ((constructor))reg()
{
command_register((command_t) {
ocsp, 'o', "ocsp", "OCSP responder",
{"[--in file] [--respond] [--cert file|--certid hex]+ [--key file|--keyid hex]+ ",
"[--cacert file [--index file]]+",
"[--digest md5|sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]",
"[--rsa-padding pkcs1|pss] [--lifetime minutes]"},
{
{"help", 'h', 0, "show usage information"},
{"respond", 'r', 0, "respond to OCSP request with OCSP response"},
{"in", 'i', 1, "input file, default: stdin"},
{"key", 'k', 1, "path to OCSP signing private key (can be used multiple times)"},
{"keyid", 'K', 1, "smartcard or TPM private key object handle (can be used multiple times)"},
{"cert", 'c', 1, "path to OCSP signing certificate (can be used multiple times"},
{"certid", 'X', 1, "smartcard or TPM certificate object handle (can be used multiple times)" },
{"cacert", 'C', 1, "CA certificate (can be used multiple times"},
{"index", 'x', 1, "OpenSSL-style index.txt to check status of certificates"},
{"digest", 'g', 1, "digest for signature creation, default: key-specific"},
{"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"},
{"lifetime", 'l', 1, "validity in minutes of the OCSP response (if missing, nextUpdate is omitted)"},
}
});
}