diff --git a/conf/Makefile.am b/conf/Makefile.am index 362aaf0fd..ab9e70ce5 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -79,6 +79,7 @@ plugins = \ plugins/lookip.opt \ plugins/ntru.opt \ plugins/openssl.opt \ + plugins/openxpki.opt \ plugins/osx-attr.opt \ plugins/p-cscf.opt \ plugins/pkcs11.opt \ diff --git a/conf/plugins/openxpki.opt b/conf/plugins/openxpki.opt new file mode 100644 index 000000000..9bf8a0376 --- /dev/null +++ b/conf/plugins/openxpki.opt @@ -0,0 +1,4 @@ +charon.plugins.openxpki.database = + Database URI connecting to the OpenXPKI **certificate** database. If it + contains a password, make sure to adjust the permissions of the config + file accordingly. diff --git a/configure.ac b/configure.ac index cd8e1bf1d..ca2599d53 100644 --- a/configure.ac +++ b/configure.ac @@ -178,6 +178,7 @@ ARG_DISBL_SET([pkcs12], [disable PKCS12 container support plugin.]) ARG_DISBL_SET([pubkey], [disable RAW public key support plugin.]) ARG_DISBL_SET([sshkey], [disable SSH key decoding plugin.]) ARG_DISBL_SET([x509], [disable X509 certificate implementation plugin.]) +ARG_ENABL_SET([openxpki], [enable OCSP responder accessing OpenXPKI certificate database.]) # fetcher/resolver plugins ARG_ENABL_SET([curl], [enable CURL fetcher plugin to fetch files via libcurl. Requires libcurl.]) ARG_ENABL_SET([files], [enable simple file:// URI fetcher.]) @@ -1592,8 +1593,9 @@ ADD_PLUGIN([curl], [s charon pki scripts nm cmd]) ADD_PLUGIN([files], [s charon pki scripts nm cmd]) ADD_PLUGIN([winhttp], [s charon pki scripts]) ADD_PLUGIN([soup], [s charon pki scripts nm cmd]) -ADD_PLUGIN([mysql], [s charon pool manager medsrv attest]) -ADD_PLUGIN([sqlite], [s charon pool manager medsrv attest]) +ADD_PLUGIN([mysql], [s charon pki pool manager medsrv attest]) +ADD_PLUGIN([sqlite], [s charon pki pool manager medsrv attest]) +ADD_PLUGIN([openxpki], [s pki]) ADD_PLUGIN([attr], [c charon]) ADD_PLUGIN([attr-sql], [c charon]) ADD_PLUGIN([load-tester], [c charon]) @@ -1728,6 +1730,7 @@ AM_CONDITIONAL(USE_PKCS1, test x$pkcs1 = xtrue) AM_CONDITIONAL(USE_PKCS7, test x$pkcs7 = xtrue) AM_CONDITIONAL(USE_PKCS8, test x$pkcs8 = xtrue) AM_CONDITIONAL(USE_PKCS12, test x$pkcs12 = xtrue) +AM_CONDITIONAL(USE_OPENXPKI, test x$openxpki = xtrue) AM_CONDITIONAL(USE_PGP, test x$pgp = xtrue) AM_CONDITIONAL(USE_DNSKEY, test x$dnskey = xtrue) AM_CONDITIONAL(USE_SSHKEY, test x$sshkey = xtrue) @@ -2010,6 +2013,7 @@ AC_CONFIG_FILES([ src/libstrongswan/plugins/pkcs7/Makefile src/libstrongswan/plugins/pkcs8/Makefile src/libstrongswan/plugins/pkcs12/Makefile + src/libstrongswan/plugins/openxpki/Makefile src/libstrongswan/plugins/pgp/Makefile src/libstrongswan/plugins/dnskey/Makefile src/libstrongswan/plugins/sshkey/Makefile @@ -2201,6 +2205,7 @@ AC_CONFIG_FILES([ src/pki/man/pki---gen.1 src/pki/man/pki---issue.1 src/pki/man/pki---keyid.1 + src/pki/man/pki---ocsp.1 src/pki/man/pki---pkcs12.1 src/pki/man/pki---pkcs7.1 src/pki/man/pki---print.1 diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 12f26f346..3ce05d931 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -25,6 +25,7 @@ credentials/keys/public_key.c credentials/keys/shared_key.c \ credentials/keys/signature_params.c \ credentials/certificates/certificate.c credentials/certificates/crl.c \ credentials/certificates/ocsp_response.c credentials/certificates/x509.c \ +credentials/certificates/ocsp_single_response.c \ credentials/certificates/certificate_printer.c \ credentials/containers/container.c credentials/containers/pkcs12.c \ credentials/credential_manager.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index f118c723a..cc00d43f7 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -23,6 +23,7 @@ credentials/keys/public_key.c credentials/keys/shared_key.c \ credentials/keys/signature_params.c \ credentials/certificates/certificate.c credentials/certificates/crl.c \ credentials/certificates/ocsp_response.c credentials/certificates/x509.c \ +credentials/certificates/ocsp_single_response.c \ credentials/certificates/certificate_printer.c \ credentials/containers/container.c credentials/containers/pkcs12.c \ credentials/credential_manager.c \ @@ -91,7 +92,9 @@ credentials/keys/signature_params.h \ credentials/certificates/certificate.h credentials/certificates/x509.h \ credentials/certificates/ac.h credentials/certificates/crl.h \ credentials/certificates/pkcs10.h credentials/certificates/ocsp_request.h \ +credentials/certificates/ocsp_single_response.h \ credentials/certificates/ocsp_response.h \ +credentials/certificates/ocsp_responder.h \ credentials/certificates/pgp_certificate.h \ credentials/certificates/certificate_printer.h \ credentials/containers/container.h credentials/containers/pkcs7.h \ @@ -479,6 +482,13 @@ if MONOLITHIC endif endif +if USE_OPENXPKI + SUBDIRS += plugins/openxpki +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/openxpki/libstrongswan-openxpki.la +endif +endif + if USE_PGP SUBDIRS += plugins/pgp if MONOLITHIC diff --git a/src/libstrongswan/credentials/builder.c b/src/libstrongswan/credentials/builder.c index bb50e097f..27ae83c8f 100644 --- a/src/libstrongswan/credentials/builder.c +++ b/src/libstrongswan/credentials/builder.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2008 Martin Willi - * Copyright (C) 2016-2022 Andreas Steffen + * Copyright (C) 2016-2023 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -78,5 +78,8 @@ ENUM(builder_part_names, BUILD_FROM_FILE, BUILD_END, "BUILD_EDDSA_PUB", "BUILD_EDDSA_PRIV_ASN1_DER", "BUILD_CRITICAL_EXTENSION", + "BUILD_NONCE", + "BUILD_OCSP_STATUS", + "BUILD_OCSP_RESPONSES", "BUILD_END", ); diff --git a/src/libstrongswan/credentials/builder.h b/src/libstrongswan/credentials/builder.h index 6d143dd4f..e95265e4c 100644 --- a/src/libstrongswan/credentials/builder.h +++ b/src/libstrongswan/credentials/builder.h @@ -165,6 +165,12 @@ enum builder_part_t { BUILD_EDDSA_PRIV_ASN1_DER, /** OID of an [unsupported] critical extension */ BUILD_CRITICAL_EXTENSION, + /** nonce needed for some security protocol */ + BUILD_NONCE, + /** OCSP response status, ocsp_status_t */ + BUILD_OCSP_STATUS, + /** enumerator_t over (ocsp_single_response_t *response) */ + BUILD_OCSP_RESPONSES, /** end of variable argument builder list */ BUILD_END, }; diff --git a/src/libstrongswan/credentials/certificates/certificate_printer.c b/src/libstrongswan/credentials/certificates/certificate_printer.c index b6f324779..2e6a9d61c 100644 --- a/src/libstrongswan/credentials/certificates/certificate_printer.c +++ b/src/libstrongswan/credentials/certificates/certificate_printer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 Andreas Steffen + * Copyright (C) 2015-2023 Andreas Steffen * Copyright (C) 2010 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -19,6 +19,7 @@ #include "credentials/certificates/x509.h" #include "credentials/certificates/crl.h" #include "credentials/certificates/ac.h" +#include "credentials/certificates/ocsp_request.h" #include "credentials/certificates/ocsp_response.h" #include "credentials/certificates/pgp_certificate.h" @@ -474,6 +475,36 @@ static void print_ac(private_certificate_printer_t *this, ac_t *ac) } } +/** + * Print OCSP request specific information + */ +static void print_ocsp_request(private_certificate_printer_t *this, + ocsp_request_t *ocsp_request) +{ + enumerator_t *enumerator; + chunk_t nonce, issuerNameHash, issuerKeyHash, serialNumber; + hash_algorithm_t hashAlgorithm; + FILE *f = this->f; + + nonce = ocsp_request->get_nonce(ocsp_request); + fprintf(f, " nonce: %#B\n", &nonce); + + enumerator = ocsp_request->create_request_enumerator(ocsp_request); + while (enumerator->enumerate(enumerator, &hashAlgorithm, &issuerNameHash, + &issuerKeyHash, &serialNumber)) + { + fprintf(f, " serial: %#B\n", &serialNumber); + fprintf(f, " issuer: keyHash: %#B\n", &issuerKeyHash); + fprintf(f, " nameHash: %#B\n", &issuerNameHash); + if (hashAlgorithm != HASH_SHA1) + { + fprintf(f, " hashAlg: %#N\n", + hash_algorithm_short_names, hashAlgorithm); + } + } + enumerator->destroy(enumerator); +} + /** * Print OCSP response specific information */ @@ -576,7 +607,8 @@ METHOD(certificate_printer_t, print, void, { fprintf(f, " subject: \"%Y\"\n", subject); } - if (type != CERT_TRUSTED_PUBKEY && type != CERT_GPG) + if (type != CERT_TRUSTED_PUBKEY && type != CERT_GPG && + type != CERT_X509_OCSP_REQUEST) { fprintf(f, " issuer: \"%Y\"\n", cert->get_issuer(cert)); } @@ -637,6 +669,9 @@ METHOD(certificate_printer_t, print, void, case CERT_X509_AC: print_ac(this, (ac_t*)cert); break; + case CERT_X509_OCSP_REQUEST: + print_ocsp_request(this, (ocsp_request_t*)cert); + break; case CERT_X509_OCSP_RESPONSE: print_ocsp_response(this, (ocsp_response_t*)cert); break; @@ -694,6 +729,9 @@ METHOD(certificate_printer_t, print_caption, void, case CERT_X509_CRL: caption = "X.509 CRL"; break; + case CERT_X509_OCSP_REQUEST: + caption = "OCSP Request"; + break; case CERT_X509_OCSP_RESPONSE: caption = "OCSP Response"; break; diff --git a/src/libstrongswan/credentials/certificates/crl.h b/src/libstrongswan/credentials/certificates/crl.h index 9edd37fb8..36b68ef95 100644 --- a/src/libstrongswan/credentials/certificates/crl.h +++ b/src/libstrongswan/credentials/certificates/crl.h @@ -39,6 +39,7 @@ typedef enum crl_reason_t crl_reason_t; # undef CRL_REASON_CA_COMPROMISE # undef CRL_REASON_AFFILIATION_CHANGED # undef CRL_REASON_SUPERSEDED +# undef CRL_REASON_CESSATION_OF_OPERATION # undef CRL_REASON_CERTIFICATE_HOLD # undef CRL_REASON_REMOVE_FROM_CRL #endif @@ -52,7 +53,7 @@ enum crl_reason_t { CRL_REASON_CA_COMPROMISE = 2, CRL_REASON_AFFILIATION_CHANGED = 3, CRL_REASON_SUPERSEDED = 4, - CRL_REASON_CESSATION_OF_OPERATON = 5, + CRL_REASON_CESSATION_OF_OPERATION = 5, CRL_REASON_CERTIFICATE_HOLD = 6, CRL_REASON_REMOVE_FROM_CRL = 8, }; diff --git a/src/libstrongswan/credentials/certificates/ocsp_request.h b/src/libstrongswan/credentials/certificates/ocsp_request.h index 2a63e0cd3..f63d78b53 100644 --- a/src/libstrongswan/credentials/certificates/ocsp_request.h +++ b/src/libstrongswan/credentials/certificates/ocsp_request.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2019 Tobias Brunner * Copyright (C) 2008 Martin Willi + * Copyright (C) 2023 Andreas Steffen, strongSec GmbH * * Copyright (C) secunet Security Networks AG * @@ -43,6 +44,20 @@ struct ocsp_request_t { * @return nonce in the request (internal data) */ chunk_t (*get_nonce)(ocsp_request_t *this); + + /** + * Get the optional signer certificate. + * + * @return X.509 signer certificate + */ + certificate_t* (*get_signer_cert)(ocsp_request_t *this); + + /** + * Create an enumerator over the request list. + * + * @return enumerator over the request fields + */ + enumerator_t* (*create_request_enumerator)(ocsp_request_t *this); }; #endif /** OCSP_REQUEST_H_ @}*/ diff --git a/src/libstrongswan/credentials/certificates/ocsp_responder.h b/src/libstrongswan/credentials/certificates/ocsp_responder.h new file mode 100644 index 000000000..e7644dda9 --- /dev/null +++ b/src/libstrongswan/credentials/certificates/ocsp_responder.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +/** + * @defgroup ocsp_responder ocsp_responder + * @{ @ingroup certificates + */ + +#ifndef OCSP_RESPONDER_H_ +#define OCSP_RESPONDER_H_ + +#include + +typedef struct ocsp_responder_t ocsp_responder_t; + +/** + * OCSP responder object. + */ +struct ocsp_responder_t { + + /** + * Check the status of a certificate given by its serial number + * + * @param cacert X.509 certificate of issuer CA + * @param serial_number serial number of the certificate to be checked + * @param revocation_time receives time of revocation, if revoked + * @param reason receives reason of revocation, if revoked + * @return certificate validation status + */ + cert_validation_t (*get_status)(ocsp_responder_t *this, + certificate_t *cacert, + chunk_t serial_number, + time_t *revocation_time, + crl_reason_t *revocation_reason); + + /** + * Destroy an ocsp_responder_t object. + */ + void (*destroy)(ocsp_responder_t *this); + +}; + +#endif /** OCSP_RESPONDER_H_ @}*/ diff --git a/src/libstrongswan/credentials/certificates/ocsp_response.h b/src/libstrongswan/credentials/certificates/ocsp_response.h index cc7713726..6f97a3e7a 100644 --- a/src/libstrongswan/credentials/certificates/ocsp_response.h +++ b/src/libstrongswan/credentials/certificates/ocsp_response.h @@ -88,9 +88,9 @@ struct ocsp_response_t { enumerator_t* (*create_cert_enumerator)(ocsp_response_t *this); /** - * Create an enumerator over the contained responses. + * Create an enumerator over the contained single responses. * - * @return enumerator over major response fields + * @return enumerator over ocsp_single_response_t objects */ enumerator_t* (*create_response_enumerator)(ocsp_response_t *this); }; diff --git a/src/libstrongswan/credentials/certificates/ocsp_single_response.c b/src/libstrongswan/credentials/certificates/ocsp_single_response.c new file mode 100644 index 000000000..db639008a --- /dev/null +++ b/src/libstrongswan/credentials/certificates/ocsp_single_response.c @@ -0,0 +1,74 @@ +/* + * 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 "ocsp_single_response.h" + +typedef struct private_ocsp_single_response_t private_ocsp_single_response_t; + +/** + * Private data of an ocsp_single_response object. + */ +struct private_ocsp_single_response_t { + + /** + * Public interface for this ocsp_single_response object. + */ + ocsp_single_response_t public; + + /** + * reference counter + */ + refcount_t ref; +}; + +METHOD(ocsp_single_response_t, get_ref, ocsp_single_response_t*, + private_ocsp_single_response_t *this) +{ + ref_get(&this->ref); + return &this->public; +} + +METHOD(ocsp_single_response_t, destroy, void, + private_ocsp_single_response_t *this) +{ + if (ref_put(&this->ref)) + { + free(this->public.issuerNameHash.ptr); + free(this->public.issuerKeyHash.ptr); + free(this->public.serialNumber.ptr); + free(this); + } +} + +/** + * See header. + */ +ocsp_single_response_t *ocsp_single_response_create() +{ + private_ocsp_single_response_t *this; + + INIT(this, + .public = { + .hashAlgorithm = HASH_UNKNOWN, + .status = VALIDATION_FAILED, + .get_ref = _get_ref, + .destroy = _destroy, + }, + .ref = 1, + ); + + return &this->public; +} diff --git a/src/libstrongswan/credentials/certificates/ocsp_single_response.h b/src/libstrongswan/credentials/certificates/ocsp_single_response.h new file mode 100644 index 000000000..5ade5f4e2 --- /dev/null +++ b/src/libstrongswan/credentials/certificates/ocsp_single_response.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +/** + * @defgroup ocsp_single_response ocsp_single_response + * @{ @ingroup certificates + */ + +#ifndef OCSP_SINGLE_RESPONSE_H_ +#define OCSP_SINGLE_RESPONSE_H_ + +#include +#include + +typedef struct ocsp_single_response_t ocsp_single_response_t; + +/** + * Single response contained in OCSP response + */ +struct ocsp_single_response_t { + + /** + * Hash algorithm for the two hashes + */ + int hashAlgorithm; + + /** + * hash of issuer DN + */ + chunk_t issuerNameHash; + + /** + * issuerKeyID + */ + chunk_t issuerKeyHash; + + /** + * Serial number of certificate + */ + chunk_t serialNumber; + + /** + * OCSP certificate status + */ + cert_validation_t status; + + /** + * Time of revocation, if revoked + */ + time_t revocationTime; + + /** + * Revocation reason, if revoked + */ + crl_reason_t revocationReason; + + /** + * Creation of the OCSP single response + */ + time_t thisUpdate; + + /** + * Creation of next OCSP single response + */ + time_t nextUpdate; + + /** + * Get a new reference to the ocsp_single_response object. + * + * @return this, with an increased refcount + */ + ocsp_single_response_t* (*get_ref)(ocsp_single_response_t *this); + + /** + * Destroy an ocsp_single_response_t object. + */ + void (*destroy)(ocsp_single_response_t *this); +}; + +/** + * Create an ocsp_single_response_t object + * + * @return ocsp_single_response_t object + */ +ocsp_single_response_t *ocsp_single_response_create(void); + +#endif /** OCSP_SINGLE_RESPONSE_H_ @}*/ diff --git a/src/libstrongswan/plugins/openxpki/Makefile.am b/src/libstrongswan/plugins/openxpki/Makefile.am new file mode 100644 index 000000000..d10c2386f --- /dev/null +++ b/src/libstrongswan/plugins/openxpki/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-openxpki.la +else +plugin_LTLIBRARIES = libstrongswan-openxpki.la +endif + +libstrongswan_openxpki_la_SOURCES = \ + openxpki_plugin.h openxpki_plugin.c \ + openxpki_ocsp_responder.h openxpki_ocsp_responder.c + +libstrongswan_openxpki_la_LDFLAGS = -module -avoid-version diff --git a/src/libstrongswan/plugins/openxpki/openxpki_ocsp_responder.c b/src/libstrongswan/plugins/openxpki/openxpki_ocsp_responder.c new file mode 100644 index 000000000..2eda8634f --- /dev/null +++ b/src/libstrongswan/plugins/openxpki/openxpki_ocsp_responder.c @@ -0,0 +1,192 @@ +/* + * 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 "openxpki_ocsp_responder.h" + +typedef struct private_openxpki_ocsp_responder_t private_openxpki_ocsp_responder_t; + +/** + * Private Data of a openxpki_ocsp_responder_t object. + */ +struct private_openxpki_ocsp_responder_t { + + /** + * Public data + */ + openxpki_ocsp_responder_t public; + + /** + * OpenXPKI certificate database + */ + database_t *db; +}; + +METHOD(ocsp_responder_t, get_status, cert_validation_t, + private_openxpki_ocsp_responder_t *this, certificate_t *cacert, + chunk_t serial_number, time_t *revocation_time, crl_reason_t *reason) +{ + cert_validation_t validation = VALIDATION_FAILED; + int rev_time; + const int max_decimals = 49; + const int max_bytes = max_decimals / 2.4083; + char serial[max_decimals + 1], authKeyId[60]; + char *status, *reason_code; + chunk_t subjectKeyId; + public_key_t *public; + enumerator_t *e; + bool success; + + /* convert serialNumber from binary to decimal as required for the DB query. + * check for a potential overflow since the database table field supports + * up to 49 decimal digits, only. + */ + if (serial_number.len > max_bytes) + { + DBG1(DBG_LIB, "serialNumber conversion exceeds %d decimals", max_decimals); + return VALIDATION_FAILED; + + } + chunk_to_dec(serial_number, serial); + + /* additionally use the subjectyKeyId of the issuing CA for the DB query */ + public = cacert->get_public_key(cacert); + success = public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &subjectKeyId); + public->destroy(public); + + if (!success) + { + DBG1(DBG_LIB, "failed to extract subjectKeyId from CA certificate"); + return VALIDATION_FAILED; + } + + /* the authKeyId of the certificate is the subjectKeyId of the issuing CA */ + snprintf(authKeyId, sizeof(authKeyId), "%#B", &subjectKeyId); + + /* query the OpenXPKI certificate database */ + e = this->db->query(this->db, "SELECT status, reason_code, revocation_time " + "FROM certificate WHERE cert_key = ? " + "AND authority_key_identifier = ?", DB_TEXT, serial, + DB_TEXT, authKeyId, DB_TEXT, DB_TEXT, DB_INT); + if (e && e->enumerate(e, &status, &reason_code, &rev_time)) + { + if (streq(status, "ISSUED") || streq(status, "ISSUANCE_PENDING")) + { + validation = VALIDATION_GOOD; + } + else if (streq(status, "UNKNOWN")) + { + validation = VALIDATION_FAILED; + } + else if (streq(status, "REVOKED") || streq(status, "HOLD") || + streq(status, "CRL_ISSUANCE_PENDING")) + { + validation = VALIDATION_REVOKED; + + if (revocation_time) + { + *revocation_time = rev_time; + } + if (reason) + { + if (streq(reason_code, "unspecified")) + { + *reason = CRL_REASON_UNSPECIFIED; + } + else if (streq(reason_code, "keyCompromise")) + { + *reason = CRL_REASON_KEY_COMPROMISE; + } + else if (streq(reason_code, "CACompromise")) + { + *reason = CRL_REASON_CA_COMPROMISE; + } + else if (streq(reason_code, "affiliationChanged")) + { + *reason = CRL_REASON_AFFILIATION_CHANGED; + } + else if (streq(reason_code, "superseded")) + { + *reason = CRL_REASON_SUPERSEDED; + } + else if (streq(reason_code, "cessationOfOperation")) + { + *reason = CRL_REASON_CESSATION_OF_OPERATION; + } + else if (streq(reason_code, "certificateHold")) + { + *reason = CRL_REASON_CERTIFICATE_HOLD; + validation = VALIDATION_ON_HOLD; + } + else if (streq(reason_code, "removeFromCRL")) + { + *reason = CRL_REASON_REMOVE_FROM_CRL; + } + else + { + *reason = CRL_REASON_UNSPECIFIED; + } + } + } + } + DESTROY_IF(e); + + return validation; +} + +METHOD(ocsp_responder_t, destroy, void, + private_openxpki_ocsp_responder_t *this) +{ + this->db->destroy(this->db); + free(this); +} + +/* + * See header + */ +ocsp_responder_t *openxpki_ocsp_responder_create() +{ + private_openxpki_ocsp_responder_t *this; + database_t *db; + char *uri; + + uri = lib->settings->get_str(lib->settings, + "%s.plugins.openxpki.database", NULL, lib->ns); + if (!uri) + { + DBG1(DBG_CFG, "openxpki database URI missing"); + return NULL; + } + db = lib->db->create(lib->db, uri); + if (!db) + { + DBG1(DBG_CFG, "opening openxpki database failed"); + return NULL; + } + INIT(this, + .public = { + .interface = { + .get_status = _get_status, + .destroy = _destroy, + }, + }, + .db = db, + ); + + return &this->public.interface; +} diff --git a/src/libstrongswan/plugins/openxpki/openxpki_ocsp_responder.h b/src/libstrongswan/plugins/openxpki/openxpki_ocsp_responder.h new file mode 100644 index 000000000..5b4365c97 --- /dev/null +++ b/src/libstrongswan/plugins/openxpki/openxpki_ocsp_responder.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +/** + * @defgroup openxpki_ocsp_responder openxpki_ocsp_responder + * @{ @ingroup openxpki_p + */ + +#ifndef OPENXPKI_OCSP_RESPONDER_H_ +#define OPENXPKI_OCSP_RESPONDER_H_ + +typedef struct openxpki_ocsp_responder_t openxpki_ocsp_responder_t; + +#include + +/** + * OCSP responder implementation using OpenXPKI. + */ +struct openxpki_ocsp_responder_t { + + /** + * Implements ocsp_responder interface + */ + ocsp_responder_t interface; +}; + +/** + * Create a openxpki_ocsp_responder instance. + */ +ocsp_responder_t *openxpki_ocsp_responder_create(); + +#endif /** OPENXPKI_OCSP_RESPONDER_H_ @}*/ diff --git a/src/libstrongswan/plugins/openxpki/openxpki_plugin.c b/src/libstrongswan/plugins/openxpki/openxpki_plugin.c new file mode 100644 index 000000000..7d7138bc5 --- /dev/null +++ b/src/libstrongswan/plugins/openxpki/openxpki_plugin.c @@ -0,0 +1,104 @@ +/* + * 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 "openxpki_plugin.h" + +#include "openxpki_ocsp_responder.h" + +typedef struct private_openxpki_plugin_t private_openxpki_plugin_t; + +/** + * Private data of an openxpki_plugin_t object. + */ +struct private_openxpki_plugin_t { + + /** + * Public interface. + */ + openxpki_plugin_t public; + + /** + * OCSP responder + */ + ocsp_responder_t *ocsp_responder; +}; + +METHOD(plugin_t, get_name, char*, + private_openxpki_plugin_t *this) +{ + return "openxpki"; +} + +static bool plugin_cb(private_openxpki_plugin_t *this, + plugin_feature_t *feature, bool reg, void *cb_data) +{ + if (reg) + { + /* Is there already a registered OCSP responder? */ + if (!lib->get(lib, "ocsp-responder")) + { + this->ocsp_responder = openxpki_ocsp_responder_create(); + lib->set(lib, "ocsp-responder", this->ocsp_responder); + } + } + else + { + if (this->ocsp_responder) + { + lib->set(lib, "ocsp-responder", NULL); + this->ocsp_responder->destroy(this->ocsp_responder); + } + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_openxpki_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "ocsp-responder"), + PLUGIN_DEPENDS(DATABASE, DB_MYSQL), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_openxpki_plugin_t *this) +{ + free(this); +} + +/* + * see header file + */ +plugin_t *openxpki_plugin_create() +{ + private_openxpki_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libstrongswan/plugins/openxpki/openxpki_plugin.h b/src/libstrongswan/plugins/openxpki/openxpki_plugin.h new file mode 100644 index 000000000..28ff8f908 --- /dev/null +++ b/src/libstrongswan/plugins/openxpki/openxpki_plugin.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +/** + * @defgroup openxpki_p openxpki + * @ingroup plugins + * + * @defgroup openxpki_plugin openxpki_plugin + * @{ @ingroup openxpki_p + */ + +#ifndef OPENXPKI_PLUGIN_H_ +#define OPENXPKI_PLUGIN_H_ + +#include + +typedef struct openxpki_plugin_t openxpki_plugin_t; + +/** + * Plugin implementing an OCSP responder based on OpenXPKI. + */ +struct openxpki_plugin_t { + + /** + * implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** OPENXPKI_PLUGIN_H_ @}*/ diff --git a/src/libstrongswan/plugins/x509/x509_ocsp_request.c b/src/libstrongswan/plugins/x509/x509_ocsp_request.c index 152fd2044..4f0362967 100644 --- a/src/libstrongswan/plugins/x509/x509_ocsp_request.c +++ b/src/libstrongswan/plugins/x509/x509_ocsp_request.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2017-2019 Tobias Brunner * Copyright (C) 2008-2009 Martin Willi - * Copyright (C) 2007-2022 Andreas Steffen + * Copyright (C) 2007-2023 Andreas Steffen, strongSec GmbH * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen * * Copyright (C) secunet Security Networks AG @@ -22,13 +22,15 @@ #include #include #include +#include #include #include #include #include #include -#define NONCE_LEN 16 +/* RFC 8954 OCSP Nonce Extension */ +#define NONCE_LEN 32 typedef struct private_x509_ocsp_request_t private_x509_ocsp_request_t; @@ -43,9 +45,9 @@ struct private_x509_ocsp_request_t { x509_ocsp_request_t public; /** - * CA the candidates belong to + * CA the certificates where issued by */ - x509_t *ca; + certificate_t *cacert; /** * Requestor name, subject of cert used if not set @@ -63,9 +65,9 @@ struct private_x509_ocsp_request_t { private_key_t *key; /** - * list of certificates to check, x509_t + * list of X.509 certificates to check */ - linked_list_t *candidates; + linked_list_t *reqCerts; /** * nonce used in request @@ -77,12 +79,53 @@ struct private_x509_ocsp_request_t { */ chunk_t encoding; + /** + * data for signature verification + */ + chunk_t tbsRequest; + + /** + * signature scheme + */ + signature_params_t *scheme; + + /** + * signature + */ + chunk_t signature; + /** * reference count */ refcount_t ref; }; +/** + * Single reqCert object sent in OCSP request + */ +typedef struct { + /** hash algorithm for the two hashes */ + hash_algorithm_t hashAlgorithm; + /** hash of issuer DN */ + chunk_t issuerNameHash; + /** issuerKeyID */ + chunk_t issuerKeyHash; + /** serial number of certificate */ + chunk_t serialNumber; +} req_cert_t; + +/** + * Clean up a reqCert object + */ +CALLBACK(req_cert_destroy, void, + req_cert_t *reqCert) +{ + chunk_free(&reqCert->issuerNameHash); + chunk_free(&reqCert->issuerKeyHash); + chunk_free(&reqCert->serialNumber); + free(reqCert); +} + static const chunk_t ASN1_nonce_oid = chunk_from_chars( 0x06, 0x09, 0x2B, 0x06, @@ -125,15 +168,15 @@ static chunk_t build_requestorName(private_x509_ocsp_request_t *this) * build Request, not using singleRequestExtensions */ static chunk_t build_Request(private_x509_ocsp_request_t *this, - chunk_t issuerNameHash, chunk_t issuerKeyHash, - chunk_t serialNumber) + req_cert_t *reqCert) { return asn1_wrap(ASN1_SEQUENCE, "m", - asn1_wrap(ASN1_SEQUENCE, "mmmm", - asn1_algorithmIdentifier(OID_SHA1), - asn1_simple_object(ASN1_OCTET_STRING, issuerNameHash), - asn1_simple_object(ASN1_OCTET_STRING, issuerKeyHash), - asn1_integer("c", serialNumber))); + asn1_wrap(ASN1_SEQUENCE, "mmmm", + asn1_algorithmIdentifier( + hasher_algorithm_to_oid(reqCert->hashAlgorithm)), + asn1_simple_object(ASN1_OCTET_STRING, reqCert->issuerNameHash), + asn1_simple_object(ASN1_OCTET_STRING, reqCert->issuerKeyHash), + asn1_integer("c", reqCert->serialNumber))); } /** @@ -141,57 +184,18 @@ static chunk_t build_Request(private_x509_ocsp_request_t *this, */ static chunk_t build_requestList(private_x509_ocsp_request_t *this) { - chunk_t issuerNameHash, issuerKeyHash; - identification_t *issuer; - x509_t *x509; - certificate_t *cert; - chunk_t list = chunk_empty; - public_key_t *public; + chunk_t list = chunk_empty, request; + enumerator_t *enumerator; + req_cert_t *reqCert; - cert = (certificate_t*)this->ca; - public = cert->get_public_key(cert); - if (public) + enumerator = this->reqCerts->create_enumerator(this->reqCerts); + while (enumerator->enumerate(enumerator, &reqCert)) { - hasher_t *hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); - if (hasher) - { - if (public->get_fingerprint(public, KEYID_PUBKEY_SHA1, - &issuerKeyHash)) - { - enumerator_t *enumerator; - - issuer = cert->get_subject(cert); - if (hasher->allocate_hash(hasher, issuer->get_encoding(issuer), - &issuerNameHash)) - { - enumerator = this->candidates->create_enumerator( - this->candidates); - while (enumerator->enumerate(enumerator, &x509)) - { - chunk_t request, serialNumber; - - serialNumber = x509->get_serial(x509); - request = build_Request(this, issuerNameHash, - issuerKeyHash, serialNumber); - list = chunk_cat("mm", list, request); - } - enumerator->destroy(enumerator); - chunk_free(&issuerNameHash); - } - hasher->destroy(hasher); - } - } - else - { - DBG1(DBG_LIB, "creating OCSP request failed, SHA1 not supported"); - } - public->destroy(public); - } - else - { - DBG1(DBG_LIB, "creating OCSP request failed, CA certificate has " - "no public key"); + request = build_Request(this, reqCert); + list = chunk_cat("mm", list, request); } + enumerator->destroy(enumerator); + return asn1_wrap(ASN1_SEQUENCE, "m", list); } @@ -205,7 +209,7 @@ static chunk_t build_nonce(private_x509_ocsp_request_t *this) rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); if (!rng || !rng->allocate_bytes(rng, NONCE_LEN, &this->nonce)) { - DBG1(DBG_LIB, "creating OCSP request nonce failed, no RNG found"); + DBG1(DBG_LIB, "failed to create RNG"); DESTROY_IF(rng); return chunk_empty; } @@ -237,7 +241,7 @@ static chunk_t build_requestExtensions(private_x509_ocsp_request_t *this) } /** - * build tbsRequest + * Build tbsRequest */ static chunk_t build_tbsRequest(private_x509_ocsp_request_t *this) { @@ -298,7 +302,6 @@ static chunk_t build_optionalSignature(private_x509_ocsp_request_t *this, /** * Build the OCSPRequest data - * */ static chunk_t build_OCSPRequest(private_x509_ocsp_request_t *this) { @@ -312,6 +315,187 @@ static chunk_t build_OCSPRequest(private_x509_ocsp_request_t *this) return asn1_wrap(ASN1_SEQUENCE, "mm", tbsRequest, optionalSignature); } +/** + * ASN.1 definition of ocspRequest + */ +static const asn1Object_t ocspRequestObjects[] = { + { 0, "OCSPRequest", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */ + { 1, "tbsRequest", ASN1_SEQUENCE, ASN1_OBJ }, /* 1 */ + { 2, "versionContext", ASN1_CONTEXT_C_0, ASN1_NONE | + ASN1_DEF }, /* 2 */ + { 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 3 */ + { 2, "requestorNameContext", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 4 */ + { 3, "requestorName", ASN1_CONTEXT_C_4, ASN1_BODY }, /* 5 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 6 */ + { 2, "requestList", ASN1_SEQUENCE, ASN1_LOOP }, /* 7 */ + { 3, "request", ASN1_SEQUENCE, ASN1_BODY }, /* 8 */ + { 4, "reqCert", ASN1_SEQUENCE, ASN1_NONE }, /* 9 */ + { 5, "hashAlgorithm", ASN1_EOC, ASN1_RAW }, /* 10 */ + { 5, "issuerNameHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 11 */ + { 5, "issuerKeyHash", ASN1_OCTET_STRING, ASN1_BODY }, /* 12 */ + { 5, "serialNumber", ASN1_INTEGER, ASN1_BODY }, /* 13 */ + { 4, "singleRequestExtensions", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 14 */ + { 4, "end opt", ASN1_EOC, ASN1_END }, /* 15 */ + { 2, "end loop", ASN1_EOC, ASN1_END }, /* 16 */ + { 2, "requestExtensions", ASN1_CONTEXT_C_2, ASN1_OPT }, /* 17 */ + { 3, "Extensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 18 */ + { 4, "Extension", ASN1_SEQUENCE, ASN1_NONE }, /* 19 */ + { 5, "extnID", ASN1_OID, ASN1_BODY }, /* 20 */ + { 5, "critical", ASN1_BOOLEAN, ASN1_BODY | + ASN1_DEF }, /* 21 */ + { 5, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 22 */ + { 3, "end loop", ASN1_EOC, ASN1_END }, /* 23 */ + { 2, "end opt", ASN1_EOC, ASN1_END }, /* 24 */ + { 1, "optionalSignature", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 25 */ + { 2, "signature", ASN1_SEQUENCE, ASN1_NONE }, /* 26 */ + { 3, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 27 */ + { 3, "signature", ASN1_BIT_STRING, ASN1_BODY }, /* 28 */ + { 3, "certsContext", ASN1_CONTEXT_C_0, ASN1_OPT }, /* 29 */ + { 4, "certs", ASN1_SEQUENCE, ASN1_LOOP }, /* 30 */ + { 5, "certificate", ASN1_EOC, ASN1_RAW }, /* 31 */ + { 4, "end loop", ASN1_EOC, ASN1_END }, /* 32 */ + { 3, "end opt", ASN1_EOC, ASN1_END }, /* 33 */ + { 1, "end opt", ASN1_EOC, ASN1_END }, /* 34 */ + { 0, "exit", ASN1_EOC, ASN1_EXIT } +}; + +#define OCSP_REQ_TBS_REQUEST 1 +#define OCSP_REQ_REQUESTOR 5 +#define OCSP_REQ_REQ_CERT 9 +#define OCSP_REQ_HASH_ALG 10 +#define OCSP_REQ_ISSUER_NAME_HASH 11 +#define OCSP_REQ_ISSUER_KEY_HASH 12 +#define OCSP_REQ_SERIAL_NUMBER 13 +#define OCSP_REQ_EXTN_ID 20 +#define OCSP_REQ_CRITICAL 21 +#define OCSP_REQ_EXTN_VALUE 22 +#define OCSP_REQ_SIG_ALG 27 +#define OCSP_REQ_SIGNATURE 28 +#define OCSP_REQ_CERTIFICATE 31 + +/** + * Parse the OCSPRequest data + * + */ +static bool parse_OCSPRequest(private_x509_ocsp_request_t *this) +{ + asn1_parser_t *parser; + req_cert_t *reqCert = NULL; + chunk_t object; + int extn_oid = OID_UNKNOWN; + int objectID; + bool critical = FALSE, success = FALSE; + + parser = asn1_parser_create(ocspRequestObjects, this->encoding); + + while (parser->iterate(parser, &objectID, &object)) + { + u_int level = parser->get_level(parser)+1; + + switch (objectID) + { + + case OCSP_REQ_TBS_REQUEST: + this->tbsRequest = object; + break; + case OCSP_REQ_REQUESTOR: + this->requestor = identification_create_from_encoding(ID_DER_ASN1_DN, object); + break; + case OCSP_REQ_REQ_CERT: + INIT(reqCert); + this->reqCerts->insert_last(this->reqCerts, reqCert); + break; + case OCSP_REQ_HASH_ALG: + reqCert->hashAlgorithm = hasher_algorithm_from_oid( + asn1_parse_algorithmIdentifier(object, level, NULL)); + if (reqCert->hashAlgorithm == HASH_UNKNOWN) + { + DBG1(DBG_ASN, "unknowm hash algorithm"); + goto end; + } + break; + case OCSP_REQ_ISSUER_NAME_HASH: + reqCert->issuerNameHash = chunk_clone(object); + break; + case OCSP_REQ_ISSUER_KEY_HASH: + reqCert->issuerKeyHash = chunk_clone(object); + break; + case OCSP_REQ_SERIAL_NUMBER: + reqCert->serialNumber = chunk_clone(chunk_skip_zero(object)); + break; + case OCSP_REQ_EXTN_ID: + extn_oid = asn1_known_oid(object); + break; + case OCSP_REQ_CRITICAL: + critical = object.len && *object.ptr; + DBG2(DBG_ASN, " %s", critical ? "TRUE" : "FALSE"); + break; + case OCSP_REQ_EXTN_VALUE: + { + switch (extn_oid) + { + case OID_NONCE: + if (!asn1_parse_simple_object(&object, ASN1_OCTET_STRING, + level, "nonce")) + { + goto end; + } + this->nonce = chunk_clone(object); + break; + case OID_RESPONSE: + if (!asn1_parse_simple_object(&object, ASN1_SEQUENCE, + level, "acceptableResponses")) + { + goto end; + } + break; + default: + if (critical && lib->settings->get_bool(lib->settings, + "%s.x509.enforce_critical", TRUE, lib->ns)) + { + DBG1(DBG_ASN, "critical '%s' extension not supported", + (extn_oid == OID_UNKNOWN) ? "unknown" : + (char*)oid_names[extn_oid].name); + goto end; + } + break; + } + break; + } + case OCSP_REQ_SIG_ALG: + INIT(this->scheme); + if (!signature_params_parse(object, level, this->scheme)) + { + DBG1(DBG_ASN, " unable to parse signature algorithm"); + goto end; + } + + break; + case OCSP_REQ_SIGNATURE: + this->signature = chunk_skip(object, 1); + break; + case OCSP_REQ_CERTIFICATE: + if (this->cert) + { + DBG1(DBG_LIB, " skipping additional signing certificate"); + break; + } + this->cert = lib->creds->create(lib->creds, + CRED_CERTIFICATE,CERT_X509, + BUILD_BLOB_ASN1_DER, object, BUILD_END); + if (!this->cert) + { + goto end; + } + break; + } + } + success = parser->success(parser); + +end: + parser->destroy(parser); + return success; +} METHOD(certificate_t, get_type, certificate_type_t, private_x509_ocsp_request_t *this) @@ -322,62 +506,60 @@ METHOD(certificate_t, get_type, certificate_type_t, METHOD(certificate_t, get_subject, identification_t*, private_x509_ocsp_request_t *this) { - certificate_t *ca = (certificate_t*)this->ca; - if (this->requestor) { return this->requestor; } - if (this->cert) - { - return this->cert->get_subject(this->cert); - } - return ca->get_subject(ca); + return (this->cert) ? this->cert->get_subject(this->cert) : NULL; } METHOD(certificate_t, get_issuer, identification_t*, private_x509_ocsp_request_t *this) { - certificate_t *ca = (certificate_t*)this->ca; - - return ca->get_subject(ca); + return this->cacert ? this->cacert->get_subject(this->cacert) : NULL; } METHOD(certificate_t, has_subject, id_match_t, private_x509_ocsp_request_t *this, identification_t *subject) { - certificate_t *current; - enumerator_t *enumerator; - id_match_t match, best = ID_MATCH_NONE; - - enumerator = this->candidates->create_enumerator(this->candidates); - while (enumerator->enumerate(enumerator, ¤t)) - { - match = current->has_subject(current, subject); - if (match > best) - { - best = match; - } - } - enumerator->destroy(enumerator); - return best; + return ID_MATCH_NONE; } METHOD(certificate_t, has_issuer, id_match_t, private_x509_ocsp_request_t *this, identification_t *issuer) { - certificate_t *ca = (certificate_t*)this->ca; - - return ca->has_subject(ca, issuer); + return this->cacert ? this->cacert->has_subject(this->cacert, issuer) : + ID_MATCH_NONE; } METHOD(certificate_t, issued_by, bool, private_x509_ocsp_request_t *this, certificate_t *issuer, signature_params_t **scheme) { - DBG1(DBG_LIB, "OCSP request validation not implemented!"); - return FALSE; + public_key_t *key; + bool valid; + + if (issuer->get_type(issuer) != CERT_X509 || this->cert == NULL || + !issuer->equals(issuer, this->cert)) + { + return FALSE; + } + + key = issuer->get_public_key(issuer); + if (!key) + { + return FALSE; + } + valid = key->verify(key, this->scheme->scheme, this->scheme->params, + this->tbsRequest, this->signature); + key->destroy(key); + + if (valid && scheme) + { + *scheme = signature_params_clone(this->scheme); + } + return valid; } METHOD(certificate_t, get_public_key, public_key_t*, @@ -390,17 +572,7 @@ METHOD(certificate_t, get_validity, bool, private_x509_ocsp_request_t *this, time_t *when, time_t *not_before, time_t *not_after) { - certificate_t *cert; - - if (this->cert) - { - cert = this->cert; - } - else - { - cert = (certificate_t*)this->ca; - } - return cert->get_validity(cert, when, not_before, not_after); + return FALSE; } METHOD(certificate_t, get_encoding, bool, @@ -455,11 +627,12 @@ METHOD(certificate_t, destroy, void, { if (ref_put(&this->ref)) { - DESTROY_IF((certificate_t*)this->ca); + DESTROY_IF(this->cacert); DESTROY_IF(this->requestor); DESTROY_IF(this->cert); DESTROY_IF(this->key); - this->candidates->destroy_offset(this->candidates, offsetof(certificate_t, destroy)); + signature_params_destroy(this->scheme); + this->reqCerts->destroy_function(this->reqCerts, req_cert_destroy); chunk_free(&this->nonce); chunk_free(&this->encoding); free(this); @@ -472,6 +645,52 @@ METHOD(ocsp_request_t, get_nonce, chunk_t, return this->nonce; } +METHOD(ocsp_request_t, get_signer_cert, certificate_t*, + private_x509_ocsp_request_t *this) +{ + return this->cert; +} + +CALLBACK(filter, bool, + void *data, enumerator_t *orig, va_list args) +{ + req_cert_t *reqCert; + hash_algorithm_t *hashAlgorithm; + chunk_t *issuerNameHash, *issuerKeyHash, *serialNumber; + + VA_ARGS_VGET(args, hashAlgorithm, issuerNameHash, issuerKeyHash, serialNumber); + + if (orig->enumerate(orig, &reqCert)) + { + if (hashAlgorithm) + { + *hashAlgorithm = reqCert->hashAlgorithm; + } + if (issuerNameHash) + { + *issuerNameHash = reqCert->issuerNameHash; + } + if (issuerKeyHash) + { + *issuerKeyHash = reqCert->issuerKeyHash; + } + if (serialNumber) + { + *serialNumber = reqCert->serialNumber; + } + return TRUE; + } + return FALSE; +} + +METHOD(ocsp_request_t, create_request_enumerator, enumerator_t*, + private_x509_ocsp_request_t *this) +{ + return enumerator_create_filter( + this->reqCerts->create_enumerator(this->reqCerts), + filter, NULL, NULL); +} + /** * create an empty but initialized OCSP request */ @@ -497,9 +716,11 @@ static private_x509_ocsp_request_t *create_empty() .destroy = _destroy, }, .get_nonce = _get_nonce, + .get_signer_cert = _get_signer_cert, + .create_request_enumerator = _create_request_enumerator, }, }, - .candidates = linked_list_create(), + .reqCerts = linked_list_create(), .ref = 1, ); @@ -511,12 +732,15 @@ static private_x509_ocsp_request_t *create_empty() */ x509_ocsp_request_t *x509_ocsp_request_gen(certificate_type_t type, va_list args) { - private_x509_ocsp_request_t *req; - certificate_t *cert; + private_x509_ocsp_request_t *this; private_key_t *private; identification_t *subject; + certificate_t *cert; + x509_t *x509; + req_cert_t *reqCert; + + this = create_empty(); - req = create_empty(); while (TRUE) { switch (va_arg(args, builder_part_t)) @@ -525,43 +749,137 @@ x509_ocsp_request_t *x509_ocsp_request_gen(certificate_type_t type, va_list args cert = va_arg(args, certificate_t*); if (cert->get_type(cert) == CERT_X509) { - req->ca = (x509_t*)cert->get_ref(cert); + this->cacert = cert->get_ref(cert); } continue; case BUILD_CERT: cert = va_arg(args, certificate_t*); if (cert->get_type(cert) == CERT_X509) { - req->candidates->insert_last(req->candidates, - cert->get_ref(cert)); + x509 = (x509_t*)cert; + INIT(reqCert, + .serialNumber = chunk_clone(x509->get_serial(x509)), + ); + this->reqCerts->insert_last(this->reqCerts, reqCert); } continue; case BUILD_SIGNING_CERT: cert = va_arg(args, certificate_t*); - req->cert = cert->get_ref(cert); + this->cert = cert->get_ref(cert); continue; case BUILD_SIGNING_KEY: private = va_arg(args, private_key_t*); - req->key = private->get_ref(private); + this->key = private->get_ref(private); continue; case BUILD_SUBJECT: subject = va_arg(args, identification_t*); - req->requestor = subject->clone(subject); + this->requestor = subject->clone(subject); + continue; + case BUILD_END: + break; + default: + goto error; + } + break; + } + + if (this->cacert) + { + chunk_t issuerNameHash, issuerKeyHash; + enumerator_t *enumerator; + identification_t *issuer; + public_key_t *public; + req_cert_t *reqCert; + hasher_t *hasher; + + public = this->cacert->get_public_key(this->cacert); + if (!public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &issuerKeyHash)) + { + DBG1(DBG_LIB, "failed to compute SHA1 issuerKeyHash"); + public->destroy(public); + goto error; + } + public->destroy(public); + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!hasher) + { + DBG1(DBG_LIB, "failed to create SHA1 hasher"); + goto error; + } + + issuer = this->cacert->get_subject(this->cacert); + if (!hasher->allocate_hash(hasher, issuer->get_encoding(issuer), + &issuerNameHash)) + { + DBG1(DBG_LIB, "failed to compute SHA1 issuerNameHash"); + hasher->destroy(hasher); + goto error; + } + hasher->destroy(hasher); + + enumerator = this->reqCerts->create_enumerator(this->reqCerts); + while (enumerator->enumerate(enumerator, &reqCert)) + { + reqCert->hashAlgorithm = HASH_SHA1; + reqCert->issuerNameHash = chunk_clone(issuerNameHash); + reqCert->issuerKeyHash = chunk_clone(issuerKeyHash); + } + enumerator->destroy(enumerator); + chunk_free(&issuerNameHash); + + this->encoding = build_OCSPRequest(this); + + return &this->public; + } + +error: + destroy(this); + return NULL; +} + +/** + * load an OCSP request + */ +static x509_ocsp_request_t *load(chunk_t blob) +{ + private_x509_ocsp_request_t *this; + + this = create_empty(); + this->encoding = chunk_clone(blob); + + if (!parse_OCSPRequest(this)) + { + destroy(this); + return NULL; + } + return &this->public; +} + +/** + * See header. + */ +x509_ocsp_request_t *x509_ocsp_request_load(certificate_type_t type, va_list args) +{ + chunk_t blob = chunk_empty; + + while (TRUE) + { + switch (va_arg(args, builder_part_t)) + { + case BUILD_BLOB_ASN1_DER: + blob = va_arg(args, chunk_t); continue; case BUILD_END: break; default: - destroy(req); return NULL; } break; } - if (req->ca) + if (blob.ptr) { - req->encoding = build_OCSPRequest(req); - return &req->public; + return load(blob); } - destroy(req); return NULL; } - diff --git a/src/libstrongswan/plugins/x509/x509_ocsp_request.h b/src/libstrongswan/plugins/x509/x509_ocsp_request.h index d9ff4f5cb..74ba0532b 100644 --- a/src/libstrongswan/plugins/x509/x509_ocsp_request.h +++ b/src/libstrongswan/plugins/x509/x509_ocsp_request.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2009 Martin Willi + * Copyright (C) 2023 Andreas Steffen, strongSec GmbH * * Copyright (C) secunet Security Networks AG * @@ -54,4 +55,13 @@ struct x509_ocsp_request_t { */ x509_ocsp_request_t *x509_ocsp_request_gen(certificate_type_t type, va_list args); +/** + * Load a X.509 OCSP request. + * + * @param type certificate type, CERT_X509_OCSP_REQUEST only + * @param args builder_part_t argument list + * @return OCSP request, NULL on failure + */ +x509_ocsp_request_t *x509_ocsp_request_load(certificate_type_t type, va_list args); + #endif /** X509_OCSP_REQUEST_H_ @}*/ diff --git a/src/libstrongswan/plugins/x509/x509_ocsp_response.c b/src/libstrongswan/plugins/x509/x509_ocsp_response.c index 9954e55ce..3badf36b9 100644 --- a/src/libstrongswan/plugins/x509/x509_ocsp_response.c +++ b/src/libstrongswan/plugins/x509/x509_ocsp_response.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2017-2019 Tobias Brunner * Copyright (C) 2008-2009 Martin Willi - * Copyright (C) 2007-2022 Andreas Steffen + * Copyright (C) 2007-2023 Andreas Steffen * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen * * Copyright (C) secunet Security Networks AG @@ -31,6 +31,7 @@ #include #include #include +#include /** * how long do we use an OCSP response without a nextUpdate @@ -73,6 +74,11 @@ struct private_x509_ocsp_response_t { */ chunk_t signature; + /** + * OCSP response status + */ + ocsp_status_t ocsp_status; + /** * name or keyid of the responder */ @@ -103,36 +109,22 @@ struct private_x509_ocsp_response_t { */ chunk_t nonce; + /** + * Signer certificate, included in response + */ + certificate_t *cert; + + /** + * Signer private key to sign response + */ + private_key_t *key; + /** * reference counter */ refcount_t ref; }; -/** - * single response contained in OCSP response - */ -typedef struct { - /** hash algorithm OID to for the two hashes */ - int hashAlgorithm; - /** hash of issuer DN */ - chunk_t issuerNameHash; - /** issuerKeyID */ - chunk_t issuerKeyHash; - /** serial number of certificate */ - chunk_t serialNumber; - /** OCSP certificate status */ - cert_validation_t status; - /** time of revocation, if revoked */ - time_t revocationTime; - /** revocation reason, if revoked */ - crl_reason_t revocationReason; - /** creation of associated CRL */ - time_t thisUpdate; - /** creation of next CRL */ - time_t nextUpdate; -} single_response_t; - /* our OCSP response version implementation */ #define OCSP_BASIC_RESPONSE_VERSION 1 @@ -142,7 +134,7 @@ METHOD(ocsp_response_t, get_status, cert_validation_t, time_t *this_update, time_t *next_update) { enumerator_t *enumerator; - single_response_t *response; + ocsp_single_response_t *response; cert_validation_t status = VALIDATION_FAILED; certificate_t *issuercert = &issuer->interface; @@ -232,7 +224,7 @@ METHOD(ocsp_response_t, create_cert_enumerator, enumerator_t*, CALLBACK(filter, bool, void *data, enumerator_t *orig, va_list args) { - single_response_t *response; + ocsp_single_response_t *response; cert_validation_t *status; crl_reason_t *revocationReason; chunk_t *serialNumber; @@ -277,6 +269,54 @@ METHOD(ocsp_response_t, get_nonce, chunk_t, return this->nonce; } +/** + * Build singleResponse + */ +static chunk_t build_singleResponse(private_x509_ocsp_response_t *this, + ocsp_single_response_t *response) +{ + chunk_t certID, certStatus, nextUpdate = chunk_empty; + + certID = asn1_wrap(ASN1_SEQUENCE, "mmmm", + asn1_algorithmIdentifier( + hasher_algorithm_to_oid(response->hashAlgorithm)), + asn1_simple_object(ASN1_OCTET_STRING, response->issuerNameHash), + asn1_simple_object(ASN1_OCTET_STRING, response->issuerKeyHash), + asn1_integer("c", response->serialNumber)); + + switch (response->status) + { + case VALIDATION_GOOD: + certStatus = asn1_wrap(ASN1_CONTEXT_S_0, "c", chunk_empty); + break; + case VALIDATION_REVOKED: + case VALIDATION_ON_HOLD: + certStatus = asn1_wrap(ASN1_CONTEXT_C_1, "mm", + asn1_from_time(&response->revocationTime, + ASN1_GENERALIZEDTIME), + asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_simple_object(ASN1_ENUMERATED, + chunk_from_chars(response->revocationReason)))); + break; + case VALIDATION_FAILED: + default: + certStatus = asn1_wrap(ASN1_CONTEXT_S_2, "c", chunk_empty); + } + + if (response->nextUpdate != 0) + { + nextUpdate = asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_from_time(&response->nextUpdate, + ASN1_GENERALIZEDTIME)); + } + + return asn1_wrap(ASN1_SEQUENCE, "mmmm", + certID, + certStatus, + asn1_from_time(&response->thisUpdate, ASN1_GENERALIZEDTIME), + nextUpdate); +} + /** * ASN.1 definition of singleResponse */ @@ -312,6 +352,7 @@ static const asn1Object_t singleResponseObjects[] = { { 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 @@ -338,17 +379,10 @@ static bool parse_singleResponse(private_x509_ocsp_response_t *this, int objectID; bool success = FALSE; - single_response_t *response; + ocsp_single_response_t *response; + + response = ocsp_single_response_create(); - response = malloc_thing(single_response_t); - response->hashAlgorithm = OID_UNKNOWN; - response->issuerNameHash = chunk_empty; - response->issuerKeyHash = chunk_empty; - response->serialNumber = chunk_empty; - response->status = VALIDATION_FAILED; - response->revocationTime = 0; - response->revocationReason = CRL_REASON_UNSPECIFIED; - response->thisUpdate = UNDEFINED_TIME; /* if nextUpdate is missing, we give it a short lifetime */ response->nextUpdate = this->producedAt + OCSP_DEFAULT_LIFETIME; @@ -364,13 +398,13 @@ static bool parse_singleResponse(private_x509_ocsp_response_t *this, parser->get_level(parser)+1, NULL); break; case SINGLE_RESPONSE_ISSUER_NAME_HASH: - response->issuerNameHash = object; + response->issuerNameHash = chunk_clone(object); break; case SINGLE_RESPONSE_ISSUER_KEY_HASH: - response->issuerKeyHash = object; + response->issuerKeyHash = chunk_clone(object); break; case SINGLE_RESPONSE_SERIAL_NUMBER: - response->serialNumber = chunk_skip_zero(object); + response->serialNumber = chunk_clone(chunk_skip_zero(object)); break; case SINGLE_RESPONSE_CERT_STATUS_GOOD: response->status = VALIDATION_GOOD; @@ -414,11 +448,31 @@ static bool parse_singleResponse(private_x509_ocsp_response_t *this, } else { - free(response); + response->destroy(response); } return success; } +/** + * Build responses + */ +static chunk_t build_responses(private_x509_ocsp_response_t *this) +{ + ocsp_single_response_t *response; + chunk_t responses = chunk_empty, single_response; + enumerator_t *enumerator; + + enumerator = this->responses->create_enumerator(this->responses); + while (enumerator->enumerate(enumerator, &response)) + { + single_response = build_singleResponse(this, response); + responses = chunk_cat("mm", responses, single_response); + } + enumerator->destroy(enumerator); + + return asn1_wrap(ASN1_SEQUENCE, "m", responses); +} + /** * ASN.1 definition of responses */ @@ -466,6 +520,94 @@ end: return success; } +/** + * Build tbsResponseData + */ +static chunk_t build_tbsResponseData(private_x509_ocsp_response_t *this) +{ + chunk_t responderIdByName; + chunk_t responseExtensions = chunk_empty; + + responderIdByName = asn1_wrap(ASN1_CONTEXT_C_1, "c", + this->responderId->get_encoding(this->responderId)); + + this->producedAt = time(NULL); + + responseExtensions = asn1_wrap(ASN1_CONTEXT_C_1, "m", + asn1_wrap(ASN1_SEQUENCE, "m", + asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_build_known_oid(OID_NONCE), + asn1_wrap(ASN1_OCTET_STRING, "m", + asn1_simple_object(ASN1_OCTET_STRING, + this->nonce))))); + + return asn1_wrap(ASN1_SEQUENCE, "mmmm", + responderIdByName, + asn1_from_time(&this->producedAt, ASN1_GENERALIZEDTIME), + build_responses(this), + responseExtensions); +} + +/** + * Build the signature + */ +static bool build_signature(private_x509_ocsp_response_t *this, + chunk_t tbsResponseData, chunk_t *signature) +{ + if (!this->key->sign(this->key, this->scheme->scheme, this->scheme->params, + tbsResponseData, signature)) + { + DBG1(DBG_LIB, "creating OCSP response signature failed"); + return FALSE; + } + return TRUE; +} + +/** + * Build the basicOCSPResponse + */ +static bool build_basicOCSPResponse(private_x509_ocsp_response_t *this, + chunk_t *basicResponse) +{ + chunk_t tbsResponseData, sig_scheme, signature; + chunk_t cert_encoding, certs = chunk_empty; + x509_t *x509 = (x509_t*)this->cert; + + *basicResponse = chunk_empty; + + if (!signature_params_build(this->scheme, &sig_scheme)) + { + return FALSE; + } + tbsResponseData = build_tbsResponseData(this); + + if (!build_signature(this, tbsResponseData, &signature)) + { + free(tbsResponseData.ptr); + free(sig_scheme.ptr); + return FALSE; + } + + /* don't include self-signed signer certificates */ + if (!(x509->get_flags(x509) & X509_SELF_SIGNED)) + { + if (!this->cert->get_encoding(this->cert, CERT_ASN1_DER, &cert_encoding)) + { + free(tbsResponseData.ptr); + free(sig_scheme.ptr); + free(signature.ptr); + return FALSE; + } + certs = asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_wrap(ASN1_SEQUENCE, "m", cert_encoding)); + } + + *basicResponse = asn1_wrap(ASN1_SEQUENCE, "mmmm", + tbsResponseData, sig_scheme, + asn1_bitstring("m", signature), certs); + return TRUE; +} + /** * ASN.1 definition of basicResponse */ @@ -580,7 +722,7 @@ static bool parse_basicOCSPResponse(private_x509_ocsp_response_t *this, asn1_parse_simple_object(&object, ASN1_OCTET_STRING, parser->get_level(parser)+1, "nonce")) { - this->nonce = object; + this->nonce = chunk_clone(object); } break; case BASIC_RESPONSE_ALGORITHM: @@ -624,6 +766,31 @@ end: return success; } +/** + * Build the OCSPResponse + * + */ +static chunk_t build_OCSPResponse(private_x509_ocsp_response_t *this) +{ + chunk_t response, responseBytes = chunk_empty; + + if (this->ocsp_status == OCSP_SUCCESSFUL) + { + if (!build_basicOCSPResponse(this, &response)) + { + return chunk_empty; + } + responseBytes = asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_build_known_oid(OID_BASIC), + asn1_wrap(ASN1_OCTET_STRING, "m", response))); + } + return asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_simple_object(ASN1_ENUMERATED, + chunk_from_chars(this->ocsp_status)), + responseBytes); +} + /** * ASN.1 definition of ocspResponse */ @@ -651,7 +818,6 @@ static bool parse_OCSPResponse(private_x509_ocsp_response_t *this) int objectID; int responseType = OID_UNKNOWN; bool success = FALSE; - ocsp_status_t status; parser = asn1_parser_create(ocspResponseObjects, this->encoding); @@ -660,15 +826,16 @@ static bool parse_OCSPResponse(private_x509_ocsp_response_t *this) switch (objectID) { case OCSP_RESPONSE_STATUS: - status = (ocsp_status_t)*object.ptr; - switch (status) + this->ocsp_status = (ocsp_status_t)*object.ptr; + switch (this->ocsp_status) { case OCSP_SUCCESSFUL: break; default: DBG1(DBG_LIB, " ocsp response status: %N", - ocsp_status_names, status); - goto end; + ocsp_status_names, this->ocsp_status); + success = TRUE; + break; } break; case OCSP_RESPONSE_TYPE: @@ -845,19 +1012,24 @@ METHOD(certificate_t, destroy, void, { if (ref_put(&this->ref)) { - this->certs->destroy_offset(this->certs, offsetof(certificate_t, destroy)); - this->responses->destroy_function(this->responses, free); - signature_params_destroy(this->scheme); + this->certs->destroy_offset(this->certs, + offsetof(certificate_t, destroy)); + this->responses->destroy_offset(this->responses, + offsetof(ocsp_single_response_t, destroy)); + DESTROY_IF(this->cert); + DESTROY_IF(this->key); DESTROY_IF(this->responderId); + signature_params_destroy(this->scheme); + free(this->nonce.ptr); free(this->encoding.ptr); free(this); } } /** - * load an OCSP response + * create an empty but initialized OCSP response */ -static x509_ocsp_response_t *load(chunk_t blob) +static private_x509_ocsp_response_t *create_empty() { private_x509_ocsp_response_t *this; @@ -885,13 +1057,118 @@ static x509_ocsp_response_t *load(chunk_t blob) }, }, .ref = 1, - .encoding = chunk_clone(blob), .producedAt = UNDEFINED_TIME, .usableUntil = UNDEFINED_TIME, .responses = linked_list_create(), .certs = linked_list_create(), ); + return this; +} + +/** + * See header. + */ +x509_ocsp_response_t *x509_ocsp_response_gen(certificate_type_t type, va_list args) +{ + private_x509_ocsp_response_t *this; + private_key_t *private; + certificate_t *cert; + chunk_t nonce; + identification_t *subject; + enumerator_t *enumerator; + ocsp_single_response_t *response; + + this = create_empty(); + + while (TRUE) + { + switch (va_arg(args, builder_part_t)) + { + case BUILD_OCSP_STATUS: + this->ocsp_status = va_arg(args, ocsp_status_t); + continue; + case BUILD_OCSP_RESPONSES: + enumerator = va_arg(args, enumerator_t*); + while (enumerator->enumerate(enumerator, &response)) + { + this->responses->insert_last(this->responses, + response->get_ref(response)); + } + continue; + case BUILD_SIGNING_CERT: + cert = va_arg(args, certificate_t*); + if (cert) + { + subject = cert->get_subject(cert); + this->cert = cert->get_ref(cert); + this->responderId = subject->clone(subject); + } + continue; + case BUILD_SIGNING_KEY: + private = va_arg(args, private_key_t*); + if (private) + { + this->key = private->get_ref(private); + } + continue; + case BUILD_SIGNATURE_SCHEME: + this->scheme = va_arg(args, signature_params_t*); + this->scheme = signature_params_clone(this->scheme); + continue; + case BUILD_NONCE: + nonce = va_arg(args, chunk_t); + this->nonce = chunk_clone(nonce); + continue; + case BUILD_END: + break; + default: + goto error; + } + break; + } + + if (this->ocsp_status == OCSP_SUCCESSFUL) + { + if (!this->key) + { + DBG1(DBG_LIB, "no OCSP signing key defined"); + goto error; + } + + /* select signature scheme, if not already specified */ + if (!this->scheme) + { + INIT(this->scheme, + .scheme = signature_scheme_from_oid( + hasher_signature_algorithm_to_oid(HASH_SHA256, + this->key->get_type(this->key))), + ); + } + if (this->scheme->scheme == SIGN_UNKNOWN) + { + goto error; + } + } + + this->encoding = build_OCSPResponse(this); + return &this->public; + +error: + destroy(this); + return NULL; +} + +/** + * load an OCSP response + */ +static x509_ocsp_response_t *load(chunk_t blob) +{ + private_x509_ocsp_response_t *this; + + this = create_empty(); + this->encoding = chunk_clone(blob); + if (!parse_OCSPResponse(this)) { destroy(this); diff --git a/src/libstrongswan/plugins/x509/x509_ocsp_response.h b/src/libstrongswan/plugins/x509/x509_ocsp_response.h index 9c66f6004..f07c1cacc 100644 --- a/src/libstrongswan/plugins/x509/x509_ocsp_response.h +++ b/src/libstrongswan/plugins/x509/x509_ocsp_response.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2009 Martin Willi + * Copyright (C) 2023 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -38,6 +39,23 @@ struct x509_ocsp_response_t { ocsp_response_t interface; }; +/** + * Generate a X.509 OCSP response. + * + * The resulting builder accepts: + * BUILD_OCSP_STATUS: status from OCSP respnder + * BUILD_OCSP_RESPONSES: enumerator over the list of OCSP single responses + * BUILD_NONCE: nonce extracted from the OCSP request + * BUILD_SIGNING_CERT: certificate to create OCSP response signature + * BUILD_SIGNING_KEY: private key to create OCSP response signature + * BUILD_SIGNATURE_SCHEME: scheme used for the OCSP response signature + * + * @param type certificate type, CERT_X509_OCSP_REQUEST only + * @param args builder_part_t argument list + * @return OCSP request, NULL on failure + */ +x509_ocsp_response_t *x509_ocsp_response_gen(certificate_type_t type, va_list args); + /** * Load a X.509 OCSP response. * diff --git a/src/libstrongswan/plugins/x509/x509_plugin.c b/src/libstrongswan/plugins/x509/x509_plugin.c index 0570e7a31..ca7922da8 100644 --- a/src/libstrongswan/plugins/x509/x509_plugin.c +++ b/src/libstrongswan/plugins/x509/x509_plugin.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2009 Martin Willi + * Copyright (C) 2023 Andreas Steffen, strongSec GmbH * * Copyright (C) secunet Security Networks AG * @@ -69,6 +70,10 @@ METHOD(plugin_t, get_features, int, PLUGIN_PROVIDE(CERT_ENCODE, CERT_X509_OCSP_REQUEST), PLUGIN_DEPENDS(HASHER, HASH_SHA1), PLUGIN_DEPENDS(RNG, RNG_WEAK), + PLUGIN_REGISTER(CERT_DECODE, x509_ocsp_request_load, TRUE), + PLUGIN_PROVIDE(CERT_DECODE, CERT_X509_OCSP_REQUEST), + PLUGIN_REGISTER(CERT_ENCODE, x509_ocsp_response_gen, FALSE), + PLUGIN_PROVIDE(CERT_ENCODE, CERT_X509_OCSP_RESPONSE), PLUGIN_REGISTER(CERT_DECODE, x509_ocsp_response_load, TRUE), PLUGIN_PROVIDE(CERT_DECODE, CERT_X509_OCSP_RESPONSE), diff --git a/src/libstrongswan/tests/suites/test_chunk.c b/src/libstrongswan/tests/suites/test_chunk.c index 7328a2caa..d3e1bd7f6 100644 --- a/src/libstrongswan/tests/suites/test_chunk.c +++ b/src/libstrongswan/tests/suites/test_chunk.c @@ -569,6 +569,57 @@ START_TEST(test_base32) } END_TEST +/******************************************************************************* + * DEC encoding test + */ + +START_TEST(test_dec) +{ + typedef struct { + chunk_t in; + char *out; + } testdata_t; + + testdata_t test[] = { + { chunk_from_chars( 0x00), "0" }, + { chunk_from_chars( 0x09), "9" }, + { chunk_from_chars( 0x0a), "10" }, + { chunk_from_chars( 0x13), "19" }, + { chunk_from_chars( 0x14), "20" }, + { chunk_from_chars( 0x63), "99" }, + { chunk_from_chars( 0x64), "100" }, + { chunk_from_chars( 0x65), "101" }, + { chunk_from_chars( 0xff), "255" }, + { chunk_from_chars( 0x00, 0xff), "255" }, + { chunk_from_chars( 0x01, 0x00), "256" }, + { chunk_from_chars( 0x01, 0x03), "259" }, + { chunk_from_chars( 0x01, 0x04), "260" }, + { chunk_from_chars( 0x09, 0xff), "2559" }, + { chunk_from_chars( 0x0a, 0x00), "2560" }, + { chunk_from_chars( 0x0a, 0x01), "2561" }, + { chunk_from_chars( 0xff, 0xff), "65535" }, + { chunk_from_chars(0x00, 0xff, 0xff), "65535" }, + { chunk_from_chars(0x01, 0x00, 0x00), "65536" }, + { chunk_from_chars(0x01, 0x86, 0x9f), "99999" }, + { chunk_from_chars(0x01, 0x86, 0xa0), "100000" }, + { chunk_from_chars(0x0f, 0x42, 0x40), "1000000" }, + { chunk_from_chars(0xa9, 0x8a, 0xc7), "11111111" }, + { chunk_from_chars(0xbc, 0x61, 0x4e), "12345678" }, + }; + + int i; + + for (i = 0; i < countof(test); i++) + { + char buf[10]; + chunk_t out; + + out = chunk_to_dec(test[i].in, buf); + ck_assert_str_eq(out.ptr, test[i].out); + } +} +END_TEST + /******************************************************************************* * chunk_increment test */ @@ -1190,6 +1241,7 @@ Suite *chunk_suite_create() tcase_add_test(tc, test_base64); tcase_add_test(tc, test_base32); tcase_add_test(tc, test_base16); + tcase_add_test(tc, test_dec); suite_add_tcase(s, tc); tc = tcase_create("chunk_mac"); diff --git a/src/libstrongswan/tests/suites/test_serial_gen.c b/src/libstrongswan/tests/suites/test_serial_gen.c index 65846fd85..696396175 100644 --- a/src/libstrongswan/tests/suites/test_serial_gen.c +++ b/src/libstrongswan/tests/suites/test_serial_gen.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Andreas Steffen, strongSec GmbH + * Copyright (C) 2022-2023 Andreas Steffen, strongSec GmbH * * Copyright (C) secunet Security Networks AG * @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include #include @@ -182,6 +184,83 @@ static certificate_t* create_ocsp_request(certificate_t *cert) return ocsp_req; } +/** + * Parse an ASN.1 encoded OCSP request + */ +static certificate_t* parse_ocsp_request(chunk_t encoding) +{ + certificate_t *ocsp_req; + + ocsp_req = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST, + BUILD_BLOB_ASN1_DER, encoding, + BUILD_END); + ck_assert(ocsp_req); + + return ocsp_req; +} + +/** + * Create an OCSP response based on an OCSP request + */ +static certificate_t* create_ocsp_response(ocsp_request_t *ocsp_request, + cert_validation_t status, + certificate_t *cert) +{ + private_key_t *privkey; + certificate_t *ocsp_rsp; + ocsp_status_t ocsp_status = OCSP_SUCCESSFUL; + ocsp_single_response_t *response; + chunk_t issuerNameHash, issuerKeyHash, serialNumber, nonce; + hash_algorithm_t hashAlgorithm; + linked_list_t *responses = linked_list_create(); + enumerator_t *enumerator; + + privkey = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, + BUILD_BLOB_ASN1_DER, chunk_from_thing(keydata), + BUILD_END); + ck_assert(privkey); + + /* generate OCSP single response */ + enumerator = ocsp_request->create_request_enumerator(ocsp_request); + ck_assert(enumerator->enumerate(enumerator, &hashAlgorithm, &issuerNameHash, + &issuerKeyHash, &serialNumber)); + 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); + response->status = status; + if (status == VALIDATION_REVOKED) + { + response->revocationReason = CRL_REASON_KEY_COMPROMISE; + response->revocationTime = time(NULL); + } + responses->insert_last(responses, response); + enumerator->destroy(enumerator); + + nonce = ocsp_request->get_nonce(ocsp_request); + + /* generate OCSP response */ + enumerator = responses->create_enumerator(responses); + ocsp_rsp = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_X509_OCSP_RESPONSE, + BUILD_OCSP_STATUS, ocsp_status, + BUILD_OCSP_RESPONSES, enumerator, + BUILD_SIGNING_KEY, privkey, + BUILD_SIGNING_CERT, cert, + BUILD_NONCE, nonce, + BUILD_END); + enumerator->destroy(enumerator); + + ck_assert(ocsp_rsp); + + privkey->destroy(privkey); + responses->destroy_offset(responses, offsetof(ocsp_single_response_t, destroy)); + + return ocsp_rsp; +} + /** * Parse an ASN.1 encoded OCSP response */ @@ -290,72 +369,22 @@ static serial_number_t serial_numbers[] = { chunk_from_chars(0x05,0x00,0xff,0xff,0xff,0xff) }, }; -static chunk_t ocsp_responses[] = { - chunk_from_chars( - 0x30,0x82,0x01,0x85,0x0a,0x01,0x00,0xa0,0x82,0x01,0x7e,0x30,0x82,0x01,0x7a,0x06, - 0x09,0x2b,0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x01,0x04,0x82,0x01,0x6b,0x30,0x82, - 0x01,0x67,0x30,0x81,0xd1,0xa1,0x33,0x30,0x31,0x31,0x0b,0x30,0x09,0x06,0x03,0x55, - 0x04,0x06,0x13,0x02,0x43,0x48,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x13, - 0x0a,0x73,0x74,0x72,0x6f,0x6e,0x67,0x53,0x77,0x61,0x6e,0x31,0x0d,0x30,0x0b,0x06, - 0x03,0x55,0x04,0x03,0x13,0x04,0x74,0x65,0x73,0x74,0x18,0x0f,0x32,0x30,0x32,0x32, - 0x31,0x31,0x32,0x32,0x30,0x39,0x31,0x36,0x34,0x37,0x5a,0x30,0x64,0x30,0x62,0x30, - 0x3a,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14,0xbd,0x25, - 0xa0,0xdf,0xc3,0x21,0xf2,0xd8,0xed,0x19,0x63,0x0a,0x4b,0x90,0x6d,0xc3,0x0f,0xe7, - 0x79,0x20,0x04,0x14,0xe2,0x6d,0x1e,0xdf,0x83,0x8e,0xa2,0x1f,0xc3,0x00,0xdd,0x44, - 0x6f,0x8a,0x4d,0x70,0x0c,0x02,0xe3,0x1f,0x02,0x01,0x7f,0x80,0x00,0x18,0x0f,0x32, - 0x30,0x32,0x32,0x31,0x31,0x32,0x32,0x30,0x39,0x31,0x36,0x34,0x37,0x5a,0xa0,0x11, - 0x18,0x0f,0x32,0x30,0x32,0x32,0x31,0x31,0x32,0x32,0x31,0x39,0x31,0x36,0x34,0x37, - 0x5a,0xa1,0x23,0x30,0x21,0x30,0x1f,0x06,0x09,0x2b,0x06,0x01,0x05,0x05,0x07,0x30, - 0x01,0x02,0x04,0x12,0x04,0x10,0x86,0x45,0x82,0x11,0xe6,0x62,0x43,0x83,0xbc,0x01, - 0xe4,0x5c,0x48,0x87,0xcd,0x2e,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d, - 0x01,0x01,0x0b,0x05,0x00,0x03,0x81,0x81,0x00,0x42,0x32,0xa9,0x24,0x97,0x8c,0xc5, - 0x35,0x37,0xe7,0x14,0xf0,0x84,0x7e,0x69,0xf1,0x99,0xf8,0xf0,0x02,0x7d,0xe4,0xd8, - 0x25,0x78,0x65,0x86,0x40,0xf6,0x30,0xc1,0x50,0x57,0x16,0x13,0xe9,0xe5,0xbc,0xa9, - 0xbb,0x87,0xce,0xb8,0x0d,0x35,0x5d,0xad,0x68,0x3b,0x34,0x9f,0x82,0x2b,0xe5,0x1f, - 0xcc,0xd5,0x54,0x8a,0xe3,0xd7,0xed,0xc9,0x7d,0xb6,0x50,0xd2,0xcb,0xc2,0xff,0x03, - 0x24,0x8c,0xcf,0x49,0x40,0xd4,0x7f,0xcb,0xc0,0x20,0x75,0x78,0x45,0xb8,0x50,0x3c, - 0x84,0xdd,0xdc,0xb7,0xfc,0xcd,0x64,0xc3,0x81,0xc6,0xb6,0xcd,0xc5,0xe9,0xc4,0x70, - 0x31,0x30,0x7c,0xff,0x93,0xc3,0x9d,0x55,0x7b,0x32,0x77,0x53,0x07,0x45,0xc2,0x80, - 0x7b,0x9b,0xfb,0x0e,0x45,0x27,0xf2,0xc5,0x16), - chunk_from_chars( - 0x30,0x82,0x01,0x9c,0x0a,0x01,0x00,0xa0,0x82,0x01,0x95,0x30,0x82,0x01,0x91,0x06, - 0x09,0x2b,0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x01,0x04,0x82,0x01,0x82,0x30,0x82, - 0x01,0x7e,0x30,0x81,0xe8,0xa1,0x33,0x30,0x31,0x31,0x0b,0x30,0x09,0x06,0x03,0x55, - 0x04,0x06,0x13,0x02,0x43,0x48,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x13, - 0x0a,0x73,0x74,0x72,0x6f,0x6e,0x67,0x53,0x77,0x61,0x6e,0x31,0x0d,0x30,0x0b,0x06, - 0x03,0x55,0x04,0x03,0x13,0x04,0x74,0x65,0x73,0x74,0x18,0x0f,0x32,0x30,0x32,0x32, - 0x31,0x31,0x32,0x32,0x30,0x39,0x32,0x32,0x32,0x34,0x5a,0x30,0x7b,0x30,0x79,0x30, - 0x3b,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14,0xbd,0x25, - 0xa0,0xdf,0xc3,0x21,0xf2,0xd8,0xed,0x19,0x63,0x0a,0x4b,0x90,0x6d,0xc3,0x0f,0xe7, - 0x79,0x20,0x04,0x14,0xe2,0x6d,0x1e,0xdf,0x83,0x8e,0xa2,0x1f,0xc3,0x00,0xdd,0x44, - 0x6f,0x8a,0x4d,0x70,0x0c,0x02,0xe3,0x1f,0x02,0x02,0x00,0x80,0xa1,0x16,0x18,0x0f, - 0x32,0x30,0x32,0x32,0x31,0x31,0x31,0x35,0x31,0x38,0x34,0x30,0x35,0x34,0x5a,0xa0, - 0x03,0x0a,0x01,0x01,0x18,0x0f,0x32,0x30,0x32,0x32,0x31,0x31,0x32,0x32,0x30,0x39, - 0x32,0x32,0x32,0x34,0x5a,0xa0,0x11,0x18,0x0f,0x32,0x30,0x32,0x32,0x31,0x31,0x32, - 0x32,0x31,0x39,0x32,0x32,0x32,0x34,0x5a,0xa1,0x23,0x30,0x21,0x30,0x1f,0x06,0x09, - 0x2b,0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x02,0x04,0x12,0x04,0x10,0xf2,0x47,0xbd, - 0xd2,0xdd,0x6d,0x58,0xba,0xa4,0x6f,0xa5,0xed,0x31,0xb1,0x37,0x89,0x30,0x0d,0x06, - 0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0b,0x05,0x00,0x03,0x81,0x81,0x00, - 0x17,0x96,0x3b,0x5a,0x4d,0x4e,0x90,0x8a,0xdf,0xe9,0x2b,0x1c,0x48,0x15,0x5d,0x8e, - 0xed,0xf0,0xa6,0x42,0x3a,0x9c,0x71,0xd9,0x6a,0xa6,0xc1,0xfd,0xef,0xe8,0x0a,0xa1, - 0x61,0x46,0xe4,0x04,0x5c,0x64,0xaf,0x47,0x95,0xdd,0x4c,0xba,0x8e,0x53,0xf6,0x9b, - 0xbc,0x16,0xcb,0xeb,0xfe,0x80,0x6a,0x70,0x54,0x10,0x59,0x40,0x5b,0xa0,0x2b,0xb3, - 0x62,0x27,0x9e,0x5d,0xd2,0xd6,0x15,0x2a,0x9d,0xa3,0xb1,0xcb,0x44,0x09,0xd8,0x29, - 0xb5,0x55,0xd9,0x63,0x86,0xd5,0xb3,0x3c,0x4b,0x78,0x14,0x5a,0x27,0x37,0x3a,0x28, - 0xd8,0xae,0x69,0x51,0x2e,0x7d,0xf1,0x06,0xc1,0xac,0x4e,0x5d,0x25,0x7a,0xd2,0xf4, - 0x41,0xfd,0x9f,0xbf,0x05,0xc1,0x70,0xa5,0x3f,0x7a,0x53,0x06,0x85,0x7b,0xeb,0x94) -}; START_TEST(test_gen_serial_numbers) { chunk_t encoding, serial, serial_asn1; - certificate_t *cert, *crl, *ocsp_req, *acert, *acert1; - enumerator_t *enumerator; + certificate_t *cert, *crl, *ocsp_req, *ocsp_rsp, *acert, *acert1; + time_t revocation_time, this_update, next_update; + ocsp_request_t *ocsp_request; + ocsp_response_t *ocsp_resp; + cert_validation_t status; + crl_reason_t revocation_reason; crl_t *x509_crl; x509_t *x509; ac_t *ac; size_t offset; u_char *pos; + enumerator_t *enumerator; /** * Use serial number with canonical encoding (no leading zeroes) @@ -502,50 +531,92 @@ START_TEST(test_gen_serial_numbers) pos = encoding.ptr + 68; serial_asn1 = chunk_create(pos, 1 + *pos); ck_assert_chunk_eq(serial_asn1, serial_numbers[_i].serial_asn1); + ocsp_req->destroy(ocsp_req); + + /* parse ocsp request */ + ocsp_req = parse_ocsp_request(encoding); + ocsp_request = (ocsp_request_t*)ocsp_req; + + /* test ocsp request */ + enumerator = ocsp_request->create_request_enumerator(ocsp_request); + ck_assert(enumerator->enumerate(enumerator, NULL, NULL, NULL, &serial)); + ck_assert_chunk_eq(serial, serial_numbers[_i].serial); + enumerator->destroy(enumerator); chunk_free(&encoding); + /* create ocsp response */ + status = (_i % 2) ? VALIDATION_GOOD : VALIDATION_REVOKED; + ocsp_rsp = create_ocsp_response(ocsp_request, status, cert); + + /* the ASN.1 TLV (Type-Length-Value) encoding of an OCSP response is + * + * 0 "OCSPResponse", ASN1_SEQUENCE + * 1 "responseStatus", ASN1_ENUMERATED + * 1 "responseBytesContext", ASN1_CONTEXT_C_0 + * 2 "responseBytes", ASN1_SEQUENCE + * 3 "responseType", ASN1_OID + * 3 "response", ASN1_OCTET_STRING + * 4 "BasicOCSPResponse", ASN1_SEQUENCE + * 5 "tbsResponseData", ASN1_SEQUENCE + * 6 "responderIdContext", ASN1_CONTEXT_C_1 + * 6 "producedAt", ASN1_GENERALIZEDTIME + * 6 "responses", ASN1_SEQUENCE + * 7 "singleResponse", ASN1_SEQUENCE + * 8 "certID", ASN1_SEQUENCE + * 9 "algorithm", ASN1_SEQUENCE + * 9 "issuerNameHash", ASN1_OCTET_STRING + * 9 "issuerKeyHash", ASN1_OCTET_STRING + * 9 "serialNumber", ASN1_INTEGER + * + * The one octet length field of the serialNumber (8) is at + * pos = 4 (TL0) + 3 (TLV1) + 4 (TL1) + 4 (TL2) + 11 (TLV3) + 4 (TL3) + + 4 (TL4) + 3 (TL5) + 53 (TLV6) + 17 (TVL6) + 2 (TL6) + 2 (TL7) + + 2 (TL8) + 11 (TLV9) + 22 (TLV9) + 22 (TLV9) + 1 (T9) = 169 + */ + ck_assert(ocsp_rsp->get_encoding(ocsp_rsp, CERT_ASN1_DER, &encoding)); + DBG2(DBG_LIB, "ocsp response: %B", &encoding); + + /* check ASN.1 integer encoding of requested serial number */ + pos = encoding.ptr + 169; + serial_asn1 = chunk_create(pos, 1 + *pos); + ck_assert_chunk_eq(serial_asn1, serial_numbers[_i].serial_asn1); + ocsp_rsp->destroy(ocsp_rsp); + + /* parse ocsp response */ + ocsp_rsp = parse_ocsp_response(encoding); + ocsp_resp = (ocsp_response_t*)ocsp_rsp; + /* test ocsp response */ - if (_i == 2 || _i == 3) + ck_assert_chunk_eq(ocsp_request->get_nonce(ocsp_request), + ocsp_resp->get_nonce(ocsp_resp)); + + status = ocsp_resp->get_status(ocsp_resp, x509, x509, &revocation_time, + &revocation_reason, &this_update, &next_update); + if (_i % 2) { - certificate_t *ocsp_rsp; - ocsp_response_t *ocsp_resp; - cert_validation_t status; - crl_reason_t revocation_reason; - time_t revocation_time, this_update, next_update; - enumerator_t *enumerator; - - ocsp_rsp = parse_ocsp_response(ocsp_responses[_i-2]); - ocsp_resp = (ocsp_response_t*)ocsp_rsp; - - status = ocsp_resp->get_status(ocsp_resp, x509, x509, &revocation_time, - &revocation_reason, &this_update, &next_update); - if (_i == 2) - { - ck_assert(status == VALIDATION_GOOD); - } - else - { - ck_assert(status == VALIDATION_REVOKED); - ck_assert(revocation_reason == CRL_REASON_KEY_COMPROMISE); - } - - enumerator = ocsp_resp->create_response_enumerator(ocsp_resp); - ck_assert(enumerator->enumerate(enumerator, &serial, &status, - &revocation_time, &revocation_reason)); - ck_assert_chunk_eq(serial, serial_numbers[_i].serial); - if (_i == 2) - { - ck_assert(status == VALIDATION_GOOD); - } - else - { - ck_assert(status == VALIDATION_REVOKED); - ck_assert(revocation_reason == CRL_REASON_KEY_COMPROMISE); - } - enumerator->destroy(enumerator); - - ocsp_rsp->destroy(ocsp_rsp); + ck_assert(status == VALIDATION_GOOD); } + else + { + ck_assert(status == VALIDATION_REVOKED); + ck_assert(revocation_reason == CRL_REASON_KEY_COMPROMISE); + } + + enumerator = ocsp_resp->create_response_enumerator(ocsp_resp); + ck_assert(enumerator->enumerate(enumerator, &serial, &status, + &revocation_time, &revocation_reason)); + ck_assert_chunk_eq(serial, serial_numbers[_i].serial); + if (_i % 2) + { + ck_assert(status == VALIDATION_GOOD); + } + else + { + ck_assert(status == VALIDATION_REVOKED); + ck_assert(revocation_reason == CRL_REASON_KEY_COMPROMISE); + } + enumerator->destroy(enumerator); + chunk_free(&encoding); /* create attribute certificate */ acert = create_acert(serial_numbers[_i].serial, cert); @@ -633,6 +704,7 @@ START_TEST(test_gen_serial_numbers) cert->destroy(cert); crl->destroy(crl); ocsp_req->destroy(ocsp_req); + ocsp_rsp->destroy(ocsp_rsp); acert->destroy(acert); acert1->destroy(acert1); diff --git a/src/libstrongswan/utils/chunk.c b/src/libstrongswan/utils/chunk.c index 9150537ce..f40a379c5 100644 --- a/src/libstrongswan/utils/chunk.c +++ b/src/libstrongswan/utils/chunk.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2019 Tobias Brunner + * Copyright (C) 2023 Andreas Steffen * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -744,6 +745,71 @@ chunk_t chunk_to_base32(chunk_t chunk, char *buf) return chunk_create(buf, len * 8 / 5); } +/** + * Described in header. + */ +chunk_t chunk_to_dec(chunk_t chunk, char *buf) +{ + int len, i, i_buf, i_bin = 0; + uint16_t remainder; + chunk_t bin; + + /* Determine the number of needed decimal digits: + * 10^len > 2^(8*chunk.len) => + * len > log(256) * chunk.len => + * len > 2.4083 * chunk.len + */ + len = (int)(2.4083 * (double)chunk.len) + 1; + + if (!buf) + { + buf = malloc(len + 1); + } + i_buf = len; + buf[i_buf] = '\0'; + bin = chunk_clone(chunk); + while (i_bin < bin.len) + { + remainder = 0; + for (i = i_bin; i < bin.len; i++) + { + remainder = bin.ptr[i] + (remainder << 8); + if (remainder < 10) + { + remainder = bin.ptr[i]; + bin.ptr[i] = 0; + if (i == i_bin) + { + i_bin++; + } + } + else + { + bin.ptr[i] = remainder / 10; + remainder %= 10; + } + } + if (i_buf > 0) + { + buf[--i_buf] = 0x30 + remainder; + } + } + chunk_free(&bin); + + /* align decimal number to the start of the string */ + if (i_buf > 0) + { + len -= i_buf; + + for (i = 0; i <= len; i++) + { + buf[i] = buf[i + i_buf]; + } + } + + return chunk_create(buf, len); +} + /** * Described in header. */ diff --git a/src/libstrongswan/utils/chunk.h b/src/libstrongswan/utils/chunk.h index f667b9bdf..0ee38d27d 100644 --- a/src/libstrongswan/utils/chunk.h +++ b/src/libstrongswan/utils/chunk.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2019 Tobias Brunner + * Copyright (C) 2023 Andreas Steffen * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -213,6 +214,18 @@ chunk_t chunk_from_base64(chunk_t base64, char *buf); */ chunk_t chunk_to_base32(chunk_t chunk, char *buf); +/** + * Convert a chunk of data to decimal encoding. + * + * The resulting string is '\\0' terminated, but the chunk does not include + * the '\\0'. If buf is supplied, it must hold at least (chunk.len * 2.41 + 1). + * + * @param chunk data to convert to decimal encoding + * @param buf buffer to write to, NULL to malloc + * @return chunk of encoded data + */ +chunk_t chunk_to_dec(chunk_t chunk, char *buf); + /** * Free contents of a chunk */ diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index b4c05318a..1dbcd9a44 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -10,6 +10,7 @@ pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/gen.c \ commands/issue.c \ commands/keyid.c \ + commands/ocsp.c \ commands/pkcs12.c \ commands/pkcs7.c \ commands/print.c \ diff --git a/src/pki/command.h b/src/pki/command.h index d628a956f..f46b49de0 100644 --- a/src/pki/command.h +++ b/src/pki/command.h @@ -25,7 +25,7 @@ /** * Maximum number of commands (+1). */ -#define MAX_COMMANDS 18 +#define MAX_COMMANDS 19 /** * Maximum number of options in a command (+3) diff --git a/src/pki/commands/ocsp.c b/src/pki/commands/ocsp.c new file mode 100644 index 000000000..44fdb89d8 --- /dev/null +++ b/src/pki/commands/ocsp.c @@ -0,0 +1,601 @@ +/* + * 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)"}, + } + }); +} diff --git a/src/pki/commands/pkcs7.c b/src/pki/commands/pkcs7.c index 074beeab5..d9aa07942 100644 --- a/src/pki/commands/pkcs7.c +++ b/src/pki/commands/pkcs7.c @@ -288,7 +288,7 @@ static int pkcs7() hash_algorithm_t digest = HASH_UNKNOWN; signature_params_t *scheme = NULL; mem_cred_t *creds; - int res = 0; + int res = 1; FILE *in; enum { OP_NONE, @@ -450,7 +450,7 @@ static int pkcs7() case OP_DECRYPT: if (!key) { - fprintf(stderr, "decryption requires a private key\n"); + error = "decryption requires a private key"; break; } res = decrypt(data); diff --git a/src/pki/commands/print.c b/src/pki/commands/print.c index f482500e1..6d82af6b8 100644 --- a/src/pki/commands/print.c +++ b/src/pki/commands/print.c @@ -124,6 +124,16 @@ static int print() type = CRED_PRIVATE_KEY; subtype = KEY_BLISS; } + else if (streq(arg, "ocsp-req")) + { + type = CRED_CERTIFICATE; + subtype = CERT_X509_OCSP_REQUEST; + } + else if (streq(arg, "ocsp-rsp")) + { + type = CRED_CERTIFICATE; + subtype = CERT_X509_OCSP_RESPONSE; + } else { return command_usage( "invalid input type"); @@ -202,7 +212,7 @@ static void __attribute__ ((constructor))reg() { print, 'a', "print", "print a credential in a human readable form", {"[--in file|--keyid hex]", - "[--type x509|crl|ac|pub|priv|rsa|ecdsa|ed25519|ed448|bliss]"}, + "[--type x509|crl|ac|pub|priv|rsa|ecdsa|ed25519|ed448|bliss|ocsp-req|ocsp-rsp]"}, { {"help", 'h', 0, "show usage information"}, {"in", 'i', 1, "input file, default: stdin"}, diff --git a/src/pki/commands/signcrl.c b/src/pki/commands/signcrl.c index f854bdc87..6479b1827 100644 --- a/src/pki/commands/signcrl.c +++ b/src/pki/commands/signcrl.c @@ -240,7 +240,7 @@ static int sign_crl() } else if (streq(arg, "cessation-of-operation")) { - reason = CRL_REASON_CESSATION_OF_OPERATON; + reason = CRL_REASON_CESSATION_OF_OPERATION; } else if (streq(arg, "certificate-hold")) { diff --git a/src/pki/man/Makefile.am b/src/pki/man/Makefile.am index f220f39f4..3f64ab815 100644 --- a/src/pki/man/Makefile.am +++ b/src/pki/man/Makefile.am @@ -7,6 +7,7 @@ man1_MANS = \ pki---gen.1 \ pki---issue.1 \ pki---keyid.1 \ + pki---ocsp.1 \ pki---pkcs7.1 \ pki---print.1 \ pki---pub.1 \ diff --git a/src/pki/man/pki---ocsp.1.in b/src/pki/man/pki---ocsp.1.in new file mode 100644 index 000000000..23d4c7de4 --- /dev/null +++ b/src/pki/man/pki---ocsp.1.in @@ -0,0 +1,247 @@ +.TH "PKI \-\-OCSP" 1 "2023-10-29" "@PACKAGE_VERSION@" "strongSwan" +. +.SH "NAME" +. +pki \-\-ocsp \- OCSP request parser and OCSP responder. +. +.SH "SYNOPSIS" +. +.SY pki\ \-\-ocsp +.OP \-\-in file +.OP \-\-cacert file +.OP \-\-debug level +.YS + +.SY pki\ \-\-ocsp +.BI \-\-respond +.OP \-\-in file +.BI \-\-cacert\~ file +.BI \-\-key\~ file +.OP \-\-cert file +.OP \-\-lifetime minutes +.OP \-\-digest digest +.OP \-\-rsa\-padding padding +.OP \-\-debug level +.YS +. +.SY pki\ \-\-ocsp +.BI \-\-options\~ file +.YS +. +.SY "pki \-\-ocsp" +.B \-h +| +.B \-\-help +.YS +. +.SH "DESCRIPTION" +. +This sub-command of +.BR pki (1) +parses an +.B Online Certificate Status Protocol +(OCSP) request as defined by RFC 6960 and with the +.B --respond +option generates an OCSP response based on the OCSP request. +The certificate status is directly retrieved from the internal +.B certificate +database of an +.B OpenXPKI +(https://openxpki.org) server. The +.B --respond +option requires the +.B openxpki +and +.B mysql +libstrongswan plugins in order to access the +.B certificate +database of the +.B OpenXPKI +server running on the same host. +. +.SH "OPTIONS" +. +.TP +.B "\-h, \-\-help" +Print usage information with a summary of the available options. +.TP +.BI "\-v, \-\-debug " level +Set debug level, default: 1. +.TP +.BI "\-+, \-\-options " file +Read command line options from \fIfile\fR. +.TP +.BI "\-i, \-\-in " file +OCSP request. If not given, the OCSP request is read from +\fISTDIN\fR. +.TP +.BI "\-C, \-\-cacert " file +CA certificate corresponding to one of the issuer hashes contained in the OCSP +request. If the OCSP request is signed, a CA certificate forming the +trust chain. Can be used multiple times. +.TP +.BI "\-k, \-\-key " file +OCSP signer key. Can be used multiple times. +.TP +.BI "\-c, \-\-cert " file +OCSP signer certificate (if it is not a CA certificate). Can be used +multiple times. +.TP +.BI "\-l, \-\-lifetime " minutes +Validity in minutes of the OCSP response (if missing, nextUpdate is omitted). +.TP +.BI "\-g, \-\-digest " digest +Digest to use for signature creation. One of \fImd5\fR, \fIsha1\fR, +\fIsha224\fR, \fIsha256\fR, \fIsha384\fR, or \fIsha512\fR, \fIsha3_224\fR, +\fIsha3_256\fR, \fIsha3_384\fR, \fIsha3_512\fR. The default is +determined based on the type and size of the ocsp signing key. +.TP +.BI "\-R, \-\-rsa\-padding " padding +Padding to use for RSA signatures. Either \fIpkcs1\fR or \fIpss\fR, defaults +to \fIpkcs1\fR. +. +.SH "EXAMPLES" +. +Show the raw content of an OCSP request: +.PP +.EX +pki \-\-ocsp \-\-in req_ca.der + +nonce: 5b:14:e3:cc:d5:b2:65:ec:c4:0d:c3:11:37:6a:9d:71 + issuerKeyHash: b6:76:79:95:b5:58:..:06:93:f3:39:79:19 (no match) + issuerNameHash: af:25:78:ce:fc:15:..:67:95:81:31:a3:4d (no match) + serialNumber: 4f:33:21:1d:4d:fd:9b:db + issuerKeyHash: b6:76:79:95:b5:58:..:06:93:f3:39:79:19 (no match) + issuerNameHash: af:25:78:ce:fc:15:..:67:95:81:31:a3:4d (no match) + serialNumber: 68:f2:93:10:65:d0:5e:d1 +.EE +.PP +Show the content of the same OCSP request if the issuer certificate is given: +.PP +.EX +pki \-\-ocsp \-\-in req_ca.der \-\-cacert cacert.pem + +nonce: 5b:14:e3:cc:d5:b2:65:ec:c4:0d:c3:11:37:6a:9d:71 +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + issuerKeyHash: b6:76:79:95:b5:58:..:06:93:f3:39:79:19 (ok) + issuerNameHash: af:25:78:ce:fc:15:..:67:95:81:31:a3:4d (ok) + serialNumber: 4f:33:21:1d:4d:fd:9b:db +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + issuerKeyHash: b6:76:79:95:b5:58:..:06:93:f3:39:79:19 (ok) + issuerNameHash: af:25:78:ce:fc:15:..:67:95:81:31:a3:4d (ok) + serialNumber: 68:f2:93:10:65:d0:5e:d1 +.EE +.PP +Respond to the OCSP request above, with the OCSP response signed by the CA itself: +.PP +.EX +pki \-\-ocsp \-\-respond \-\-in req_ca.der \-\-cacert cacert.pem \-\-key cakey.pem \\ + \-\-lifetime 10 > rsp_ca.der + +nonce: 5b:14:e3:cc:d5:b2:65:ec:c4:0d:c3:11:37:6a:9d:71 +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + issuerKeyHash: b6:76:79:95:b5:58:..:06:93:f3:39:79:19 (ok) + issuerNameHash: af:25:78:ce:fc:15:..:67:95:81:31:a3:4d (ok) + serialNumber: 4f:33:21:1d:4d:fd:9b:db + thisUpdate: Oct 19 15:54:15 UTC 2023 + nextUpdate: Oct 19 16:04:15 UTC 2023 + certValidation: GOOD +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + issuerKeyHash: b6:76:79:95:b5:58:..:06:93:f3:39:79:19 (ok) + issuerNameHash: af:25:78:ce:fc:15:..:67:95:81:31:a3:4d (ok) + serialNumber: 68:f2:93:10:65:d0:5e:d1 + thisUpdate: Oct 19 15:54:15 UTC 2023 + nextUpdate: Oct 19 16:04:15 UTC 2023 + certValidation: GOOD +trusted signer: "C=CH, O=strongSwan Project, CN=strongSwan Root CA" +ocspResponseStatus: successful +.EE +.PP +Respond to a signed OCSP request providing the complete trust chain: +.PP +.EX +pki \-\-ocsp --respond --in req_signed.der --cacert cacert.pem --cacert issuer1.pem \\ + \-\-key signerKey1.pem \-\-cert signerCert1.pem \-\-lifetime 10 > rsp_signed.der + +requestor: "C=CH, O=strongSwan Project, CN=vpn.strongswan.org" + using certificate "C=CH, O=strongSwan Project, CN=vpn.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA 1" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +requestor is trusted +nonce: a8:0f:29:0f:08:9c:29:c1:0d:a8:cb:b0:21:fa:e1:f7 +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA 1" + issuerKeyHash: 5a:1b:ec:17:f0:6d:..:a2:c8:e7:6a:84:20 (ok) + issuerNameHash: df:1e:24:71:96:e6:..:b9:82:18:45:e7:09 (ok) + serialNumber: 04:ff:cc:8d:36:91:cb:35:d7:c4 + thisUpdate: Oct 19 16:30:54 UTC 2023 + nextUpdate: Oct 19 16:40:54 UTC 2023 + certValidation: REVOKED + revocationTime: Mar 26 06:41:54 UTC 2023 + revocationReason: superseded +trusted signer: "C=CH, O=strongSwan Project, CN=OCSP signer of strongSwan Issuing CA 1" +ocspResponseStatus: successful +.EE +.PP +Respond to an OCSP request containing two items from different known issuers +having an OCSP signer each. The issuer of the first request item determines the +OCSP signer used to sign the OCSP response: +.PP +.EX +pki \-\-ocsp \-\-respond \-\-in req.der \-\-cacert issuer1.pem \-\-cacert issuer2.pem \\ + \-\-key signerKey1.pem \-\-cert signerCert1.pem \\ + \-\-key signerKey2.pem \-\-cert signerCert2.pem \\ + \-\-lifetime 10 > rsp_trusted.der + +nonce: a1:33:aa:bc:96:60:69:76:f3:bc:9c:88:3b:07:50:47 +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA 2" + issuerKeyHash: 72:41:ca:f9:35:87:..:d3:83:ab:d5:89:7b (ok) + issuerNameHash: 5e:b2:b4:42:e1:a5:..:b2:c3:9a:38:4f:cd (ok) + serialNumber: 29:ff:36:d9:9a:21:49:61:91:1d + thisUpdate: Oct 19 16:02:35 UTC 2023 + nextUpdate: Oct 19 16:12:35 UTC 2023 + certValidation: REVOKED + revocationTime: Sep 22 13:13:04 UTC 2023 + revocationReason: superseded +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA 1" + issuerKeyHash: 5a:1b:ec:17:f0:6d:..:a2:c8:e7:6a:84:20 (ok) + issuerNameHash: df:1e:24:71:96:e6:..:b9:82:18:45:e7:09 (ok) + serialNumber: 10:ff:45:9a:6d:ee:4c:ec:7c:97 + thisUpdate: Oct 19 16:02:35 UTC 2023 + nextUpdate: Oct 19 16:12:35 UTC 2023 + certValidation: FAILED +there are multiple known issuers +trusted signer: "C=CH, O=strongSwan Project, CN=OCSP signer of strongSwan Issuing CA 2" +ocspResponseStatus: successful +.EE +.PP +Repeat the OCSP response above but with a self-signed OCSP signing certificate +.PP +.EX +pki \-\-ocsp --respond \-\-in req.der \-\-cacert issuer1.pem \-\-cacert issuer2.pem \\ + \-\-key signerKey.pem \-\-cert signerCert.pem \-\-lifetime 10 > rsp_self_signed.der + +nonce: a1:33:aa:bc:96:60:69:76:f3:bc:9c:88:3b:07:50:47 +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA 2" + issuerKeyHash: 72:41:ca:f9:35:87:..:d3:83:ab:d5:89:7b (ok) + issuerNameHash: 5e:b2:b4:42:e1:a5:..:b2:c3:9a:38:4f:cd (ok) + serialNumber: 29:ff:36:d9:9a:21:49:61:91:1d + thisUpdate: Oct 19 16:13:23 UTC 2023 + nextUpdate: Oct 19 16:23:23 UTC 2023 + certValidation: REVOKED + revocationTime: Sep 22 13:13:04 UTC 2023 + revocationReason: superseded +issuer: "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA 1" + issuerKeyHash: 5a:1b:ec:17:f0:6d:..:a2:c8:e7:6a:84:20 (ok) + issuerNameHash: df:1e:24:71:96:e6:..:b9:82:18:45:e7:09 (ok) + serialNumber: 10:ff:45:9a:6d:ee:4c:ec:7c:97 + thisUpdate: Oct 19 16:13:23 UTC 2023 + nextUpdate: Oct 19 16:23:23 UTC 2023 + certValidation: GOOD +there are multiple known issuers +self-signed signer: "C=CH, O=strongSwan Project, CN=strongSwan OCSP signer" +ocspResponseStatus: successful +.EE +.PP +.SH "SEE ALSO" +.BR pki (1) diff --git a/src/pki/man/pki---print.1.in b/src/pki/man/pki---print.1.in index 6de0e7340..66eaf63db 100644 --- a/src/pki/man/pki---print.1.in +++ b/src/pki/man/pki---print.1.in @@ -54,7 +54,8 @@ Type of input. One of \fIx509\fR (X.509 certificate), \fIcrl\fR (Certificate Revocation List, CRL), \fIac\fR (Attribute Certificate), \fIpub\fR (public key), \fIpriv\fR (private key), \fIrsa\fR (RSA private key), \fIecdsa\fR (ECDSA private key), \fIed25519\fR (Ed25519 private key), \fIed448\fR (Ed448 private -key), \fIbliss\fR (BLISS private key), defaults to \fIx509\fR. +key), \fIbliss\fR (BLISS private key), \fIocsp-req\fR (OCSP request), +\fIocsp-rsp\fR (OCSP response), defaults to \fIx509\fR. . .SH "SEE ALSO" . diff --git a/src/pki/man/pki.1.in b/src/pki/man/pki.1.in index 6f1efa7a0..9dddf0061 100644 --- a/src/pki/man/pki.1.in +++ b/src/pki/man/pki.1.in @@ -1,4 +1,4 @@ -.TH PKI 1 "2022-08-22" "@PACKAGE_VERSION@" "strongSwan" +.TH PKI 1 "2023-10-20" "@PACKAGE_VERSION@" "strongSwan" . .SH "NAME" . @@ -33,13 +33,19 @@ key IDs. .P The .B pki -command now supports certificate enrollment via the +command also supports certificate enrollment via the .B Simple Certificate Enrollment Protocol (SCEP) as defined by RFC 8894, replacing the obsoleted .B ipsec scepclient tool. Additionally the .B Enrollment over Secure Transport (EST) protocol (RFC 7030) is supported, too. +.P +The latest feature is an +.B Online Certificate Status Protocol +(OCSP) responder as defined by RFC 6960, interoperating with an +.B OpenXPKI +server by directly accessing its internal certificate datebase. . .SH "COMMANDS" . @@ -94,6 +100,9 @@ Enroll an X.509 certificate with an EST server. .TP .B "\-e, \-\-estca" Get CA certificate[s] from an EST server. +.TP +.B "\-o, \-\-ocsp" +OCSP request parser and OCSP responder. . .SH "EXAMPLES" . @@ -188,3 +197,4 @@ certificates with the \-\-crl option. .BR pki\ \-\-scepca (1) .BR pki\ \-\-est (1) .BR pki\ \-\-estca (1) +.BR pki\ \-\-ocsp (1)