--- /dev/null
+/*
+ * 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 <credentials/sets/mem_cred.h>
+#include <credentials/certificates/ocsp_request.h>
+#include <credentials/certificates/ocsp_response.h>
+#include <credentials/certificates/ocsp_single_response.h>
+#include <credentials/certificates/ocsp_responder.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 entitity 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 identfiers 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 *ocsp_responder = NULL;
+ linked_list_t *responses = NULL;
+ chunk_t encoding = chunk_empty, nonce = 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':
+ goto usage;
+ case 'i':
+ file = arg;
+ continue;
+ case 'r':
+ op = OP_RESPOND;
+ continue;
+ case 'k':
+ 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 'c':
+ 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 'C':
+ 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;
+ }
+ creds->add_cert(creds, TRUE, cacert);
+ continue;
+ case 'l':
+ lifetime = atoi(arg) * 60;
+ if (!lifetime)
+ {
+ error = "invalid --lifetime value";
+ goto usage;
+ }
+ continue;
+ case 'g':
+ if (!enum_from_name(hash_algorithm_short_names, arg, &digest))
+ {
+ error = "invalid --digest type";
+ goto usage;
+ }
+ continue;
+ case 'R':
+ if (!parse_rsa_padding(arg, &pss))
+ {
+ error = "invalid RSA padding";
+ goto usage;
+ }
+ 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);
+ }
+
+ /* check for an ocsp responder */
+ if (op == OP_RESPOND)
+ {
+ ocsp_responder = lib->get(lib, "ocsp-responder");
+ if (!ocsp_responder)
+ {
+ DBG1(DBG_APP, " no ocsp-responder found");
+ ocsp_status = OCSP_INTERNALERROR;
+ goto gen;
+ }
+ }
+
+ /* 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 = ocsp_responder->get_status(ocsp_responder,
+ 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;
+ }
+ }
+ 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:
+ 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:
+ 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]+ [--key file]+ [--cacert 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)"},
+ {"cert", 'c', 1, "path to OCSP signing certificate (can be used multiple times"},
+ {"cacert", 'C', 1, "CA certificate (can be used multiple times"},
+ {"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)"},
+ }
+ });
+}