freebsd-dev/crypto/heimdal/lib/hx509/cms.c
2008-05-07 13:39:42 +00:00

1427 lines
35 KiB
C

/*
* 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"
RCSID("$Id: cms.c 22327 2007-12-15 04:49:37Z lha $");
/**
* @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;
/* FALL THOUGH */
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;
*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;
}
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;
asprintf(str, "certificate with id %s", keyid);
free(keyid);
break;
}
default:
asprintf(str, "certificate have unknown CMSidentifier type");
break;
}
if (*str == NULL)
return ENOMEM;
return 0;
}
static int
find_CMSIdentifier(hx509_context context,
CMSIdentifier *client,
hx509_certs certs,
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;
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 contentType output type oid, should be freed with der_free_oid().
* @param content the data, free with der_free_octet_string().
*
* @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,
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, i, matched = 0, findflags = 0;
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, &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 = &params_data;
} else
params = NULL;
{
hx509_crypto crypto;
ret = hx509_crypto_init(context, NULL, &ai->algorithm, &crypto);
if (ret)
goto out;
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, no flags today
* @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().
*
* @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;
EnvelopedData ed;
size_t size;
int ret;
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 = oid_id_aes_256_cbc();
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;
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];
ri->version = 0;
ret = fill_CMSIdentifier(cert, CMS_ID_SKI, &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, i;
if (sd->certificates == NULL)
return 0;
for (i = 0; i < sd->certificates->len; i++) {
hx509_cert c;
ret = hx509_cert_init_data(context,
sd->certificates->val[i].data,
sd->certificates->val[i].length,
&c);
if (ret)
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)
{
int 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 version context
* @param data
* @param length length of the data that data point to.
* @param signedContent
* @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().
*
* @ingroup hx509_cms
*/
int
hx509_cms_verify_signed(hx509_context context,
hx509_verify_ctx ctx,
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, i, found_valid_sig;
*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)
signedContent = sd.encapContentInfo.eContent;
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;
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, &cert,
HX509_QUERY_KU_DIGITALSIGNATURE);
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, 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,
signedContent,
&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, oid_id_pkcs9_contentType());
if (attr == NULL) {
match_oid = 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;
}
ALLOC(signed_data, 1);
if (signed_data == NULL) {
if (match_oid == &decode_oid)
der_free_oid(&decode_oid);
ret = ENOMEM;
hx509_clear_error_string(context);
goto next_sigature;
}
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);
free(signed_data);
hx509_clear_error_string(context);
goto next_sigature;
}
if (size != signed_data->length)
_hx509_abort("internal ASN.1 encoder error");
} else {
signed_data = rk_UNCONST(signedContent);
match_oid = oid_id_pkcs7_data();
}
if (der_heim_oid_cmp(match_oid, &sd.encapContentInfo.eContentType)) {
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 sigature in "
"CMS SignedData");
}
if (signed_data != signedContent) {
der_free_octet_string(signed_data);
free(signed_data);
}
if (ret)
goto next_sigature;
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 (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;
}
content->data = malloc(signedContent->length);
if (content->data == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
content->length = signedContent->length;
memcpy(content->data, signedContent->data, content->length);
out:
free_SignedData(&sd);
if (certs)
hx509_certs_free(&certs);
if (ret) {
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().
*
* @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)
{
AlgorithmIdentifier digest;
hx509_name name;
SignerInfo *signer_info;
heim_octet_string buf, content, sigdata = { 0, NULL };
SignedData sd;
int ret;
size_t size;
hx509_path path;
int cmsidflag = CMS_ID_SKI;
memset(&sd, 0, sizeof(sd));
memset(&name, 0, sizeof(name));
memset(&path, 0, sizeof(path));
memset(&digest, 0, sizeof(digest));
content.data = rk_UNCONST(data);
content.length = length;
if (flags & HX509_CMS_SIGATURE_ID_NAME)
cmsidflag = CMS_ID_NAME;
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 (digest_alg == NULL) {
ret = hx509_crypto_select(context, HX509_SELECT_DIGEST,
_hx509_cert_private_key(cert), peer, &digest);
} else {
ret = copy_AlgorithmIdentifier(digest_alg, &digest);
if (ret)
hx509_clear_error_string(context);
}
if (ret)
goto out;
sd.version = CMSVersion_v3;
if (eContentType == NULL)
eContentType = oid_id_pkcs7_data();
der_copy_oid(eContentType, &sd.encapContentInfo.eContentType);
/* */
if ((flags & HX509_CMS_SIGATURE_DETACHED) == 0) {
ALLOC(sd.encapContentInfo.eContent, 1);
if (sd.encapContentInfo.eContent == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
sd.encapContentInfo.eContent->data = malloc(length);
if (sd.encapContentInfo.eContent->data == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
memcpy(sd.encapContentInfo.eContent->data, data, length);
sd.encapContentInfo.eContent->length = length;
}
ALLOC_SEQ(&sd.signerInfos, 1);
if (sd.signerInfos.val == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
signer_info = &sd.signerInfos.val[0];
signer_info->version = 1;
ret = fill_CMSIdentifier(cert, 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(eContentType, 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,
&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,
oid_id_pkcs9_messageDigest(),
&buf);
if (ret) {
hx509_clear_error_string(context);
goto out;
}
ASN1_MALLOC_ENCODE(ContentType,
buf.data,
buf.length,
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,
oid_id_pkcs9_contentType(),
&buf);
if (ret) {
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 = content.data;
sigdata.length = content.length;
}
{
AlgorithmIdentifier sigalg;
ret = hx509_crypto_select(context, HX509_SELECT_PUBLIC_SIG,
_hx509_cert_private_key(cert), 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;
}
ALLOC_SEQ(&sd.digestAlgorithms, 1);
if (sd.digestAlgorithms.val == NULL) {
ret = ENOMEM;
hx509_clear_error_string(context);
goto out;
}
ret = copy_AlgorithmIdentifier(&digest, &sd.digestAlgorithms.val[0]);
if (ret) {
hx509_clear_error_string(context);
goto out;
}
/*
* Provide best effort path
*/
if (pool) {
_hx509_calculate_path(context,
HX509_CALCULATE_PATH_NO_ANCHOR,
time(NULL),
anchors,
0,
cert,
pool,
&path);
} else
_hx509_path_append(context, &path, cert);
if (path.len) {
int i;
ALLOC(sd.certificates, 1);
if (sd.certificates == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
ALLOC_SEQ(sd.certificates, path.len);
if (sd.certificates->val == NULL) {
hx509_clear_error_string(context);
ret = ENOMEM;
goto out;
}
for (i = 0; i < path.len; i++) {
ret = hx509_cert_binary(context, path.val[i],
&sd.certificates->val[i]);
if (ret) {
hx509_clear_error_string(context);
goto out;
}
}
}
ASN1_MALLOC_ENCODE(SignedData,
signed_data->data, signed_data->length,
&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:
if (sigdata.data != content.data)
der_free_octet_string(&sigdata);
free_AlgorithmIdentifier(&digest);
_hx509_path_free(&path);
free_SignedData(&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;
}