/* $NetBSD: cms.c,v 1.3.8.1 2023/08/11 13:39:59 martin Exp $ */ /* * Copyright (c) 2003 - 2007 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "hx_locl.h" /** * @page page_cms CMS/PKCS7 message functions. * * CMS is defined in RFC 3369 and is an continuation of the RSA Labs * standard PKCS7. The basic messages in CMS is * * - SignedData * Data signed with private key (RSA, DSA, ECDSA) or secret * (symmetric) key * - EnvelopedData * Data encrypted with private key (RSA) * - EncryptedData * Data encrypted with secret (symmetric) key. * - ContentInfo * Wrapper structure including type and data. * * * See the library functions here: @ref hx509_cms */ #define ALLOC(X, N) (X) = calloc((N), sizeof(*(X))) #define ALLOC_SEQ(X, N) do { (X)->len = (N); ALLOC((X)->val, (N)); } while(0) /** * Wrap data and oid in a ContentInfo and encode it. * * @param oid type of the content. * @param buf data to be wrapped. If a NULL pointer is passed in, the * optional content field in the ContentInfo is not going be filled * in. * @param res the encoded buffer, the result should be freed with * der_free_octet_string(). * * @return Returns an hx509 error code. * * @ingroup hx509_cms */ int hx509_cms_wrap_ContentInfo(const heim_oid *oid, const heim_octet_string *buf, heim_octet_string *res) { ContentInfo ci; size_t size; int ret; memset(res, 0, sizeof(*res)); memset(&ci, 0, sizeof(ci)); ret = der_copy_oid(oid, &ci.contentType); if (ret) return ret; if (buf) { ALLOC(ci.content, 1); if (ci.content == NULL) { free_ContentInfo(&ci); return ENOMEM; } ci.content->data = malloc(buf->length); if (ci.content->data == NULL) { free_ContentInfo(&ci); return ENOMEM; } memcpy(ci.content->data, buf->data, buf->length); ci.content->length = buf->length; } ASN1_MALLOC_ENCODE(ContentInfo, res->data, res->length, &ci, &size, ret); free_ContentInfo(&ci); if (ret) return ret; if (res->length != size) _hx509_abort("internal ASN.1 encoder error"); return 0; } /** * Decode an ContentInfo and unwrap data and oid it. * * @param in the encoded buffer. * @param oid type of the content. * @param out data to be wrapped. * @param have_data since the data is optional, this flags show dthe * diffrence between no data and the zero length data. * * @return Returns an hx509 error code. * * @ingroup hx509_cms */ int hx509_cms_unwrap_ContentInfo(const heim_octet_string *in, heim_oid *oid, heim_octet_string *out, int *have_data) { ContentInfo ci; size_t size; int ret; memset(oid, 0, sizeof(*oid)); memset(out, 0, sizeof(*out)); ret = decode_ContentInfo(in->data, in->length, &ci, &size); if (ret) return ret; ret = der_copy_oid(&ci.contentType, oid); if (ret) { free_ContentInfo(&ci); return ret; } if (ci.content) { ret = der_copy_octet_string(ci.content, out); if (ret) { der_free_oid(oid); free_ContentInfo(&ci); return ret; } } else memset(out, 0, sizeof(*out)); if (have_data) *have_data = (ci.content != NULL) ? 1 : 0; free_ContentInfo(&ci); return 0; } #define CMS_ID_SKI 0 #define CMS_ID_NAME 1 static int fill_CMSIdentifier(const hx509_cert cert, int type, CMSIdentifier *id) { int ret; switch (type) { case CMS_ID_SKI: id->element = choice_CMSIdentifier_subjectKeyIdentifier; ret = _hx509_find_extension_subject_key_id(_hx509_get_cert(cert), &id->u.subjectKeyIdentifier); if (ret == 0) break; /* FALLTHROUGH */ case CMS_ID_NAME: { hx509_name name; id->element = choice_CMSIdentifier_issuerAndSerialNumber; ret = hx509_cert_get_issuer(cert, &name); if (ret) return ret; ret = hx509_name_to_Name(name, &id->u.issuerAndSerialNumber.issuer); hx509_name_free(&name); if (ret) return ret; ret = hx509_cert_get_serialnumber(cert, &id->u.issuerAndSerialNumber.serialNumber); break; } default: _hx509_abort("CMS fill identifier with unknown type"); } return ret; } static int unparse_CMSIdentifier(hx509_context context, CMSIdentifier *id, char **str) { int ret = -1; *str = NULL; switch (id->element) { case choice_CMSIdentifier_issuerAndSerialNumber: { IssuerAndSerialNumber *iasn; char *serial, *name; iasn = &id->u.issuerAndSerialNumber; ret = _hx509_Name_to_string(&iasn->issuer, &name); if(ret) return ret; ret = der_print_hex_heim_integer(&iasn->serialNumber, &serial); if (ret) { free(name); return ret; } ret = asprintf(str, "certificate issued by %s with serial number %s", name, serial); free(name); free(serial); break; } case choice_CMSIdentifier_subjectKeyIdentifier: { KeyIdentifier *ki = &id->u.subjectKeyIdentifier; char *keyid; ssize_t len; len = hex_encode(ki->data, ki->length, &keyid); if (len < 0) return ENOMEM; ret = asprintf(str, "certificate with id %s", keyid); free(keyid); break; } default: ret = asprintf(str, "certificate have unknown CMSidentifier type"); break; } /* * In the following if, we check ret and *str which should be returned/set * by asprintf(3) in every branch of the switch statement. */ if (ret == -1 || *str == NULL) return ENOMEM; return 0; } static int find_CMSIdentifier(hx509_context context, CMSIdentifier *client, hx509_certs certs, time_t time_now, hx509_cert *signer_cert, int match) { hx509_query q; hx509_cert cert; Certificate c; int ret; memset(&c, 0, sizeof(c)); _hx509_query_clear(&q); *signer_cert = NULL; switch (client->element) { case choice_CMSIdentifier_issuerAndSerialNumber: q.serial = &client->u.issuerAndSerialNumber.serialNumber; q.issuer_name = &client->u.issuerAndSerialNumber.issuer; q.match = HX509_QUERY_MATCH_SERIALNUMBER|HX509_QUERY_MATCH_ISSUER_NAME; break; case choice_CMSIdentifier_subjectKeyIdentifier: q.subject_id = &client->u.subjectKeyIdentifier; q.match = HX509_QUERY_MATCH_SUBJECT_KEY_ID; break; default: hx509_set_error_string(context, 0, HX509_CMS_NO_RECIPIENT_CERTIFICATE, "unknown CMS identifier element"); return HX509_CMS_NO_RECIPIENT_CERTIFICATE; } q.match |= match; q.match |= HX509_QUERY_MATCH_TIME; if (time_now) q.timenow = time_now; else q.timenow = time(NULL); ret = hx509_certs_find(context, certs, &q, &cert); if (ret == HX509_CERT_NOT_FOUND) { char *str; ret = unparse_CMSIdentifier(context, client, &str); if (ret == 0) { hx509_set_error_string(context, 0, HX509_CMS_NO_RECIPIENT_CERTIFICATE, "Failed to find %s", str); } else hx509_clear_error_string(context); return HX509_CMS_NO_RECIPIENT_CERTIFICATE; } else if (ret) { hx509_set_error_string(context, HX509_ERROR_APPEND, HX509_CMS_NO_RECIPIENT_CERTIFICATE, "Failed to find CMS id in cert store"); return HX509_CMS_NO_RECIPIENT_CERTIFICATE; } *signer_cert = cert; return 0; } /** * Decode and unencrypt EnvelopedData. * * Extract data and parameteres from from the EnvelopedData. Also * supports using detached EnvelopedData. * * @param context A hx509 context. * @param certs Certificate that can decrypt the EnvelopedData * encryption key. * @param flags HX509_CMS_UE flags to control the behavior. * @param data pointer the structure the contains the DER/BER encoded * EnvelopedData stucture. * @param length length of the data that data point to. * @param encryptedContent in case of detached signature, this * contains the actual encrypted data, othersize its should be NULL. * @param time_now set the current time, if zero the library uses now as the date. * @param contentType output type oid, should be freed with der_free_oid(). * @param content the data, free with der_free_octet_string(). * * @return an hx509 error code. * * @ingroup hx509_cms */ int hx509_cms_unenvelope(hx509_context context, hx509_certs certs, int flags, const void *data, size_t length, const heim_octet_string *encryptedContent, time_t time_now, heim_oid *contentType, heim_octet_string *content) { heim_octet_string key; EnvelopedData ed; hx509_cert cert; AlgorithmIdentifier *ai; const heim_octet_string *enccontent; heim_octet_string *params, params_data; heim_octet_string ivec; size_t size; int ret, matched = 0, findflags = 0; size_t i; memset(&key, 0, sizeof(key)); memset(&ed, 0, sizeof(ed)); memset(&ivec, 0, sizeof(ivec)); memset(content, 0, sizeof(*content)); memset(contentType, 0, sizeof(*contentType)); if ((flags & HX509_CMS_UE_DONT_REQUIRE_KU_ENCIPHERMENT) == 0) findflags |= HX509_QUERY_KU_ENCIPHERMENT; ret = decode_EnvelopedData(data, length, &ed, &size); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decode EnvelopedData"); return ret; } if (ed.recipientInfos.len == 0) { ret = HX509_CMS_NO_RECIPIENT_CERTIFICATE; hx509_set_error_string(context, 0, ret, "No recipient info in enveloped data"); goto out; } enccontent = ed.encryptedContentInfo.encryptedContent; if (enccontent == NULL) { if (encryptedContent == NULL) { ret = HX509_CMS_NO_DATA_AVAILABLE; hx509_set_error_string(context, 0, ret, "Content missing from encrypted data"); goto out; } enccontent = encryptedContent; } else if (encryptedContent != NULL) { ret = HX509_CMS_NO_DATA_AVAILABLE; hx509_set_error_string(context, 0, ret, "Both internal and external encrypted data"); goto out; } cert = NULL; for (i = 0; i < ed.recipientInfos.len; i++) { KeyTransRecipientInfo *ri; char *str; int ret2; ri = &ed.recipientInfos.val[i]; ret = find_CMSIdentifier(context, &ri->rid, certs, time_now, &cert, HX509_QUERY_PRIVATE_KEY|findflags); if (ret) continue; matched = 1; /* found a matching certificate, let decrypt */ ret = _hx509_cert_private_decrypt(context, &ri->encryptedKey, &ri->keyEncryptionAlgorithm.algorithm, cert, &key); hx509_cert_free(cert); if (ret == 0) break; /* succuessfully decrypted cert */ cert = NULL; ret2 = unparse_CMSIdentifier(context, &ri->rid, &str); if (ret2 == 0) { hx509_set_error_string(context, HX509_ERROR_APPEND, ret, "Failed to decrypt with %s", str); free(str); } } if (!matched) { ret = HX509_CMS_NO_RECIPIENT_CERTIFICATE; hx509_set_error_string(context, 0, ret, "No private key matched any certificate"); goto out; } if (cert == NULL) { ret = HX509_CMS_NO_RECIPIENT_CERTIFICATE; hx509_set_error_string(context, HX509_ERROR_APPEND, ret, "No private key decrypted the transfer key"); goto out; } ret = der_copy_oid(&ed.encryptedContentInfo.contentType, contentType); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to copy EnvelopedData content oid"); goto out; } ai = &ed.encryptedContentInfo.contentEncryptionAlgorithm; if (ai->parameters) { params_data.data = ai->parameters->data; params_data.length = ai->parameters->length; params = ¶ms_data; } else params = NULL; { hx509_crypto crypto; ret = hx509_crypto_init(context, NULL, &ai->algorithm, &crypto); if (ret) goto out; if (flags & HX509_CMS_UE_ALLOW_WEAK) hx509_crypto_allow_weak(crypto); if (params) { ret = hx509_crypto_set_params(context, crypto, params, &ivec); if (ret) { hx509_crypto_destroy(crypto); goto out; } } ret = hx509_crypto_set_key_data(crypto, key.data, key.length); if (ret) { hx509_crypto_destroy(crypto); hx509_set_error_string(context, 0, ret, "Failed to set key for decryption " "of EnvelopedData"); goto out; } ret = hx509_crypto_decrypt(crypto, enccontent->data, enccontent->length, ivec.length ? &ivec : NULL, content); hx509_crypto_destroy(crypto); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decrypt EnvelopedData"); goto out; } } out: free_EnvelopedData(&ed); der_free_octet_string(&key); if (ivec.length) der_free_octet_string(&ivec); if (ret) { der_free_oid(contentType); der_free_octet_string(content); } return ret; } /** * Encrypt end encode EnvelopedData. * * Encrypt and encode EnvelopedData. The data is encrypted with a * random key and the the random key is encrypted with the * certificates private key. This limits what private key type can be * used to RSA. * * @param context A hx509 context. * @param flags flags to control the behavior. * - HX509_CMS_EV_NO_KU_CHECK - Don't check KU on certificate * - HX509_CMS_EV_ALLOW_WEAK - Allow weak crytpo * - HX509_CMS_EV_ID_NAME - prefer issuer name and serial number * @param cert Certificate to encrypt the EnvelopedData encryption key * with. * @param data pointer the data to encrypt. * @param length length of the data that data point to. * @param encryption_type Encryption cipher to use for the bulk data, * use NULL to get default. * @param contentType type of the data that is encrypted * @param content the output of the function, * free with der_free_octet_string(). * * @return an hx509 error code. * * @ingroup hx509_cms */ int hx509_cms_envelope_1(hx509_context context, int flags, hx509_cert cert, const void *data, size_t length, const heim_oid *encryption_type, const heim_oid *contentType, heim_octet_string *content) { KeyTransRecipientInfo *ri; heim_octet_string ivec; heim_octet_string key; hx509_crypto crypto = NULL; int ret, cmsidflag; EnvelopedData ed; size_t size; memset(&ivec, 0, sizeof(ivec)); memset(&key, 0, sizeof(key)); memset(&ed, 0, sizeof(ed)); memset(content, 0, sizeof(*content)); if (encryption_type == NULL) encryption_type = &asn1_oid_id_aes_256_cbc; if ((flags & HX509_CMS_EV_NO_KU_CHECK) == 0) { ret = _hx509_check_key_usage(context, cert, 1 << 2, TRUE); if (ret) goto out; } ret = hx509_crypto_init(context, NULL, encryption_type, &crypto); if (ret) goto out; if (flags & HX509_CMS_EV_ALLOW_WEAK) hx509_crypto_allow_weak(crypto); ret = hx509_crypto_set_random_key(crypto, &key); if (ret) { hx509_set_error_string(context, 0, ret, "Create random key for EnvelopedData content"); goto out; } ret = hx509_crypto_random_iv(crypto, &ivec); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to create a random iv"); goto out; } ret = hx509_crypto_encrypt(crypto, data, length, &ivec, &ed.encryptedContentInfo.encryptedContent); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to encrypt EnvelopedData content"); goto out; } { AlgorithmIdentifier *enc_alg; enc_alg = &ed.encryptedContentInfo.contentEncryptionAlgorithm; ret = der_copy_oid(encryption_type, &enc_alg->algorithm); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to set crypto oid " "for EnvelopedData"); goto out; } ALLOC(enc_alg->parameters, 1); if (enc_alg->parameters == NULL) { ret = ENOMEM; hx509_set_error_string(context, 0, ret, "Failed to allocate crypto paramaters " "for EnvelopedData"); goto out; } ret = hx509_crypto_get_params(context, crypto, &ivec, enc_alg->parameters); if (ret) { goto out; } } ALLOC_SEQ(&ed.recipientInfos, 1); if (ed.recipientInfos.val == NULL) { ret = ENOMEM; hx509_set_error_string(context, 0, ret, "Failed to allocate recipients info " "for EnvelopedData"); goto out; } ri = &ed.recipientInfos.val[0]; if (flags & HX509_CMS_EV_ID_NAME) { ri->version = 0; cmsidflag = CMS_ID_NAME; } else { ri->version = 2; cmsidflag = CMS_ID_SKI; } ret = fill_CMSIdentifier(cert, cmsidflag, &ri->rid); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to set CMS identifier info " "for EnvelopedData"); goto out; } ret = hx509_cert_public_encrypt(context, &key, cert, &ri->keyEncryptionAlgorithm.algorithm, &ri->encryptedKey); if (ret) { hx509_set_error_string(context, HX509_ERROR_APPEND, ret, "Failed to encrypt transport key for " "EnvelopedData"); goto out; } /* * */ ed.version = 0; ed.originatorInfo = NULL; ret = der_copy_oid(contentType, &ed.encryptedContentInfo.contentType); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to copy content oid for " "EnvelopedData"); goto out; } ed.unprotectedAttrs = NULL; ASN1_MALLOC_ENCODE(EnvelopedData, content->data, content->length, &ed, &size, ret); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to encode EnvelopedData"); goto out; } if (size != content->length) _hx509_abort("internal ASN.1 encoder error"); out: if (crypto) hx509_crypto_destroy(crypto); if (ret) der_free_octet_string(content); der_free_octet_string(&key); der_free_octet_string(&ivec); free_EnvelopedData(&ed); return ret; } static int any_to_certs(hx509_context context, const SignedData *sd, hx509_certs certs) { int ret; size_t i; if (sd->certificates == NULL) return 0; for (i = 0; i < sd->certificates->len; i++) { heim_error_t error; hx509_cert c; c = hx509_cert_init_data(context, sd->certificates->val[i].data, sd->certificates->val[i].length, &error); if (c == NULL) { ret = heim_error_get_code(error); heim_release(error); return ret; } ret = hx509_certs_add(context, certs, c); hx509_cert_free(c); if (ret) return ret; } return 0; } static const Attribute * find_attribute(const CMSAttributes *attr, const heim_oid *oid) { size_t i; for (i = 0; i < attr->len; i++) if (der_heim_oid_cmp(&attr->val[i].type, oid) == 0) return &attr->val[i]; return NULL; } /** * Decode SignedData and verify that the signature is correct. * * @param context A hx509 context. * @param ctx a hx509 verify context. * @param flags to control the behaivor of the function. * - HX509_CMS_VS_NO_KU_CHECK - Don't check KeyUsage * - HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH - allow oid mismatch * - HX509_CMS_VS_ALLOW_ZERO_SIGNER - no signer, see below. * @param data pointer to CMS SignedData encoded data. * @param length length of the data that data point to. * @param signedContent external data used for signature. * @param pool certificate pool to build certificates paths. * @param contentType free with der_free_oid(). * @param content the output of the function, free with * der_free_octet_string(). * @param signer_certs list of the cerficates used to sign this * request, free with hx509_certs_free(). * * @return an hx509 error code. * * @ingroup hx509_cms */ int hx509_cms_verify_signed(hx509_context context, hx509_verify_ctx ctx, unsigned int flags, const void *data, size_t length, const heim_octet_string *signedContent, hx509_certs pool, heim_oid *contentType, heim_octet_string *content, hx509_certs *signer_certs) { SignerInfo *signer_info; hx509_cert cert = NULL; hx509_certs certs = NULL; SignedData sd; size_t size; int ret, found_valid_sig; size_t i; *signer_certs = NULL; content->data = NULL; content->length = 0; contentType->length = 0; contentType->components = NULL; memset(&sd, 0, sizeof(sd)); ret = decode_SignedData(data, length, &sd, &size); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decode SignedData"); goto out; } if (sd.encapContentInfo.eContent == NULL && signedContent == NULL) { ret = HX509_CMS_NO_DATA_AVAILABLE; hx509_set_error_string(context, 0, ret, "No content data in SignedData"); goto out; } if (sd.encapContentInfo.eContent && signedContent) { ret = HX509_CMS_NO_DATA_AVAILABLE; hx509_set_error_string(context, 0, ret, "Both external and internal SignedData"); goto out; } if (sd.encapContentInfo.eContent) ret = der_copy_octet_string(sd.encapContentInfo.eContent, content); else ret = der_copy_octet_string(signedContent, content); if (ret) { hx509_set_error_string(context, 0, ret, "malloc: out of memory"); goto out; } ret = hx509_certs_init(context, "MEMORY:cms-cert-buffer", 0, NULL, &certs); if (ret) goto out; ret = hx509_certs_init(context, "MEMORY:cms-signer-certs", 0, NULL, signer_certs); if (ret) goto out; /* XXX Check CMS version */ ret = any_to_certs(context, &sd, certs); if (ret) goto out; if (pool) { ret = hx509_certs_merge(context, certs, pool); if (ret) goto out; } for (found_valid_sig = 0, i = 0; i < sd.signerInfos.len; i++) { heim_octet_string signed_data = { 0, 0 }; const heim_oid *match_oid; heim_oid decode_oid; signer_info = &sd.signerInfos.val[i]; match_oid = NULL; if (signer_info->signature.length == 0) { ret = HX509_CMS_MISSING_SIGNER_DATA; hx509_set_error_string(context, 0, ret, "SignerInfo %d in SignedData " "missing sigature", i); continue; } ret = find_CMSIdentifier(context, &signer_info->sid, certs, _hx509_verify_get_time(ctx), &cert, HX509_QUERY_KU_DIGITALSIGNATURE); if (ret) { /** * If HX509_CMS_VS_NO_KU_CHECK is set, allow more liberal * search for matching certificates by not considering * KeyUsage bits on the certificates. */ if ((flags & HX509_CMS_VS_NO_KU_CHECK) == 0) continue; ret = find_CMSIdentifier(context, &signer_info->sid, certs, _hx509_verify_get_time(ctx), &cert, 0); if (ret) continue; } if (signer_info->signedAttrs) { const Attribute *attr; CMSAttributes sa; heim_octet_string os; sa.val = signer_info->signedAttrs->val; sa.len = signer_info->signedAttrs->len; /* verify that sigature exists */ attr = find_attribute(&sa, &asn1_oid_id_pkcs9_messageDigest); if (attr == NULL) { ret = HX509_CRYPTO_SIGNATURE_MISSING; hx509_set_error_string(context, 0, ret, "SignerInfo have signed attributes " "but messageDigest (signature) " "is missing"); goto next_sigature; } if (attr->value.len != 1) { ret = HX509_CRYPTO_SIGNATURE_MISSING; hx509_set_error_string(context, 0, ret, "SignerInfo have more then one " "messageDigest (signature)"); goto next_sigature; } ret = decode_MessageDigest(attr->value.val[0].data, attr->value.val[0].length, &os, &size); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decode " "messageDigest (signature)"); goto next_sigature; } ret = _hx509_verify_signature(context, NULL, &signer_info->digestAlgorithm, content, &os); der_free_octet_string(&os); if (ret) { hx509_set_error_string(context, HX509_ERROR_APPEND, ret, "Failed to verify messageDigest"); goto next_sigature; } /* * Fetch content oid inside signedAttrs or set it to * id-pkcs7-data. */ attr = find_attribute(&sa, &asn1_oid_id_pkcs9_contentType); if (attr == NULL) { match_oid = &asn1_oid_id_pkcs7_data; } else { if (attr->value.len != 1) { ret = HX509_CMS_DATA_OID_MISMATCH; hx509_set_error_string(context, 0, ret, "More then one oid in signedAttrs"); goto next_sigature; } ret = decode_ContentType(attr->value.val[0].data, attr->value.val[0].length, &decode_oid, &size); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decode " "oid in signedAttrs"); goto next_sigature; } match_oid = &decode_oid; } ASN1_MALLOC_ENCODE(CMSAttributes, signed_data.data, signed_data.length, &sa, &size, ret); if (ret) { if (match_oid == &decode_oid) der_free_oid(&decode_oid); hx509_clear_error_string(context); goto next_sigature; } if (size != signed_data.length) _hx509_abort("internal ASN.1 encoder error"); } else { signed_data.data = content->data; signed_data.length = content->length; match_oid = &asn1_oid_id_pkcs7_data; } /** * If HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH, allow * encapContentInfo mismatch with the oid in signedAttributes * (or if no signedAttributes where use, pkcs7-data oid). * This is only needed to work with broken CMS implementations * that doesn't follow CMS signedAttributes rules. */ if (der_heim_oid_cmp(match_oid, &sd.encapContentInfo.eContentType) && (flags & HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH) == 0) { ret = HX509_CMS_DATA_OID_MISMATCH; hx509_set_error_string(context, 0, ret, "Oid in message mismatch from the expected"); } if (match_oid == &decode_oid) der_free_oid(&decode_oid); if (ret == 0) { ret = hx509_verify_signature(context, cert, &signer_info->signatureAlgorithm, &signed_data, &signer_info->signature); if (ret) hx509_set_error_string(context, HX509_ERROR_APPEND, ret, "Failed to verify signature in " "CMS SignedData"); } if (signed_data.data != NULL && content->data != signed_data.data) { free(signed_data.data); signed_data.data = NULL; } if (ret) goto next_sigature; /** * If HX509_CMS_VS_NO_VALIDATE flags is set, do not verify the * signing certificates and leave that up to the caller. */ if ((flags & HX509_CMS_VS_NO_VALIDATE) == 0) { ret = hx509_verify_path(context, ctx, cert, certs); if (ret) goto next_sigature; } ret = hx509_certs_add(context, *signer_certs, cert); if (ret) goto next_sigature; found_valid_sig++; next_sigature: if (cert) hx509_cert_free(cert); cert = NULL; } /** * If HX509_CMS_VS_ALLOW_ZERO_SIGNER is set, allow empty * SignerInfo (no signatures). If SignedData have no signatures, * the function will return 0 with signer_certs set to NULL. Zero * signers is allowed by the standard, but since its only useful * in corner cases, it make into a flag that the caller have to * turn on. */ if (sd.signerInfos.len == 0 && (flags & HX509_CMS_VS_ALLOW_ZERO_SIGNER)) { if (*signer_certs) hx509_certs_free(signer_certs); } else if (found_valid_sig == 0) { if (ret == 0) { ret = HX509_CMS_SIGNER_NOT_FOUND; hx509_set_error_string(context, 0, ret, "No signers where found"); } goto out; } ret = der_copy_oid(&sd.encapContentInfo.eContentType, contentType); if (ret) { hx509_clear_error_string(context); goto out; } out: free_SignedData(&sd); if (certs) hx509_certs_free(&certs); if (ret) { if (content->data) der_free_octet_string(content); if (*signer_certs) hx509_certs_free(signer_certs); der_free_oid(contentType); der_free_octet_string(content); } return ret; } static int add_one_attribute(Attribute **attr, unsigned int *len, const heim_oid *oid, heim_octet_string *data) { void *d; int ret; d = realloc(*attr, sizeof((*attr)[0]) * (*len + 1)); if (d == NULL) return ENOMEM; (*attr) = d; ret = der_copy_oid(oid, &(*attr)[*len].type); if (ret) return ret; ALLOC_SEQ(&(*attr)[*len].value, 1); if ((*attr)[*len].value.val == NULL) { der_free_oid(&(*attr)[*len].type); return ENOMEM; } (*attr)[*len].value.val[0].data = data->data; (*attr)[*len].value.val[0].length = data->length; *len += 1; return 0; } /** * Decode SignedData and verify that the signature is correct. * * @param context A hx509 context. * @param flags * @param eContentType the type of the data. * @param data data to sign * @param length length of the data that data point to. * @param digest_alg digest algorithm to use, use NULL to get the * default or the peer determined algorithm. * @param cert certificate to use for sign the data. * @param peer info about the peer the message to send the message to, * like what digest algorithm to use. * @param anchors trust anchors that the client will use, used to * polulate the certificates included in the message * @param pool certificates to use in try to build the path to the * trust anchors. * @param signed_data the output of the function, free with * der_free_octet_string(). * * @return Returns an hx509 error code. * * @ingroup hx509_cms */ int hx509_cms_create_signed_1(hx509_context context, int flags, const heim_oid *eContentType, const void *data, size_t length, const AlgorithmIdentifier *digest_alg, hx509_cert cert, hx509_peer_info peer, hx509_certs anchors, hx509_certs pool, heim_octet_string *signed_data) { hx509_certs certs; int ret = 0; signed_data->data = NULL; signed_data->length = 0; ret = hx509_certs_init(context, "MEMORY:certs", 0, NULL, &certs); if (ret) return ret; ret = hx509_certs_add(context, certs, cert); if (ret) goto out; ret = hx509_cms_create_signed(context, flags, eContentType, data, length, digest_alg, certs, peer, anchors, pool, signed_data); out: hx509_certs_free(&certs); return ret; } struct sigctx { SignedData sd; const AlgorithmIdentifier *digest_alg; const heim_oid *eContentType; heim_octet_string content; hx509_peer_info peer; int cmsidflag; int leafonly; hx509_certs certs; hx509_certs anchors; hx509_certs pool; }; static int sig_process(hx509_context context, void *ctx, hx509_cert cert) { struct sigctx *sigctx = ctx; heim_octet_string buf, sigdata = { 0, NULL }; SignerInfo *signer_info = NULL; AlgorithmIdentifier digest; size_t size; void *ptr; int ret; SignedData *sd = &sigctx->sd; hx509_path path; memset(&digest, 0, sizeof(digest)); memset(&path, 0, sizeof(path)); if (_hx509_cert_private_key(cert) == NULL) { hx509_set_error_string(context, 0, HX509_PRIVATE_KEY_MISSING, "Private key missing for signing"); return HX509_PRIVATE_KEY_MISSING; } if (sigctx->digest_alg) { ret = copy_AlgorithmIdentifier(sigctx->digest_alg, &digest); if (ret) hx509_clear_error_string(context); } else { ret = hx509_crypto_select(context, HX509_SELECT_DIGEST, _hx509_cert_private_key(cert), sigctx->peer, &digest); } if (ret) goto out; /* * Allocate on more signerInfo and do the signature processing */ ptr = realloc(sd->signerInfos.val, (sd->signerInfos.len + 1) * sizeof(sd->signerInfos.val[0])); if (ptr == NULL) { ret = ENOMEM; goto out; } sd->signerInfos.val = ptr; signer_info = &sd->signerInfos.val[sd->signerInfos.len]; memset(signer_info, 0, sizeof(*signer_info)); signer_info->version = 1; ret = fill_CMSIdentifier(cert, sigctx->cmsidflag, &signer_info->sid); if (ret) { hx509_clear_error_string(context); goto out; } signer_info->signedAttrs = NULL; signer_info->unsignedAttrs = NULL; ret = copy_AlgorithmIdentifier(&digest, &signer_info->digestAlgorithm); if (ret) { hx509_clear_error_string(context); goto out; } /* * If it isn't pkcs7-data send signedAttributes */ if (der_heim_oid_cmp(sigctx->eContentType, &asn1_oid_id_pkcs7_data) != 0) { CMSAttributes sa; heim_octet_string sig; ALLOC(signer_info->signedAttrs, 1); if (signer_info->signedAttrs == NULL) { ret = ENOMEM; goto out; } ret = _hx509_create_signature(context, NULL, &digest, &sigctx->content, NULL, &sig); if (ret) goto out; ASN1_MALLOC_ENCODE(MessageDigest, buf.data, buf.length, &sig, &size, ret); der_free_octet_string(&sig); if (ret) { hx509_clear_error_string(context); goto out; } if (size != buf.length) _hx509_abort("internal ASN.1 encoder error"); ret = add_one_attribute(&signer_info->signedAttrs->val, &signer_info->signedAttrs->len, &asn1_oid_id_pkcs9_messageDigest, &buf); if (ret) { free(buf.data); hx509_clear_error_string(context); goto out; } ASN1_MALLOC_ENCODE(ContentType, buf.data, buf.length, sigctx->eContentType, &size, ret); if (ret) goto out; if (size != buf.length) _hx509_abort("internal ASN.1 encoder error"); ret = add_one_attribute(&signer_info->signedAttrs->val, &signer_info->signedAttrs->len, &asn1_oid_id_pkcs9_contentType, &buf); if (ret) { free(buf.data); hx509_clear_error_string(context); goto out; } sa.val = signer_info->signedAttrs->val; sa.len = signer_info->signedAttrs->len; ASN1_MALLOC_ENCODE(CMSAttributes, sigdata.data, sigdata.length, &sa, &size, ret); if (ret) { hx509_clear_error_string(context); goto out; } if (size != sigdata.length) _hx509_abort("internal ASN.1 encoder error"); } else { sigdata.data = sigctx->content.data; sigdata.length = sigctx->content.length; } { AlgorithmIdentifier sigalg; ret = hx509_crypto_select(context, HX509_SELECT_PUBLIC_SIG, _hx509_cert_private_key(cert), sigctx->peer, &sigalg); if (ret) goto out; ret = _hx509_create_signature(context, _hx509_cert_private_key(cert), &sigalg, &sigdata, &signer_info->signatureAlgorithm, &signer_info->signature); free_AlgorithmIdentifier(&sigalg); if (ret) goto out; } sigctx->sd.signerInfos.len++; signer_info = NULL; /* * Provide best effort path */ if (sigctx->certs) { unsigned int i; if (sigctx->pool && sigctx->leafonly == 0) { _hx509_calculate_path(context, HX509_CALCULATE_PATH_NO_ANCHOR, time(NULL), sigctx->anchors, 0, cert, sigctx->pool, &path); } else _hx509_path_append(context, &path, cert); for (i = 0; i < path.len; i++) { /* XXX remove dups */ ret = hx509_certs_add(context, sigctx->certs, path.val[i]); if (ret) { hx509_clear_error_string(context); goto out; } } } out: if (signer_info) free_SignerInfo(signer_info); if (sigdata.data != sigctx->content.data) der_free_octet_string(&sigdata); _hx509_path_free(&path); free_AlgorithmIdentifier(&digest); return ret; } static int cert_process(hx509_context context, void *ctx, hx509_cert cert) { struct sigctx *sigctx = ctx; const unsigned int i = sigctx->sd.certificates->len; void *ptr; int ret; ptr = realloc(sigctx->sd.certificates->val, (i + 1) * sizeof(sigctx->sd.certificates->val[0])); if (ptr == NULL) return ENOMEM; sigctx->sd.certificates->val = ptr; ret = hx509_cert_binary(context, cert, &sigctx->sd.certificates->val[i]); if (ret == 0) sigctx->sd.certificates->len++; return ret; } static int cmp_AlgorithmIdentifier(const AlgorithmIdentifier *p, const AlgorithmIdentifier *q) { return der_heim_oid_cmp(&p->algorithm, &q->algorithm); } int hx509_cms_create_signed(hx509_context context, int flags, const heim_oid *eContentType, const void *data, size_t length, const AlgorithmIdentifier *digest_alg, hx509_certs certs, hx509_peer_info peer, hx509_certs anchors, hx509_certs pool, heim_octet_string *signed_data) { unsigned int i, j; hx509_name name; int ret; size_t size; struct sigctx sigctx; memset(&sigctx, 0, sizeof(sigctx)); memset(&name, 0, sizeof(name)); if (eContentType == NULL) eContentType = &asn1_oid_id_pkcs7_data; sigctx.digest_alg = digest_alg; sigctx.content.data = rk_UNCONST(data); sigctx.content.length = length; sigctx.eContentType = eContentType; sigctx.peer = peer; /** * Use HX509_CMS_SIGNATURE_ID_NAME to preferred use of issuer name * and serial number if possible. Otherwise subject key identifier * will preferred. */ if (flags & HX509_CMS_SIGNATURE_ID_NAME) sigctx.cmsidflag = CMS_ID_NAME; else sigctx.cmsidflag = CMS_ID_SKI; /** * Use HX509_CMS_SIGNATURE_LEAF_ONLY to only request leaf * certificates to be added to the SignedData. */ sigctx.leafonly = (flags & HX509_CMS_SIGNATURE_LEAF_ONLY) ? 1 : 0; /** * Use HX509_CMS_NO_CERTS to make the SignedData contain no * certificates, overrides HX509_CMS_SIGNATURE_LEAF_ONLY. */ if ((flags & HX509_CMS_SIGNATURE_NO_CERTS) == 0) { ret = hx509_certs_init(context, "MEMORY:certs", 0, NULL, &sigctx.certs); if (ret) return ret; } sigctx.anchors = anchors; sigctx.pool = pool; sigctx.sd.version = CMSVersion_v3; der_copy_oid(eContentType, &sigctx.sd.encapContentInfo.eContentType); /** * Use HX509_CMS_SIGNATURE_DETACHED to create detached signatures. */ if ((flags & HX509_CMS_SIGNATURE_DETACHED) == 0) { ALLOC(sigctx.sd.encapContentInfo.eContent, 1); if (sigctx.sd.encapContentInfo.eContent == NULL) { hx509_clear_error_string(context); ret = ENOMEM; goto out; } sigctx.sd.encapContentInfo.eContent->data = malloc(length); if (sigctx.sd.encapContentInfo.eContent->data == NULL) { hx509_clear_error_string(context); ret = ENOMEM; goto out; } memcpy(sigctx.sd.encapContentInfo.eContent->data, data, length); sigctx.sd.encapContentInfo.eContent->length = length; } /** * Use HX509_CMS_SIGNATURE_NO_SIGNER to create no sigInfo (no * signatures). */ if ((flags & HX509_CMS_SIGNATURE_NO_SIGNER) == 0) { ret = hx509_certs_iter_f(context, certs, sig_process, &sigctx); if (ret) goto out; } if (sigctx.sd.signerInfos.len) { /* * For each signerInfo, collect all different digest types. */ for (i = 0; i < sigctx.sd.signerInfos.len; i++) { AlgorithmIdentifier *di = &sigctx.sd.signerInfos.val[i].digestAlgorithm; for (j = 0; j < sigctx.sd.digestAlgorithms.len; j++) if (cmp_AlgorithmIdentifier(di, &sigctx.sd.digestAlgorithms.val[j]) == 0) break; if (j == sigctx.sd.digestAlgorithms.len) { ret = add_DigestAlgorithmIdentifiers(&sigctx.sd.digestAlgorithms, di); if (ret) { hx509_clear_error_string(context); goto out; } } } } /* * Add certs we think are needed, build as part of sig_process */ if (sigctx.certs) { ALLOC(sigctx.sd.certificates, 1); if (sigctx.sd.certificates == NULL) { hx509_clear_error_string(context); ret = ENOMEM; goto out; } ret = hx509_certs_iter_f(context, sigctx.certs, cert_process, &sigctx); if (ret) goto out; } ASN1_MALLOC_ENCODE(SignedData, signed_data->data, signed_data->length, &sigctx.sd, &size, ret); if (ret) { hx509_clear_error_string(context); goto out; } if (signed_data->length != size) _hx509_abort("internal ASN.1 encoder error"); out: hx509_certs_free(&sigctx.certs); free_SignedData(&sigctx.sd); return ret; } int hx509_cms_decrypt_encrypted(hx509_context context, hx509_lock lock, const void *data, size_t length, heim_oid *contentType, heim_octet_string *content) { heim_octet_string cont; CMSEncryptedData ed; AlgorithmIdentifier *ai; int ret; memset(content, 0, sizeof(*content)); memset(&cont, 0, sizeof(cont)); ret = decode_CMSEncryptedData(data, length, &ed, NULL); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to decode CMSEncryptedData"); return ret; } if (ed.encryptedContentInfo.encryptedContent == NULL) { ret = HX509_CMS_NO_DATA_AVAILABLE; hx509_set_error_string(context, 0, ret, "No content in EncryptedData"); goto out; } ret = der_copy_oid(&ed.encryptedContentInfo.contentType, contentType); if (ret) { hx509_clear_error_string(context); goto out; } ai = &ed.encryptedContentInfo.contentEncryptionAlgorithm; if (ai->parameters == NULL) { ret = HX509_ALG_NOT_SUPP; hx509_clear_error_string(context); goto out; } ret = _hx509_pbe_decrypt(context, lock, ai, ed.encryptedContentInfo.encryptedContent, &cont); if (ret) goto out; *content = cont; out: if (ret) { if (cont.data) free(cont.data); } free_CMSEncryptedData(&ed); return ret; }