]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
x509: Support generation of OCSP responses
authorAndreas Steffen <andreas.steffen@strongswan.org>
Thu, 15 Jun 2023 13:47:19 +0000 (15:47 +0200)
committerTobias Brunner <tobias@strongswan.org>
Mon, 13 Nov 2023 11:40:55 +0000 (12:40 +0100)
src/libstrongswan/credentials/builder.c
src/libstrongswan/credentials/builder.h
src/libstrongswan/credentials/certificates/ocsp_response.h
src/libstrongswan/plugins/x509/x509_ocsp_response.c
src/libstrongswan/plugins/x509/x509_ocsp_response.h
src/libstrongswan/plugins/x509/x509_plugin.c

index bb50e097f49299e6867e2b03d6b339d2df78f658..27ae83c8fa33ef781e75670ae7e7236916c6f8c9 100644 (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",
 );
index 6d143dd4fc0cf076fb7bf3fa16bc64ad14dd2e03..e95265e4c60ed8908258974621decbe42a90310c 100644 (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,
 };
index cc771372638ad9a872395ac89a96341ae74d9ffc..6f97a3e7a9937d7f4670f79ef1fe478e5796cad3 100644 (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);
 };
index 9954e55ce9d80ef22932ecc99130456a93e48d51..3badf36b979707df725c217cccc14bde9e01bc82 100644 (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;
-
-       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);
index 9c66f6004ed11b41c8683ce63b79c4d34ca17a76..f07c1cacc6b9682519e0982c6be8f720a27933a8 100644 (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.
  *
index 6742bc0cd613b5427459697ac49bd8d5cff611d0..ca7922da8523abc22cd0cfac67cc2605c4d428eb 100644 (file)
@@ -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),