Merge branch 'ocsp-responder'

Implements a new --ocsp command for the pki tool that can produce OCSP
responses based on information provided by a plugin.  A first plugin
that accesses the OpenXPKI database is also added.

Closes strongswan/strongswan#1958
This commit is contained in:
Tobias Brunner 2023-11-13 12:42:00 +01:00
commit 3197523bd5
38 changed files with 2706 additions and 289 deletions

View File

@ -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 \

View File

@ -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.

View File

@ -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

View File

@ -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 \

View File

@ -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

View File

@ -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",
);

View File

@ -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,
};

View File

@ -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;

View File

@ -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,
};

View File

@ -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_ @}*/

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @defgroup ocsp_responder ocsp_responder
* @{ @ingroup certificates
*/
#ifndef OCSP_RESPONDER_H_
#define OCSP_RESPONDER_H_
#include <credentials/certificates/crl.h>
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_ @}*/

View File

@ -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);
};

View File

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

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @defgroup ocsp_single_response ocsp_single_response
* @{ @ingroup certificates
*/
#ifndef OCSP_SINGLE_RESPONSE_H_
#define OCSP_SINGLE_RESPONSE_H_
#include <credentials/certificates/x509.h>
#include <credentials/certificates/crl.h>
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_ @}*/

View File

@ -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

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <library.h>
#include <utils/debug.h>
#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;
}

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @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 <credentials/certificates/ocsp_responder.h>
/**
* 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_ @}*/

View File

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

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @defgroup openxpki_p openxpki
* @ingroup plugins
*
* @defgroup openxpki_plugin openxpki_plugin
* @{ @ingroup openxpki_p
*/
#ifndef OPENXPKI_PLUGIN_H_
#define OPENXPKI_PLUGIN_H_
#include <plugins/plugin.h>
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_ @}*/

View File

@ -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 <library.h>
#include <asn1/oid.h>
#include <asn1/asn1.h>
#include <asn1/asn1_parser.h>
#include <utils/identification.h>
#include <collections/linked_list.h>
#include <utils/debug.h>
#include <credentials/certificates/x509.h>
#include <credentials/keys/private_key.h>
#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, &current))
{
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;
}

View File

@ -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_ @}*/

View File

@ -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 <library.h>
#include <credentials/certificates/x509.h>
#include <credentials/certificates/crl.h>
#include <credentials/certificates/ocsp_single_response.h>
/**
* 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);

View File

@ -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.
*

View File

@ -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),

View File

@ -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");

View File

@ -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 <credentials/certificates/x509.h>
#include <credentials/certificates/crl.h>
#include <credentials/certificates/ocsp_request.h>
#include <credentials/certificates/ocsp_response.h>
#include <credentials/certificates/ocsp_single_response.h>
#include <credentials/certificates/ac.h>
#include <time.h>
@ -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);

View File

@ -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.
*/

View File

@ -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
*/

View File

@ -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 \

View File

@ -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)

601
src/pki/commands/ocsp.c Normal file
View File

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

View File

@ -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);

View File

@ -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"},

View File

@ -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"))
{

View File

@ -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 \

247
src/pki/man/pki---ocsp.1.in Normal file
View File

@ -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)

View File

@ -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"
.

View File

@ -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)