From: Andreas Steffen Date: Thu, 15 Jun 2023 13:47:19 +0000 (+0200) Subject: x509: Support generation of OCSP responses X-Git-Tag: 5.9.12rc1~3^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=00ab8d62c08906c4d33336587f772993565b424a;p=thirdparty%2Fstrongswan.git x509: Support generation of OCSP responses --- diff --git a/src/libstrongswan/credentials/builder.c b/src/libstrongswan/credentials/builder.c index bb50e097f4..27ae83c8fa 100644 --- a/src/libstrongswan/credentials/builder.c +++ b/src/libstrongswan/credentials/builder.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2008 Martin Willi - * Copyright (C) 2016-2022 Andreas Steffen + * Copyright (C) 2016-2023 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -78,5 +78,8 @@ ENUM(builder_part_names, BUILD_FROM_FILE, BUILD_END, "BUILD_EDDSA_PUB", "BUILD_EDDSA_PRIV_ASN1_DER", "BUILD_CRITICAL_EXTENSION", + "BUILD_NONCE", + "BUILD_OCSP_STATUS", + "BUILD_OCSP_RESPONSES", "BUILD_END", ); diff --git a/src/libstrongswan/credentials/builder.h b/src/libstrongswan/credentials/builder.h index 6d143dd4fc..e95265e4c6 100644 --- a/src/libstrongswan/credentials/builder.h +++ b/src/libstrongswan/credentials/builder.h @@ -165,6 +165,12 @@ enum builder_part_t { BUILD_EDDSA_PRIV_ASN1_DER, /** OID of an [unsupported] critical extension */ BUILD_CRITICAL_EXTENSION, + /** nonce needed for some security protocol */ + BUILD_NONCE, + /** OCSP response status, ocsp_status_t */ + BUILD_OCSP_STATUS, + /** enumerator_t over (ocsp_single_response_t *response) */ + BUILD_OCSP_RESPONSES, /** end of variable argument builder list */ BUILD_END, }; diff --git a/src/libstrongswan/credentials/certificates/ocsp_response.h b/src/libstrongswan/credentials/certificates/ocsp_response.h index cc77137263..6f97a3e7a9 100644 --- a/src/libstrongswan/credentials/certificates/ocsp_response.h +++ b/src/libstrongswan/credentials/certificates/ocsp_response.h @@ -88,9 +88,9 @@ struct ocsp_response_t { enumerator_t* (*create_cert_enumerator)(ocsp_response_t *this); /** - * Create an enumerator over the contained responses. + * Create an enumerator over the contained single responses. * - * @return enumerator over major response fields + * @return enumerator over ocsp_single_response_t objects */ enumerator_t* (*create_response_enumerator)(ocsp_response_t *this); }; diff --git a/src/libstrongswan/plugins/x509/x509_ocsp_response.c b/src/libstrongswan/plugins/x509/x509_ocsp_response.c index 9954e55ce9..3badf36b97 100644 --- a/src/libstrongswan/plugins/x509/x509_ocsp_response.c +++ b/src/libstrongswan/plugins/x509/x509_ocsp_response.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2017-2019 Tobias Brunner * Copyright (C) 2008-2009 Martin Willi - * Copyright (C) 2007-2022 Andreas Steffen + * Copyright (C) 2007-2023 Andreas Steffen * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen * * Copyright (C) secunet Security Networks AG @@ -31,6 +31,7 @@ #include #include #include +#include /** * how long do we use an OCSP response without a nextUpdate @@ -73,6 +74,11 @@ struct private_x509_ocsp_response_t { */ chunk_t signature; + /** + * OCSP response status + */ + ocsp_status_t ocsp_status; + /** * name or keyid of the responder */ @@ -103,36 +109,22 @@ struct private_x509_ocsp_response_t { */ chunk_t nonce; + /** + * Signer certificate, included in response + */ + certificate_t *cert; + + /** + * Signer private key to sign response + */ + private_key_t *key; + /** * reference counter */ refcount_t ref; }; -/** - * single response contained in OCSP response - */ -typedef struct { - /** hash algorithm OID to for the two hashes */ - int hashAlgorithm; - /** hash of issuer DN */ - chunk_t issuerNameHash; - /** issuerKeyID */ - chunk_t issuerKeyHash; - /** serial number of certificate */ - chunk_t serialNumber; - /** OCSP certificate status */ - cert_validation_t status; - /** time of revocation, if revoked */ - time_t revocationTime; - /** revocation reason, if revoked */ - crl_reason_t revocationReason; - /** creation of associated CRL */ - time_t thisUpdate; - /** creation of next CRL */ - time_t nextUpdate; -} single_response_t; - /* our OCSP response version implementation */ #define OCSP_BASIC_RESPONSE_VERSION 1 @@ -142,7 +134,7 @@ METHOD(ocsp_response_t, get_status, cert_validation_t, time_t *this_update, time_t *next_update) { enumerator_t *enumerator; - single_response_t *response; + ocsp_single_response_t *response; cert_validation_t status = VALIDATION_FAILED; certificate_t *issuercert = &issuer->interface; @@ -232,7 +224,7 @@ METHOD(ocsp_response_t, create_cert_enumerator, enumerator_t*, CALLBACK(filter, bool, void *data, enumerator_t *orig, va_list args) { - single_response_t *response; + ocsp_single_response_t *response; cert_validation_t *status; crl_reason_t *revocationReason; chunk_t *serialNumber; @@ -277,6 +269,54 @@ METHOD(ocsp_response_t, get_nonce, chunk_t, return this->nonce; } +/** + * Build singleResponse + */ +static chunk_t build_singleResponse(private_x509_ocsp_response_t *this, + ocsp_single_response_t *response) +{ + chunk_t certID, certStatus, nextUpdate = chunk_empty; + + certID = asn1_wrap(ASN1_SEQUENCE, "mmmm", + asn1_algorithmIdentifier( + hasher_algorithm_to_oid(response->hashAlgorithm)), + asn1_simple_object(ASN1_OCTET_STRING, response->issuerNameHash), + asn1_simple_object(ASN1_OCTET_STRING, response->issuerKeyHash), + asn1_integer("c", response->serialNumber)); + + switch (response->status) + { + case VALIDATION_GOOD: + certStatus = asn1_wrap(ASN1_CONTEXT_S_0, "c", chunk_empty); + break; + case VALIDATION_REVOKED: + case VALIDATION_ON_HOLD: + certStatus = asn1_wrap(ASN1_CONTEXT_C_1, "mm", + asn1_from_time(&response->revocationTime, + ASN1_GENERALIZEDTIME), + asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_simple_object(ASN1_ENUMERATED, + chunk_from_chars(response->revocationReason)))); + break; + case VALIDATION_FAILED: + default: + certStatus = asn1_wrap(ASN1_CONTEXT_S_2, "c", chunk_empty); + } + + if (response->nextUpdate != 0) + { + nextUpdate = asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_from_time(&response->nextUpdate, + ASN1_GENERALIZEDTIME)); + } + + return asn1_wrap(ASN1_SEQUENCE, "mmmm", + certID, + certStatus, + asn1_from_time(&response->thisUpdate, ASN1_GENERALIZEDTIME), + nextUpdate); +} + /** * ASN.1 definition of singleResponse */ @@ -312,6 +352,7 @@ static const asn1Object_t singleResponseObjects[] = { { 1, "end opt", ASN1_EOC, ASN1_END }, /* 27 */ { 0, "exit", ASN1_EOC, ASN1_EXIT } }; + #define SINGLE_RESPONSE_ALGORITHM 2 #define SINGLE_RESPONSE_ISSUER_NAME_HASH 3 #define SINGLE_RESPONSE_ISSUER_KEY_HASH 4 @@ -338,17 +379,10 @@ static bool parse_singleResponse(private_x509_ocsp_response_t *this, int objectID; bool success = FALSE; - single_response_t *response; - - 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; + ocsp_single_response_t *response; + + response = ocsp_single_response_create(); + /* if nextUpdate is missing, we give it a short lifetime */ response->nextUpdate = this->producedAt + OCSP_DEFAULT_LIFETIME; @@ -364,13 +398,13 @@ static bool parse_singleResponse(private_x509_ocsp_response_t *this, parser->get_level(parser)+1, NULL); break; case SINGLE_RESPONSE_ISSUER_NAME_HASH: - response->issuerNameHash = object; + response->issuerNameHash = chunk_clone(object); break; case SINGLE_RESPONSE_ISSUER_KEY_HASH: - response->issuerKeyHash = object; + response->issuerKeyHash = chunk_clone(object); break; case SINGLE_RESPONSE_SERIAL_NUMBER: - response->serialNumber = chunk_skip_zero(object); + response->serialNumber = chunk_clone(chunk_skip_zero(object)); break; case SINGLE_RESPONSE_CERT_STATUS_GOOD: response->status = VALIDATION_GOOD; @@ -414,11 +448,31 @@ static bool parse_singleResponse(private_x509_ocsp_response_t *this, } else { - free(response); + response->destroy(response); } return success; } +/** + * Build responses + */ +static chunk_t build_responses(private_x509_ocsp_response_t *this) +{ + ocsp_single_response_t *response; + chunk_t responses = chunk_empty, single_response; + enumerator_t *enumerator; + + enumerator = this->responses->create_enumerator(this->responses); + while (enumerator->enumerate(enumerator, &response)) + { + single_response = build_singleResponse(this, response); + responses = chunk_cat("mm", responses, single_response); + } + enumerator->destroy(enumerator); + + return asn1_wrap(ASN1_SEQUENCE, "m", responses); +} + /** * ASN.1 definition of responses */ @@ -466,6 +520,94 @@ end: return success; } +/** + * Build tbsResponseData + */ +static chunk_t build_tbsResponseData(private_x509_ocsp_response_t *this) +{ + chunk_t responderIdByName; + chunk_t responseExtensions = chunk_empty; + + responderIdByName = asn1_wrap(ASN1_CONTEXT_C_1, "c", + this->responderId->get_encoding(this->responderId)); + + this->producedAt = time(NULL); + + responseExtensions = asn1_wrap(ASN1_CONTEXT_C_1, "m", + asn1_wrap(ASN1_SEQUENCE, "m", + asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_build_known_oid(OID_NONCE), + asn1_wrap(ASN1_OCTET_STRING, "m", + asn1_simple_object(ASN1_OCTET_STRING, + this->nonce))))); + + return asn1_wrap(ASN1_SEQUENCE, "mmmm", + responderIdByName, + asn1_from_time(&this->producedAt, ASN1_GENERALIZEDTIME), + build_responses(this), + responseExtensions); +} + +/** + * Build the signature + */ +static bool build_signature(private_x509_ocsp_response_t *this, + chunk_t tbsResponseData, chunk_t *signature) +{ + if (!this->key->sign(this->key, this->scheme->scheme, this->scheme->params, + tbsResponseData, signature)) + { + DBG1(DBG_LIB, "creating OCSP response signature failed"); + return FALSE; + } + return TRUE; +} + +/** + * Build the basicOCSPResponse + */ +static bool build_basicOCSPResponse(private_x509_ocsp_response_t *this, + chunk_t *basicResponse) +{ + chunk_t tbsResponseData, sig_scheme, signature; + chunk_t cert_encoding, certs = chunk_empty; + x509_t *x509 = (x509_t*)this->cert; + + *basicResponse = chunk_empty; + + if (!signature_params_build(this->scheme, &sig_scheme)) + { + return FALSE; + } + tbsResponseData = build_tbsResponseData(this); + + if (!build_signature(this, tbsResponseData, &signature)) + { + free(tbsResponseData.ptr); + free(sig_scheme.ptr); + return FALSE; + } + + /* don't include self-signed signer certificates */ + if (!(x509->get_flags(x509) & X509_SELF_SIGNED)) + { + if (!this->cert->get_encoding(this->cert, CERT_ASN1_DER, &cert_encoding)) + { + free(tbsResponseData.ptr); + free(sig_scheme.ptr); + free(signature.ptr); + return FALSE; + } + certs = asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_wrap(ASN1_SEQUENCE, "m", cert_encoding)); + } + + *basicResponse = asn1_wrap(ASN1_SEQUENCE, "mmmm", + tbsResponseData, sig_scheme, + asn1_bitstring("m", signature), certs); + return TRUE; +} + /** * ASN.1 definition of basicResponse */ @@ -580,7 +722,7 @@ static bool parse_basicOCSPResponse(private_x509_ocsp_response_t *this, asn1_parse_simple_object(&object, ASN1_OCTET_STRING, parser->get_level(parser)+1, "nonce")) { - this->nonce = object; + this->nonce = chunk_clone(object); } break; case BASIC_RESPONSE_ALGORITHM: @@ -624,6 +766,31 @@ end: return success; } +/** + * Build the OCSPResponse + * + */ +static chunk_t build_OCSPResponse(private_x509_ocsp_response_t *this) +{ + chunk_t response, responseBytes = chunk_empty; + + if (this->ocsp_status == OCSP_SUCCESSFUL) + { + if (!build_basicOCSPResponse(this, &response)) + { + return chunk_empty; + } + responseBytes = asn1_wrap(ASN1_CONTEXT_C_0, "m", + asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_build_known_oid(OID_BASIC), + asn1_wrap(ASN1_OCTET_STRING, "m", response))); + } + return asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_simple_object(ASN1_ENUMERATED, + chunk_from_chars(this->ocsp_status)), + responseBytes); +} + /** * ASN.1 definition of ocspResponse */ @@ -651,7 +818,6 @@ static bool parse_OCSPResponse(private_x509_ocsp_response_t *this) int objectID; int responseType = OID_UNKNOWN; bool success = FALSE; - ocsp_status_t status; parser = asn1_parser_create(ocspResponseObjects, this->encoding); @@ -660,15 +826,16 @@ static bool parse_OCSPResponse(private_x509_ocsp_response_t *this) switch (objectID) { case OCSP_RESPONSE_STATUS: - status = (ocsp_status_t)*object.ptr; - switch (status) + this->ocsp_status = (ocsp_status_t)*object.ptr; + switch (this->ocsp_status) { case OCSP_SUCCESSFUL: break; default: DBG1(DBG_LIB, " ocsp response status: %N", - ocsp_status_names, status); - goto end; + ocsp_status_names, this->ocsp_status); + success = TRUE; + break; } break; case OCSP_RESPONSE_TYPE: @@ -845,19 +1012,24 @@ METHOD(certificate_t, destroy, void, { if (ref_put(&this->ref)) { - this->certs->destroy_offset(this->certs, offsetof(certificate_t, destroy)); - this->responses->destroy_function(this->responses, free); - signature_params_destroy(this->scheme); + this->certs->destroy_offset(this->certs, + offsetof(certificate_t, destroy)); + this->responses->destroy_offset(this->responses, + offsetof(ocsp_single_response_t, destroy)); + DESTROY_IF(this->cert); + DESTROY_IF(this->key); DESTROY_IF(this->responderId); + signature_params_destroy(this->scheme); + free(this->nonce.ptr); free(this->encoding.ptr); free(this); } } /** - * load an OCSP response + * create an empty but initialized OCSP response */ -static x509_ocsp_response_t *load(chunk_t blob) +static private_x509_ocsp_response_t *create_empty() { private_x509_ocsp_response_t *this; @@ -885,13 +1057,118 @@ static x509_ocsp_response_t *load(chunk_t blob) }, }, .ref = 1, - .encoding = chunk_clone(blob), .producedAt = UNDEFINED_TIME, .usableUntil = UNDEFINED_TIME, .responses = linked_list_create(), .certs = linked_list_create(), ); + return this; +} + +/** + * See header. + */ +x509_ocsp_response_t *x509_ocsp_response_gen(certificate_type_t type, va_list args) +{ + private_x509_ocsp_response_t *this; + private_key_t *private; + certificate_t *cert; + chunk_t nonce; + identification_t *subject; + enumerator_t *enumerator; + ocsp_single_response_t *response; + + this = create_empty(); + + while (TRUE) + { + switch (va_arg(args, builder_part_t)) + { + case BUILD_OCSP_STATUS: + this->ocsp_status = va_arg(args, ocsp_status_t); + continue; + case BUILD_OCSP_RESPONSES: + enumerator = va_arg(args, enumerator_t*); + while (enumerator->enumerate(enumerator, &response)) + { + this->responses->insert_last(this->responses, + response->get_ref(response)); + } + continue; + case BUILD_SIGNING_CERT: + cert = va_arg(args, certificate_t*); + if (cert) + { + subject = cert->get_subject(cert); + this->cert = cert->get_ref(cert); + this->responderId = subject->clone(subject); + } + continue; + case BUILD_SIGNING_KEY: + private = va_arg(args, private_key_t*); + if (private) + { + this->key = private->get_ref(private); + } + continue; + case BUILD_SIGNATURE_SCHEME: + this->scheme = va_arg(args, signature_params_t*); + this->scheme = signature_params_clone(this->scheme); + continue; + case BUILD_NONCE: + nonce = va_arg(args, chunk_t); + this->nonce = chunk_clone(nonce); + continue; + case BUILD_END: + break; + default: + goto error; + } + break; + } + + if (this->ocsp_status == OCSP_SUCCESSFUL) + { + if (!this->key) + { + DBG1(DBG_LIB, "no OCSP signing key defined"); + goto error; + } + + /* select signature scheme, if not already specified */ + if (!this->scheme) + { + INIT(this->scheme, + .scheme = signature_scheme_from_oid( + hasher_signature_algorithm_to_oid(HASH_SHA256, + this->key->get_type(this->key))), + ); + } + if (this->scheme->scheme == SIGN_UNKNOWN) + { + goto error; + } + } + + this->encoding = build_OCSPResponse(this); + return &this->public; + +error: + destroy(this); + return NULL; +} + +/** + * load an OCSP response + */ +static x509_ocsp_response_t *load(chunk_t blob) +{ + private_x509_ocsp_response_t *this; + + this = create_empty(); + this->encoding = chunk_clone(blob); + if (!parse_OCSPResponse(this)) { destroy(this); diff --git a/src/libstrongswan/plugins/x509/x509_ocsp_response.h b/src/libstrongswan/plugins/x509/x509_ocsp_response.h index 9c66f6004e..f07c1cacc6 100644 --- a/src/libstrongswan/plugins/x509/x509_ocsp_response.h +++ b/src/libstrongswan/plugins/x509/x509_ocsp_response.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2008-2009 Martin Willi + * Copyright (C) 2023 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -38,6 +39,23 @@ struct x509_ocsp_response_t { ocsp_response_t interface; }; +/** + * Generate a X.509 OCSP response. + * + * The resulting builder accepts: + * BUILD_OCSP_STATUS: status from OCSP respnder + * BUILD_OCSP_RESPONSES: enumerator over the list of OCSP single responses + * BUILD_NONCE: nonce extracted from the OCSP request + * BUILD_SIGNING_CERT: certificate to create OCSP response signature + * BUILD_SIGNING_KEY: private key to create OCSP response signature + * BUILD_SIGNATURE_SCHEME: scheme used for the OCSP response signature + * + * @param type certificate type, CERT_X509_OCSP_REQUEST only + * @param args builder_part_t argument list + * @return OCSP request, NULL on failure + */ +x509_ocsp_response_t *x509_ocsp_response_gen(certificate_type_t type, va_list args); + /** * Load a X.509 OCSP response. * diff --git a/src/libstrongswan/plugins/x509/x509_plugin.c b/src/libstrongswan/plugins/x509/x509_plugin.c index 6742bc0cd6..ca7922da85 100644 --- a/src/libstrongswan/plugins/x509/x509_plugin.c +++ b/src/libstrongswan/plugins/x509/x509_plugin.c @@ -72,6 +72,8 @@ METHOD(plugin_t, get_features, int, 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),