From: Tobias Brunner Date: Mon, 30 Oct 2023 17:11:53 +0000 (+0100) Subject: pki: Add index.txt-based OCSP responder X-Git-Tag: 5.9.12rc1~2^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f26ca67d8c16f4ab20767bb29b6a02444644fe1f;p=thirdparty%2Fstrongswan.git pki: Add index.txt-based OCSP responder --- diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 1dbcd9a444..8ba99746ca 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -22,6 +22,7 @@ pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/signcrl.c \ commands/verify.c \ est/est_tls.h est/est_tls.c \ + ocsp/index_responder.h ocsp/index_responder.c \ scep/scep.h scep/scep.c pki_LDADD = \ diff --git a/src/pki/ocsp/index_responder.c b/src/pki/ocsp/index_responder.c new file mode 100644 index 0000000000..c00512d7b8 --- /dev/null +++ b/src/pki/ocsp/index_responder.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2023 Tobias Brunner + * + * 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 "index_responder.h" + +#include +#include + +typedef struct private_ocsp_responder_t private_ocsp_responder_t; + +/** + * Private data + */ +struct private_ocsp_responder_t { + + /** + * Public interface + */ + ocsp_responder_t public; + + /** + * CA certificate + */ + certificate_t *ca; + + /** + * Certificate database + */ + hashtable_t *certs; +}; + +/** + * Status information on a certificate + */ +typedef struct { + /** Serial number */ + chunk_t serial; + /** Certificate's validity */ + cert_validation_t validation; + /** Revocation reason */ + crl_reason_t reason; + /** Revocation time */ + time_t revocation_time; +} cert_entry_t; + +/** + * Destroy a certificate status entry + */ +static void destroy_cert_entry(cert_entry_t *this) +{ + chunk_free(&this->serial); + free(this); +} + +METHOD(ocsp_responder_t, get_status, cert_validation_t, + private_ocsp_responder_t *this, certificate_t *cacert, + chunk_t serial_number, time_t *revocation_time, crl_reason_t *reason) +{ + cert_entry_t *cert; + + if (!cacert->equals(cacert, this->ca)) + { + return VALIDATION_SKIPPED; + } + cert = this->certs->get(this->certs, &serial_number); + if (!cert) + { + return VALIDATION_FAILED; + } + if (revocation_time) + { + *revocation_time = cert->revocation_time; + } + if (reason) + { + *reason = cert->reason; + } + return cert->validation; +} + +METHOD(ocsp_responder_t, destroy, void, + private_ocsp_responder_t *this) +{ + lib->ocsp->remove_responder(lib->ocsp, &this->public); + this->certs->destroy_function(this->certs, (void*)destroy_cert_entry); + this->ca->destroy(this->ca); + free(this); +} + +/* + * Described in header + */ +ocsp_responder_t *index_responder_create(certificate_t *ca, char *path) +{ + private_ocsp_responder_t *this; + hashtable_t *certs; + char line[BUF_LEN], *token, *pos, *reason_str; + FILE *file; + + file = fopen(path, "r"); + if (!file) + { + fprintf(stderr, "failed to open '%s': %s\n", path, strerror(errno)); + return NULL; + } + certs = hashtable_create((hashtable_hash_t)chunk_hash_ptr, + (hashtable_equals_t)chunk_equals_ptr, 32); + while (fgets(line, sizeof(line), file)) + { + enumerator_t *enumerator; + cert_entry_t *cert; + cert_validation_t validation; + crl_reason_t reason = CRL_REASON_UNSPECIFIED; + time_t revocation_time = 0; + chunk_t revocation, serial; + bool valid = FALSE; + int i = 0; + + switch (line[0]) + { + case 'E': + /* OpenSSL only converts valid entries to expired ones */ + case 'V': + validation = VALIDATION_GOOD; + break; + case 'R': + validation = VALIDATION_REVOKED; + break; + default: + /* ignore comments, empty lines etc. */ + continue; + } + enumerator = enumerator_create_token(&line[1], "\t", " \n\r"); + while (enumerator->enumerate(enumerator, &token)) + { + switch (i++) + { + case 0: /* expiration date/time in UTC (YYMMDDHHMMSSZ), ignored */ + continue; + case 1: /* if revoked, revocation date/time and optional reason */ + if (validation == VALIDATION_REVOKED) + { + reason_str = NULL; + pos = strchr(token, ','); + if (pos) + { + *pos = '\0'; + reason_str = ++pos; + /* OpenSSL may optionally store an OID if the reason + * is certificateHold (hold instruction code in + * RFC 3280, was removed with RFC 5280) */ + pos = strchr(pos, ','); + if (pos) + { + *pos = '\0'; + } + } + revocation = chunk_from_str(token); + revocation_time = asn1_to_time(&revocation, + ASN1_UTCTIME); + if (!revocation_time) + { + break; + } + if (strcaseeq(reason_str, "keyCompromise")) + { + reason = CRL_REASON_KEY_COMPROMISE; + } + else if (strcaseeq(reason_str, "CACompromise")) + { + reason = CRL_REASON_CA_COMPROMISE; + } + else if (strcaseeq(reason_str, "affiliationChanged")) + { + reason = CRL_REASON_AFFILIATION_CHANGED; + } + else if (strcaseeq(reason_str, "superseded")) + { + reason = CRL_REASON_SUPERSEDED; + } + else if (strcaseeq(reason_str, "cessationOfOperation")) + { + reason = CRL_REASON_CESSATION_OF_OPERATION; + } + else if (strcaseeq(reason_str, "certificateHold")) + { + reason = CRL_REASON_CERTIFICATE_HOLD; + validation = VALIDATION_ON_HOLD; + } + else if (strcaseeq(reason_str, "removeFromCRL")) + { + reason = CRL_REASON_REMOVE_FROM_CRL; + } + else + { + reason = CRL_REASON_UNSPECIFIED; + } + continue; + } + i++; + /* if not revoked, this field is empty, fall-through */ + case 2: /* hexadecimal serial number */ + serial = chunk_from_hex(chunk_from_str(token), NULL); + valid = serial.len > 0; + /* skip the last two fields, an optional path, usually set + * to "unknown", and the subject DN (RDNs separated by + * slashes), which we don't use */ + break; + default: + break; + } + break; + } + enumerator->destroy(enumerator); + + if (valid) + { + INIT(cert, + .serial = serial, + .validation = validation, + .reason = reason, + .revocation_time = revocation_time, + ); + cert = certs->put(certs, &cert->serial, cert); + if (cert) + { + destroy_cert_entry(cert); + } + } + } + fclose(file); + + INIT(this, + .public = { + .get_status = _get_status, + .destroy = _destroy, + }, + .ca = ca->get_ref(ca), + .certs = certs, + ); + lib->ocsp->add_responder(lib->ocsp, &this->public); + + DBG1(DBG_APP, "loaded status of %u certificates issued by '%Y' from %s", + certs->get_count(certs), ca->get_subject(ca), path); + return &this->public; +} diff --git a/src/pki/ocsp/index_responder.h b/src/pki/ocsp/index_responder.h new file mode 100644 index 0000000000..c126b36149 --- /dev/null +++ b/src/pki/ocsp/index_responder.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Tobias Brunner + * + * 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 ocsp + * @{ @ingroup pki + */ + +#ifndef INDEX_RESPONDER_H_ +#define INDEX_RESPONDER_H_ + +#include + +/** + * Create an index.txt-based OCSP responder for the given CA and file. + * + * On success, the responder is automatically registered until destroyed. + * + * @param ca CA certificate (referenced) + * @param path path to index.txt + * @return OCSP responder, NULL if file is invalid + */ +ocsp_responder_t *index_responder_create(certificate_t *ca, char *path); + +#endif /** INDEX_RESPONDER_H_ @} */