/* * Copyright (C) 2017-2019 Tobias Brunner * Copyright (C) 2008-2009 Martin Willi * Copyright (C) 2007-2023 Andreas Steffen, strongSec GmbH * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen * * Copyright (C) secunet Security Networks AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "x509_ocsp_request.h" #include #include #include #include #include #include #include #include #include /* RFC 8954 OCSP Nonce Extension */ #define NONCE_LEN 32 typedef struct private_x509_ocsp_request_t private_x509_ocsp_request_t; /** * private data of x509_ocsp_request */ struct private_x509_ocsp_request_t { /** * public functions */ x509_ocsp_request_t public; /** * CA the certificates where issued by */ certificate_t *cacert; /** * Requestor name, subject of cert used if not set */ identification_t *requestor; /** * Requestor certificate, included in request */ certificate_t *cert; /** * Requestor private key to sign request */ private_key_t *key; /** * list of X.509 certificates to check */ linked_list_t *reqCerts; /** * nonce used in request */ chunk_t nonce; /** * encoded OCSP request */ 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, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x02 ); static const chunk_t ASN1_response_oid = chunk_from_chars( 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x04 ); static const chunk_t ASN1_response_content = chunk_from_chars( 0x04, 0x0D, 0x30, 0x0B, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 ); /** * build requestorName */ static chunk_t build_requestorName(private_x509_ocsp_request_t *this) { if (this->requestor || this->cert) { /* use requestor name, fallback to his cert subject */ if (!this->requestor) { this->requestor = this->cert->get_subject(this->cert); this->requestor = this->requestor->clone(this->requestor); } return asn1_wrap(ASN1_CONTEXT_C_1, "m", asn1_simple_object(ASN1_CONTEXT_C_4, this->requestor->get_encoding(this->requestor))); } return chunk_empty; } /** * build Request, not using singleRequestExtensions */ static chunk_t build_Request(private_x509_ocsp_request_t *this, req_cert_t *reqCert) { return asn1_wrap(ASN1_SEQUENCE, "m", 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))); } /** * build requestList */ static chunk_t build_requestList(private_x509_ocsp_request_t *this) { chunk_t list = chunk_empty, request; enumerator_t *enumerator; req_cert_t *reqCert; enumerator = this->reqCerts->create_enumerator(this->reqCerts); while (enumerator->enumerate(enumerator, &reqCert)) { request = build_Request(this, reqCert); list = chunk_cat("mm", list, request); } enumerator->destroy(enumerator); return asn1_wrap(ASN1_SEQUENCE, "m", list); } /** * build nonce extension */ static chunk_t build_nonce(private_x509_ocsp_request_t *this) { rng_t *rng; rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); if (!rng || !rng->allocate_bytes(rng, NONCE_LEN, &this->nonce)) { DBG1(DBG_LIB, "failed to create RNG"); DESTROY_IF(rng); return chunk_empty; } rng->destroy(rng); return asn1_wrap(ASN1_SEQUENCE, "cm", ASN1_nonce_oid, asn1_wrap(ASN1_OCTET_STRING, "m", asn1_simple_object(ASN1_OCTET_STRING, this->nonce))); } /** * build acceptableResponses extension */ static chunk_t build_acceptableResponses(private_x509_ocsp_request_t *this) { return asn1_wrap(ASN1_SEQUENCE, "cc", ASN1_response_oid, ASN1_response_content); } /** * build requestExtensions */ static chunk_t build_requestExtensions(private_x509_ocsp_request_t *this) { return asn1_wrap(ASN1_CONTEXT_C_2, "m", asn1_wrap(ASN1_SEQUENCE, "mm", build_nonce(this), build_acceptableResponses(this))); } /** * Build tbsRequest */ static chunk_t build_tbsRequest(private_x509_ocsp_request_t *this) { return asn1_wrap(ASN1_SEQUENCE, "mmm", build_requestorName(this), build_requestList(this), build_requestExtensions(this)); } /** * Build the optionalSignature */ static chunk_t build_optionalSignature(private_x509_ocsp_request_t *this, chunk_t tbsRequest) { int oid; signature_scheme_t scheme; chunk_t certs = chunk_empty, signature, encoding; switch (this->key->get_type(this->key)) { /* TODO: use a generic mapping function */ case KEY_RSA: oid = OID_SHA1_WITH_RSA; scheme = SIGN_RSA_EMSA_PKCS1_SHA1; break; case KEY_ECDSA: oid = OID_ECDSA_WITH_SHA1; scheme = SIGN_ECDSA_WITH_SHA1_DER; break; case KEY_BLISS: oid = OID_BLISS_WITH_SHA2_512; scheme = SIGN_BLISS_WITH_SHA2_512; break; default: DBG1(DBG_LIB, "unable to sign OCSP request, %N signature not " "supported", key_type_names, this->key->get_type(this->key)); return chunk_empty; } if (!this->key->sign(this->key, scheme, NULL, tbsRequest, &signature)) { DBG1(DBG_LIB, "creating OCSP signature failed, skipped"); return chunk_empty; } if (this->cert && this->cert->get_encoding(this->cert, CERT_ASN1_DER, &encoding)) { certs = asn1_wrap(ASN1_CONTEXT_C_0, "m", asn1_wrap(ASN1_SEQUENCE, "m", encoding)); } return asn1_wrap(ASN1_CONTEXT_C_0, "m", asn1_wrap(ASN1_SEQUENCE, "cmm", asn1_algorithmIdentifier(oid), asn1_bitstring("m", signature), certs)); } /** * Build the OCSPRequest data */ static chunk_t build_OCSPRequest(private_x509_ocsp_request_t *this) { chunk_t tbsRequest, optionalSignature = chunk_empty; tbsRequest = build_tbsRequest(this); if (this->key) { optionalSignature = build_optionalSignature(this, tbsRequest); } 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) { return CERT_X509_OCSP_REQUEST; } METHOD(certificate_t, get_subject, identification_t*, private_x509_ocsp_request_t *this) { if (this->requestor) { return this->requestor; } return (this->cert) ? this->cert->get_subject(this->cert) : NULL; } METHOD(certificate_t, get_issuer, identification_t*, private_x509_ocsp_request_t *this) { 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) { return ID_MATCH_NONE; } METHOD(certificate_t, has_issuer, id_match_t, private_x509_ocsp_request_t *this, identification_t *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) { 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*, private_x509_ocsp_request_t *this) { return NULL; } METHOD(certificate_t, get_validity, bool, private_x509_ocsp_request_t *this, time_t *when, time_t *not_before, time_t *not_after) { return FALSE; } METHOD(certificate_t, get_encoding, bool, private_x509_ocsp_request_t *this, cred_encoding_type_t type, chunk_t *encoding) { if (type == CERT_ASN1_DER) { *encoding = chunk_clone(this->encoding); return TRUE; } return lib->encoding->encode(lib->encoding, type, NULL, encoding, CRED_PART_X509_OCSP_REQ_ASN1_DER, this->encoding, CRED_PART_END); } METHOD(certificate_t, equals, bool, private_x509_ocsp_request_t *this, certificate_t *other) { chunk_t encoding; bool equal; if (this == (private_x509_ocsp_request_t*)other) { return TRUE; } if (other->get_type(other) != CERT_X509_OCSP_REQUEST) { return FALSE; } if (other->equals == (void*)equals) { /* skip allocation if we have the same implementation */ return chunk_equals(this->encoding, ((private_x509_ocsp_request_t*)other)->encoding); } if (!other->get_encoding(other, CERT_ASN1_DER, &encoding)) { return FALSE; } equal = chunk_equals(this->encoding, encoding); free(encoding.ptr); return equal; } METHOD(certificate_t, get_ref, certificate_t*, private_x509_ocsp_request_t *this) { ref_get(&this->ref); return &this->public.interface.interface; } METHOD(certificate_t, destroy, void, private_x509_ocsp_request_t *this) { if (ref_put(&this->ref)) { DESTROY_IF(this->cacert); DESTROY_IF(this->requestor); DESTROY_IF(this->cert); DESTROY_IF(this->key); signature_params_destroy(this->scheme); this->reqCerts->destroy_function(this->reqCerts, req_cert_destroy); chunk_free(&this->nonce); chunk_free(&this->encoding); free(this); } } METHOD(ocsp_request_t, get_nonce, chunk_t, private_x509_ocsp_request_t *this) { 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 */ static private_x509_ocsp_request_t *create_empty() { private_x509_ocsp_request_t *this; INIT(this, .public = { .interface = { .interface = { .get_type = _get_type, .get_subject = _get_subject, .get_issuer = _get_issuer, .has_subject = _has_subject, .has_issuer = _has_issuer, .issued_by = _issued_by, .get_public_key = _get_public_key, .get_validity = _get_validity, .get_encoding = _get_encoding, .equals = _equals, .get_ref = _get_ref, .destroy = _destroy, }, .get_nonce = _get_nonce, .get_signer_cert = _get_signer_cert, .create_request_enumerator = _create_request_enumerator, }, }, .reqCerts = linked_list_create(), .ref = 1, ); return this; } /** * See header. */ x509_ocsp_request_t *x509_ocsp_request_gen(certificate_type_t type, va_list args) { 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(); while (TRUE) { switch (va_arg(args, builder_part_t)) { case BUILD_CA_CERT: cert = va_arg(args, certificate_t*); if (cert->get_type(cert) == CERT_X509) { this->cacert = cert->get_ref(cert); } continue; case BUILD_CERT: cert = va_arg(args, certificate_t*); if (cert->get_type(cert) == CERT_X509) { 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*); this->cert = cert->get_ref(cert); continue; case BUILD_SIGNING_KEY: private = va_arg(args, private_key_t*); this->key = private->get_ref(private); continue; case BUILD_SUBJECT: subject = va_arg(args, identification_t*); 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: return NULL; } break; } if (blob.ptr) { return load(blob); } return NULL; }