/* * 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 . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include #include #include "pki.h" #include #include #include #include #include /* * 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)"}, } }); }