summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/pkcs7/p7encode.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/pkcs7/p7encode.c')
-rw-r--r--security/nss/lib/pkcs7/p7encode.c1079
1 files changed, 1079 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs7/p7encode.c b/security/nss/lib/pkcs7/p7encode.c
new file mode 100644
index 0000000000..739e25f812
--- /dev/null
+++ b/security/nss/lib/pkcs7/p7encode.c
@@ -0,0 +1,1079 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * PKCS7 encoding.
+ */
+
+#include "p7local.h"
+
+#include "cert.h"
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "secasn1.h"
+#include "secoid.h"
+#include "secitem.h"
+#include "pk11func.h"
+#include "secerr.h"
+#include "sechash.h" /* for HASH_GetHashObject() */
+
+struct sec_pkcs7_encoder_output {
+ SEC_PKCS7EncoderOutputCallback outputfn;
+ void *outputarg;
+};
+
+struct SEC_PKCS7EncoderContextStr {
+ SEC_ASN1EncoderContext *ecx;
+ SEC_PKCS7ContentInfo *cinfo;
+ struct sec_pkcs7_encoder_output output;
+ sec_PKCS7CipherObject *encryptobj;
+ const SECHashObject *digestobj;
+ void *digestcx;
+};
+
+/*
+ * The little output function that the ASN.1 encoder calls to hand
+ * us bytes which we in turn hand back to our caller (via the callback
+ * they gave us).
+ */
+static void
+sec_pkcs7_encoder_out(void *arg, const char *buf, unsigned long len,
+ int depth, SEC_ASN1EncodingPart data_kind)
+{
+ struct sec_pkcs7_encoder_output *output;
+
+ output = (struct sec_pkcs7_encoder_output *)arg;
+ output->outputfn(output->outputarg, buf, len);
+}
+
+static sec_PKCS7CipherObject *
+sec_pkcs7_encoder_start_encrypt(SEC_PKCS7ContentInfo *cinfo,
+ PK11SymKey *orig_bulkkey)
+{
+ SECOidTag kind;
+ sec_PKCS7CipherObject *encryptobj;
+ SEC_PKCS7RecipientInfo **recipientinfos, *ri;
+ SEC_PKCS7EncryptedContentInfo *enccinfo;
+ SECKEYPublicKey *publickey = NULL;
+ SECKEYPrivateKey *ourPrivKey = NULL;
+ PK11SymKey *bulkkey;
+ void *mark;
+ int i;
+ PLArenaPool *arena = NULL;
+
+ kind = SEC_PKCS7ContentType(cinfo);
+ switch (kind) {
+ default:
+ case SEC_OID_PKCS7_DATA:
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ recipientinfos = NULL;
+ enccinfo = NULL;
+ break;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA: {
+ SEC_PKCS7EncryptedData *encdp;
+
+ /* To do EncryptedData we *must* be given a bulk key. */
+ PORT_Assert(orig_bulkkey != NULL);
+ if (orig_bulkkey == NULL) {
+ /* XXX error? */
+ return NULL;
+ }
+
+ encdp = cinfo->content.encryptedData;
+ recipientinfos = NULL;
+ enccinfo = &(encdp->encContentInfo);
+ } break;
+ case SEC_OID_PKCS7_ENVELOPED_DATA: {
+ SEC_PKCS7EnvelopedData *envdp;
+
+ envdp = cinfo->content.envelopedData;
+ recipientinfos = envdp->recipientInfos;
+ enccinfo = &(envdp->encContentInfo);
+ } break;
+ case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
+ SEC_PKCS7SignedAndEnvelopedData *saedp;
+
+ saedp = cinfo->content.signedAndEnvelopedData;
+ recipientinfos = saedp->recipientInfos;
+ enccinfo = &(saedp->encContentInfo);
+ } break;
+ }
+
+ if (enccinfo == NULL)
+ return NULL;
+
+ bulkkey = orig_bulkkey;
+ if (bulkkey == NULL) {
+ CK_MECHANISM_TYPE type = PK11_AlgtagToMechanism(enccinfo->encalg);
+ PK11SlotInfo *slot;
+
+ slot = PK11_GetBestSlot(type, cinfo->pwfn_arg);
+ if (slot == NULL) {
+ return NULL;
+ }
+ bulkkey = PK11_KeyGen(slot, type, NULL, enccinfo->keysize / 8,
+ cinfo->pwfn_arg);
+ PK11_FreeSlot(slot);
+ if (bulkkey == NULL) {
+ return NULL;
+ }
+ }
+
+ encryptobj = NULL;
+ mark = PORT_ArenaMark(cinfo->poolp);
+
+ /*
+ * Encrypt the bulk key with the public key of each recipient.
+ */
+ for (i = 0; recipientinfos && (ri = recipientinfos[i]) != NULL; i++) {
+ CERTCertificate *cert;
+ SECOidTag certalgtag, encalgtag;
+ SECStatus rv;
+ int data_len;
+ SECItem *params = NULL;
+
+ cert = ri->cert;
+ PORT_Assert(cert != NULL);
+ if (cert == NULL)
+ continue;
+
+ /*
+ * XXX Want an interface that takes a cert and some data and
+ * fills in an algorithmID and encrypts the data with the public
+ * key from the cert. Or, give me two interfaces -- one which
+ * gets the algorithm tag from a cert (I should not have to go
+ * down into the subjectPublicKeyInfo myself) and another which
+ * takes a public key and algorithm tag and data and encrypts
+ * the data. Or something like that. The point is that all
+ * of the following hardwired RSA stuff should be done elsewhere.
+ */
+
+ certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
+
+ switch (certalgtag) {
+ case SEC_OID_PKCS1_RSA_ENCRYPTION:
+ encalgtag = certalgtag;
+ publickey = CERT_ExtractPublicKey(cert);
+ if (publickey == NULL)
+ goto loser;
+
+ data_len = SECKEY_PublicKeyStrength(publickey);
+ ri->encKey.data =
+ (unsigned char *)PORT_ArenaAlloc(cinfo->poolp, data_len);
+ ri->encKey.len = data_len;
+ if (ri->encKey.data == NULL)
+ goto loser;
+
+ rv = PK11_PubWrapSymKey(PK11_AlgtagToMechanism(certalgtag), publickey,
+ bulkkey, &ri->encKey);
+
+ SECKEY_DestroyPublicKey(publickey);
+ publickey = NULL;
+ if (rv != SECSuccess)
+ goto loser;
+ params = NULL; /* paranoia */
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ goto loser;
+ }
+
+ rv = SECOID_SetAlgorithmID(cinfo->poolp, &ri->keyEncAlg, encalgtag,
+ params);
+ if (rv != SECSuccess)
+ goto loser;
+ if (arena)
+ PORT_FreeArena(arena, PR_FALSE);
+ arena = NULL;
+ }
+
+ encryptobj = sec_PKCS7CreateEncryptObject(cinfo->poolp, bulkkey,
+ enccinfo->encalg,
+ &(enccinfo->contentEncAlg));
+ if (encryptobj != NULL) {
+ PORT_ArenaUnmark(cinfo->poolp, mark);
+ mark = NULL; /* good one; do not want to release */
+ }
+ /* fallthru */
+
+loser:
+ if (arena) {
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ if (publickey) {
+ SECKEY_DestroyPublicKey(publickey);
+ }
+ if (ourPrivKey) {
+ SECKEY_DestroyPrivateKey(ourPrivKey);
+ }
+ if (mark != NULL) {
+ PORT_ArenaRelease(cinfo->poolp, mark);
+ }
+ if (orig_bulkkey == NULL) {
+ if (bulkkey)
+ PK11_FreeSymKey(bulkkey);
+ }
+
+ return encryptobj;
+}
+
+static void
+sec_pkcs7_encoder_notify(void *arg, PRBool before, void *dest, int depth)
+{
+ SEC_PKCS7EncoderContext *p7ecx;
+ SEC_PKCS7ContentInfo *cinfo;
+ SECOidTag kind;
+ PRBool before_content;
+
+ /*
+ * We want to notice just before the content field. After fields are
+ * not interesting to us.
+ */
+ if (!before)
+ return;
+
+ p7ecx = (SEC_PKCS7EncoderContext *)arg;
+ cinfo = p7ecx->cinfo;
+
+ before_content = PR_FALSE;
+
+ /*
+ * Watch for the content field, at which point we want to instruct
+ * the ASN.1 encoder to start taking bytes from the buffer.
+ *
+ * XXX The following assumes the inner content type is data;
+ * if/when we want to handle fully nested types, this will have
+ * to recurse until reaching the innermost data content.
+ */
+ kind = SEC_PKCS7ContentType(cinfo);
+ switch (kind) {
+ default:
+ case SEC_OID_PKCS7_DATA:
+ if (dest == &(cinfo->content.data))
+ before_content = PR_TRUE;
+ break;
+
+ case SEC_OID_PKCS7_DIGESTED_DATA: {
+ SEC_PKCS7DigestedData *digd;
+
+ digd = cinfo->content.digestedData;
+ if (digd == NULL)
+ break;
+
+ if (dest == &(digd->contentInfo.content))
+ before_content = PR_TRUE;
+ } break;
+
+ case SEC_OID_PKCS7_ENCRYPTED_DATA: {
+ SEC_PKCS7EncryptedData *encd;
+
+ encd = cinfo->content.encryptedData;
+ if (encd == NULL)
+ break;
+
+ if (dest == &(encd->encContentInfo.encContent))
+ before_content = PR_TRUE;
+ } break;
+
+ case SEC_OID_PKCS7_ENVELOPED_DATA: {
+ SEC_PKCS7EnvelopedData *envd;
+
+ envd = cinfo->content.envelopedData;
+ if (envd == NULL)
+ break;
+
+ if (dest == &(envd->encContentInfo.encContent))
+ before_content = PR_TRUE;
+ } break;
+
+ case SEC_OID_PKCS7_SIGNED_DATA: {
+ SEC_PKCS7SignedData *sigd;
+
+ sigd = cinfo->content.signedData;
+ if (sigd == NULL)
+ break;
+
+ if (dest == &(sigd->contentInfo.content))
+ before_content = PR_TRUE;
+ } break;
+
+ case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
+ SEC_PKCS7SignedAndEnvelopedData *saed;
+
+ saed = cinfo->content.signedAndEnvelopedData;
+ if (saed == NULL)
+ break;
+
+ if (dest == &(saed->encContentInfo.encContent))
+ before_content = PR_TRUE;
+ } break;
+ }
+
+ if (before_content) {
+ /*
+ * This will cause the next SEC_ASN1EncoderUpdate to take the
+ * contents bytes from the passed-in buffer.
+ */
+ SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx);
+ /*
+ * And that is all we needed this notify function for.
+ */
+ SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx);
+ }
+}
+
+static SEC_PKCS7EncoderContext *
+sec_pkcs7_encoder_start_contexts(SEC_PKCS7ContentInfo *cinfo,
+ PK11SymKey *bulkkey)
+{
+ SEC_PKCS7EncoderContext *p7ecx;
+ SECOidTag kind;
+ PRBool encrypt;
+ SECItem **digests;
+ SECAlgorithmID *digestalg, **digestalgs;
+
+ p7ecx =
+ (SEC_PKCS7EncoderContext *)PORT_ZAlloc(sizeof(SEC_PKCS7EncoderContext));
+ if (p7ecx == NULL)
+ return NULL;
+
+ digests = NULL;
+ digestalg = NULL;
+ digestalgs = NULL;
+ encrypt = PR_FALSE;
+
+ kind = SEC_PKCS7ContentType(cinfo);
+ switch (kind) {
+ default:
+ case SEC_OID_PKCS7_DATA:
+ break;
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ digestalg = &(cinfo->content.digestedData->digestAlg);
+ break;
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ digests = cinfo->content.signedData->digests;
+ digestalgs = cinfo->content.signedData->digestAlgorithms;
+ break;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ encrypt = PR_TRUE;
+ break;
+ case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
+ digests = cinfo->content.signedAndEnvelopedData->digests;
+ digestalgs = cinfo->content.signedAndEnvelopedData->digestAlgorithms;
+ encrypt = PR_TRUE;
+ break;
+ }
+
+ if (encrypt) {
+ p7ecx->encryptobj = sec_pkcs7_encoder_start_encrypt(cinfo, bulkkey);
+ if (p7ecx->encryptobj == NULL) {
+ PORT_Free(p7ecx);
+ return NULL;
+ }
+ }
+
+ if (digestalgs != NULL) {
+ if (digests != NULL) {
+ /* digests already created (probably for detached data) */
+ digestalg = NULL;
+ } else {
+ /*
+ * XXX Some day we should handle multiple digests; for now,
+ * assume only one will be done.
+ */
+ PORT_Assert(digestalgs[0] != NULL && digestalgs[1] == NULL);
+ digestalg = digestalgs[0];
+ }
+ }
+
+ if (digestalg != NULL) {
+ SECOidTag oidTag = SECOID_FindOIDTag(&(digestalg->algorithm));
+
+ p7ecx->digestobj = HASH_GetHashObjectByOidTag(oidTag);
+ if (p7ecx->digestobj != NULL) {
+ p7ecx->digestcx = (*p7ecx->digestobj->create)();
+ if (p7ecx->digestcx == NULL)
+ p7ecx->digestobj = NULL;
+ else
+ (*p7ecx->digestobj->begin)(p7ecx->digestcx);
+ }
+ if (p7ecx->digestobj == NULL) {
+ if (p7ecx->encryptobj != NULL)
+ sec_PKCS7DestroyEncryptObject(p7ecx->encryptobj);
+ PORT_Free(p7ecx);
+ return NULL;
+ }
+ }
+
+ p7ecx->cinfo = cinfo;
+ return p7ecx;
+}
+
+SEC_PKCS7EncoderContext *
+SEC_PKCS7EncoderStart(SEC_PKCS7ContentInfo *cinfo,
+ SEC_PKCS7EncoderOutputCallback outputfn,
+ void *outputarg,
+ PK11SymKey *bulkkey)
+{
+ SEC_PKCS7EncoderContext *p7ecx;
+ SECStatus rv;
+
+ p7ecx = sec_pkcs7_encoder_start_contexts(cinfo, bulkkey);
+ if (p7ecx == NULL)
+ return NULL;
+
+ p7ecx->output.outputfn = outputfn;
+ p7ecx->output.outputarg = outputarg;
+
+ /*
+ * Initialize the BER encoder.
+ */
+ p7ecx->ecx = SEC_ASN1EncoderStart(cinfo, sec_PKCS7ContentInfoTemplate,
+ sec_pkcs7_encoder_out, &(p7ecx->output));
+ if (p7ecx->ecx == NULL) {
+ PORT_Free(p7ecx);
+ return NULL;
+ }
+
+ /*
+ * Indicate that we are streaming. We will be streaming until we
+ * get past the contents bytes.
+ */
+ SEC_ASN1EncoderSetStreaming(p7ecx->ecx);
+
+ /*
+ * The notify function will watch for the contents field.
+ */
+ SEC_ASN1EncoderSetNotifyProc(p7ecx->ecx, sec_pkcs7_encoder_notify, p7ecx);
+
+ /*
+ * This will encode everything up to the content bytes. (The notify
+ * function will then cause the encoding to stop there.) Then our
+ * caller can start passing contents bytes to our Update, which we
+ * will pass along.
+ */
+ rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0);
+ if (rv != SECSuccess) {
+ PORT_Free(p7ecx);
+ return NULL;
+ }
+
+ return p7ecx;
+}
+
+/*
+ * XXX If/when we support nested contents, this needs to be revised.
+ */
+static SECStatus
+sec_pkcs7_encoder_work_data(SEC_PKCS7EncoderContext *p7ecx, SECItem *dest,
+ const unsigned char *data, unsigned long len,
+ PRBool final)
+{
+ unsigned char *buf = NULL;
+ SECStatus rv;
+
+ rv = SECSuccess; /* may as well be optimistic */
+
+ /*
+ * We should really have data to process, or we should be trying
+ * to finish/flush the last block. (This is an overly paranoid
+ * check since all callers are in this file and simple inspection
+ * proves they do it right. But it could find a bug in future
+ * modifications/development, that is why it is here.)
+ */
+ PORT_Assert((data != NULL && len) || final);
+
+ /*
+ * Update the running digest.
+ * XXX This needs modification if/when we handle multiple digests.
+ */
+ if (len && p7ecx->digestobj != NULL) {
+ (*p7ecx->digestobj->update)(p7ecx->digestcx, data, len);
+ }
+
+ /*
+ * Encrypt this chunk.
+ */
+ if (p7ecx->encryptobj != NULL) {
+ /* XXX the following lengths should all be longs? */
+ unsigned int inlen; /* length of data being encrypted */
+ unsigned int outlen; /* length of encrypted data */
+ unsigned int buflen; /* length available for encrypted data */
+
+ inlen = len;
+ buflen = sec_PKCS7EncryptLength(p7ecx->encryptobj, inlen, final);
+ if (buflen == 0) {
+ /*
+ * No output is expected, but the input data may be buffered
+ * so we still have to call Encrypt.
+ */
+ rv = sec_PKCS7Encrypt(p7ecx->encryptobj, NULL, &outlen, 0,
+ data, inlen, final);
+ if (final) {
+ len = 0;
+ goto done;
+ }
+ return rv;
+ }
+
+ if (dest != NULL)
+ buf = (unsigned char *)PORT_ArenaAlloc(p7ecx->cinfo->poolp, buflen);
+ else
+ buf = (unsigned char *)PORT_Alloc(buflen);
+
+ if (buf == NULL) {
+ rv = SECFailure;
+ } else {
+ rv = sec_PKCS7Encrypt(p7ecx->encryptobj, buf, &outlen, buflen,
+ data, inlen, final);
+ data = buf;
+ len = outlen;
+ }
+ if (rv != SECSuccess) {
+ if (final)
+ goto done;
+ return rv;
+ }
+ }
+
+ if (p7ecx->ecx != NULL) {
+ /*
+ * Encode the contents bytes.
+ */
+ if (len) {
+ rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, (const char *)data, len);
+ }
+ }
+
+done:
+ if (p7ecx->encryptobj != NULL) {
+ if (final)
+ sec_PKCS7DestroyEncryptObject(p7ecx->encryptobj);
+ if (dest != NULL) {
+ dest->data = buf;
+ dest->len = len;
+ } else if (buf != NULL) {
+ PORT_Free(buf);
+ }
+ }
+
+ if (final && p7ecx->digestobj != NULL) {
+ SECItem *digest, **digests, ***digestsp;
+ unsigned char *digdata;
+ SECOidTag kind;
+
+ kind = SEC_PKCS7ContentType(p7ecx->cinfo);
+ switch (kind) {
+ default:
+ PORT_Assert(0);
+ return SECFailure;
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ digest = &(p7ecx->cinfo->content.digestedData->digest);
+ digestsp = NULL;
+ break;
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ digest = NULL;
+ digestsp = &(p7ecx->cinfo->content.signedData->digests);
+ break;
+ case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
+ digest = NULL;
+ digestsp = &(p7ecx->cinfo->content.signedAndEnvelopedData->digests);
+ break;
+ }
+
+ digdata = (unsigned char *)PORT_ArenaAlloc(p7ecx->cinfo->poolp,
+ p7ecx->digestobj->length);
+ if (digdata == NULL)
+ return SECFailure;
+
+ if (digestsp != NULL) {
+ PORT_Assert(digest == NULL);
+
+ digest = (SECItem *)PORT_ArenaAlloc(p7ecx->cinfo->poolp,
+ sizeof(SECItem));
+ digests = (SECItem **)PORT_ArenaAlloc(p7ecx->cinfo->poolp,
+ 2 * sizeof(SECItem *));
+ if (digests == NULL || digest == NULL)
+ return SECFailure;
+
+ digests[0] = digest;
+ digests[1] = NULL;
+
+ *digestsp = digests;
+ }
+
+ PORT_Assert(digest != NULL);
+
+ digest->data = digdata;
+ digest->len = p7ecx->digestobj->length;
+
+ (*p7ecx->digestobj->end)(p7ecx->digestcx, digest->data,
+ &(digest->len), digest->len);
+ (*p7ecx->digestobj->destroy)(p7ecx->digestcx, PR_TRUE);
+ }
+
+ return rv;
+}
+
+SECStatus
+SEC_PKCS7EncoderUpdate(SEC_PKCS7EncoderContext *p7ecx,
+ const char *data, unsigned long len)
+{
+ /* XXX Error handling needs help. Return what? Do "Finish" on failure? */
+ return sec_pkcs7_encoder_work_data(p7ecx, NULL,
+ (const unsigned char *)data, len,
+ PR_FALSE);
+}
+
+static SECStatus
+sec_pkcs7_encoder_sig_and_certs(SEC_PKCS7ContentInfo *cinfo,
+ SECKEYGetPasswordKey pwfn, void *pwfnarg)
+{
+ SECOidTag kind;
+ CERTCertificate **certs;
+ CERTCertificateList **certlists;
+ SECAlgorithmID **digestalgs;
+ SECItem **digests;
+ SEC_PKCS7SignerInfo *signerinfo, **signerinfos;
+ SECItem **rawcerts, ***rawcertsp;
+ PLArenaPool *poolp;
+ int certcount;
+ int ci, cli, rci, si;
+
+ kind = SEC_PKCS7ContentType(cinfo);
+ switch (kind) {
+ default:
+ case SEC_OID_PKCS7_DATA:
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ certs = NULL;
+ certlists = NULL;
+ digestalgs = NULL;
+ digests = NULL;
+ signerinfos = NULL;
+ rawcertsp = NULL;
+ break;
+ case SEC_OID_PKCS7_SIGNED_DATA: {
+ SEC_PKCS7SignedData *sdp;
+
+ sdp = cinfo->content.signedData;
+ certs = sdp->certs;
+ certlists = sdp->certLists;
+ digestalgs = sdp->digestAlgorithms;
+ digests = sdp->digests;
+ signerinfos = sdp->signerInfos;
+ rawcertsp = &(sdp->rawCerts);
+ } break;
+ case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
+ SEC_PKCS7SignedAndEnvelopedData *saedp;
+
+ saedp = cinfo->content.signedAndEnvelopedData;
+ certs = saedp->certs;
+ certlists = saedp->certLists;
+ digestalgs = saedp->digestAlgorithms;
+ digests = saedp->digests;
+ signerinfos = saedp->signerInfos;
+ rawcertsp = &(saedp->rawCerts);
+ } break;
+ }
+
+ if (certs == NULL && certlists == NULL && signerinfos == NULL)
+ return SECSuccess; /* nothing for us to do! */
+
+ poolp = cinfo->poolp;
+ certcount = 0;
+
+ if (signerinfos != NULL) {
+ SECOidTag digestalgtag;
+ int di;
+ SECStatus rv;
+ CERTCertificate *cert;
+ SECKEYPrivateKey *privkey;
+ SECItem signature;
+ SECOidTag signalgtag;
+
+ PORT_Assert(digestalgs != NULL && digests != NULL);
+
+ /*
+ * If one fails, we bail right then. If we want to continue and
+ * try to do subsequent signatures, this loop, and the departures
+ * from it, will need to be reworked.
+ */
+ for (si = 0; signerinfos[si] != NULL; si++) {
+
+ signerinfo = signerinfos[si];
+
+ /* find right digest */
+ digestalgtag = SECOID_GetAlgorithmTag(&(signerinfo->digestAlg));
+ for (di = 0; digestalgs[di] != NULL; di++) {
+ /* XXX Should I be comparing more than the tag? */
+ if (digestalgtag == SECOID_GetAlgorithmTag(digestalgs[di]))
+ break;
+ }
+ if (digestalgs[di] == NULL) {
+ /* XXX oops; do what? set an error? */
+ return SECFailure;
+ }
+ PORT_Assert(digests[di] != NULL);
+
+ cert = signerinfo->cert;
+ privkey = PK11_FindKeyByAnyCert(cert, pwfnarg);
+ if (privkey == NULL)
+ return SECFailure;
+
+ /*
+ * XXX I think there should be a cert-level interface for this,
+ * so that I do not have to know about subjectPublicKeyInfo...
+ */
+ signalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
+
+ if (signerinfo->authAttr != NULL) {
+ SEC_PKCS7Attribute *attr;
+ SECItem encoded_attrs;
+ SECItem *dummy;
+ SECOidTag algid;
+
+ /*
+ * First, find and fill in the message digest attribute.
+ */
+ attr = sec_PKCS7FindAttribute(signerinfo->authAttr,
+ SEC_OID_PKCS9_MESSAGE_DIGEST,
+ PR_TRUE);
+ PORT_Assert(attr != NULL);
+ if (attr == NULL) {
+ SECKEY_DestroyPrivateKey(privkey);
+ return SECFailure;
+ }
+
+ /*
+ * XXX The second half of the following assertion prevents
+ * the encoder from being called twice on the same content.
+ * Either just remove the second half the assertion, or
+ * change the code to check if the value already there is
+ * the same as digests[di], whichever seems more right.
+ */
+ PORT_Assert(attr->values != NULL && attr->values[0] == NULL);
+ attr->values[0] = digests[di];
+
+ /*
+ * Before encoding, reorder the attributes so that when they
+ * are encoded, they will be conforming DER, which is required
+ * to have a specific order and that is what must be used for
+ * the hash/signature. We do this here, rather than building
+ * it into EncodeAttributes, because we do not want to do
+ * such reordering on incoming messages (which also uses
+ * EncodeAttributes) or our old signatures (and other "broken"
+ * implementations) will not verify. So, we want to guarantee
+ * that we send out good DER encodings of attributes, but not
+ * to expect to receive them.
+ */
+ rv = sec_PKCS7ReorderAttributes(signerinfo->authAttr);
+ if (rv != SECSuccess) {
+ SECKEY_DestroyPrivateKey(privkey);
+ return SECFailure;
+ }
+
+ encoded_attrs.data = NULL;
+ encoded_attrs.len = 0;
+ dummy = sec_PKCS7EncodeAttributes(NULL, &encoded_attrs,
+ &(signerinfo->authAttr));
+ if (dummy == NULL) {
+ SECKEY_DestroyPrivateKey(privkey);
+ return SECFailure;
+ }
+
+ algid = SEC_GetSignatureAlgorithmOidTag(privkey->keyType,
+ digestalgtag);
+ if (algid == SEC_OID_UNKNOWN) {
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ SECKEY_DestroyPrivateKey(privkey);
+ return SECFailure;
+ }
+ rv = SEC_SignData(&signature,
+ encoded_attrs.data, encoded_attrs.len,
+ privkey,
+ algid);
+ SECITEM_FreeItem(&encoded_attrs, PR_FALSE);
+ } else {
+ rv = SGN_Digest(privkey, digestalgtag, &signature,
+ digests[di]);
+ }
+
+ SECKEY_DestroyPrivateKey(privkey);
+
+ if (rv != SECSuccess)
+ return rv;
+
+ rv = SECITEM_CopyItem(poolp, &(signerinfo->encDigest), &signature);
+ if (rv != SECSuccess)
+ return rv;
+
+ SECITEM_FreeItem(&signature, PR_FALSE);
+
+ rv = SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg),
+ signalgtag, NULL);
+ if (rv != SECSuccess)
+ return SECFailure;
+
+ /*
+ * Count the cert chain for this signer.
+ */
+ if (signerinfo->certList != NULL)
+ certcount += signerinfo->certList->len;
+ }
+ }
+
+ if (certs != NULL) {
+ for (ci = 0; certs[ci] != NULL; ci++)
+ certcount++;
+ }
+
+ if (certlists != NULL) {
+ for (cli = 0; certlists[cli] != NULL; cli++)
+ certcount += certlists[cli]->len;
+ }
+
+ if (certcount == 0)
+ return SECSuccess; /* signing done; no certs */
+
+ /*
+ * Combine all of the certs and cert chains into rawcerts.
+ * Note: certcount is an upper bound; we may not need that many slots
+ * but we will allocate anyway to avoid having to do another pass.
+ * (The temporary space saving is not worth it.)
+ */
+ rawcerts = (SECItem **)PORT_ArenaAlloc(poolp,
+ (certcount + 1) * sizeof(SECItem *));
+ if (rawcerts == NULL)
+ return SECFailure;
+
+ /*
+ * XXX Want to check for duplicates and not add *any* cert that is
+ * already in the set. This will be more important when we start
+ * dealing with larger sets of certs, dual-key certs (signing and
+ * encryption), etc. For the time being we can slide by...
+ */
+ rci = 0;
+ if (signerinfos != NULL) {
+ for (si = 0; signerinfos[si] != NULL; si++) {
+ signerinfo = signerinfos[si];
+ for (ci = 0; ci < signerinfo->certList->len; ci++)
+ rawcerts[rci++] = &(signerinfo->certList->certs[ci]);
+ }
+ }
+
+ if (certs != NULL) {
+ for (ci = 0; certs[ci] != NULL; ci++)
+ rawcerts[rci++] = &(certs[ci]->derCert);
+ }
+
+ if (certlists != NULL) {
+ for (cli = 0; certlists[cli] != NULL; cli++) {
+ for (ci = 0; ci < certlists[cli]->len; ci++)
+ rawcerts[rci++] = &(certlists[cli]->certs[ci]);
+ }
+ }
+
+ rawcerts[rci] = NULL;
+ *rawcertsp = rawcerts;
+
+ return SECSuccess;
+}
+
+SECStatus
+SEC_PKCS7EncoderFinish(SEC_PKCS7EncoderContext *p7ecx,
+ SECKEYGetPasswordKey pwfn, void *pwfnarg)
+{
+ SECStatus rv;
+
+ /*
+ * Flush out any remaining data.
+ */
+ rv = sec_pkcs7_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE);
+
+ /*
+ * Turn off streaming stuff.
+ */
+ SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx);
+ SEC_ASN1EncoderClearStreaming(p7ecx->ecx);
+
+ if (rv != SECSuccess)
+ goto loser;
+
+ rv = sec_pkcs7_encoder_sig_and_certs(p7ecx->cinfo, pwfn, pwfnarg);
+ if (rv != SECSuccess)
+ goto loser;
+
+ rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0);
+
+loser:
+ SEC_ASN1EncoderFinish(p7ecx->ecx);
+ PORT_Free(p7ecx);
+ return rv;
+}
+
+/*
+ * Abort the ASN.1 stream. Used by pkcs 12
+ */
+void
+SEC_PKCS7EncoderAbort(SEC_PKCS7EncoderContext *p7ecx, int error)
+{
+ PORT_Assert(p7ecx);
+ SEC_ASN1EncoderAbort(p7ecx->ecx, error);
+}
+
+/*
+ * After this routine is called, the entire PKCS7 contentInfo is ready
+ * to be encoded. This is used internally, but can also be called from
+ * elsewhere for those who want to be able to just have pointers to
+ * the ASN1 template for pkcs7 contentInfo built into their own encodings.
+ */
+SECStatus
+SEC_PKCS7PrepareForEncode(SEC_PKCS7ContentInfo *cinfo,
+ PK11SymKey *bulkkey,
+ SECKEYGetPasswordKey pwfn,
+ void *pwfnarg)
+{
+ SEC_PKCS7EncoderContext *p7ecx;
+ SECItem *content, *enc_content;
+ SECStatus rv;
+
+ p7ecx = sec_pkcs7_encoder_start_contexts(cinfo, bulkkey);
+ if (p7ecx == NULL)
+ return SECFailure;
+
+ content = SEC_PKCS7GetContent(cinfo);
+
+ if (p7ecx->encryptobj != NULL) {
+ SECOidTag kind;
+ SEC_PKCS7EncryptedContentInfo *enccinfo;
+
+ kind = SEC_PKCS7ContentType(p7ecx->cinfo);
+ switch (kind) {
+ default:
+ PORT_Assert(0);
+ rv = SECFailure;
+ goto loser;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ enccinfo = &(p7ecx->cinfo->content.encryptedData->encContentInfo);
+ break;
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ enccinfo = &(p7ecx->cinfo->content.envelopedData->encContentInfo);
+ break;
+ case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
+ enccinfo = &(p7ecx->cinfo->content.signedAndEnvelopedData->encContentInfo);
+ break;
+ }
+ enc_content = &(enccinfo->encContent);
+ } else {
+ enc_content = NULL;
+ }
+
+ if (content != NULL && content->data != NULL && content->len) {
+ rv = sec_pkcs7_encoder_work_data(p7ecx, enc_content,
+ content->data, content->len, PR_TRUE);
+ if (rv != SECSuccess)
+ goto loser;
+ }
+
+ rv = sec_pkcs7_encoder_sig_and_certs(cinfo, pwfn, pwfnarg);
+
+loser:
+ PORT_Free(p7ecx);
+ return rv;
+}
+
+/*
+ * Encode a PKCS7 object, in one shot. All necessary components
+ * of the object must already be specified. Either the data has
+ * already been included (via SetContent), or the data is detached,
+ * or there is no data at all (certs-only).
+ *
+ * "cinfo" specifies the object to be encoded.
+ *
+ * "outputfn" is where the encoded bytes will be passed.
+ *
+ * "outputarg" is an opaque argument to the above callback.
+ *
+ * "bulkkey" specifies the bulk encryption key to use. This argument
+ * can be NULL if no encryption is being done, or if the bulk key should
+ * be generated internally (usually the case for EnvelopedData but never
+ * for EncryptedData, which *must* provide a bulk encryption key).
+ *
+ * "pwfn" is a callback for getting the password which protects the
+ * private key of the signer. This argument can be NULL if it is known
+ * that no signing is going to be done.
+ *
+ * "pwfnarg" is an opaque argument to the above callback.
+ */
+SECStatus
+SEC_PKCS7Encode(SEC_PKCS7ContentInfo *cinfo,
+ SEC_PKCS7EncoderOutputCallback outputfn,
+ void *outputarg,
+ PK11SymKey *bulkkey,
+ SECKEYGetPasswordKey pwfn,
+ void *pwfnarg)
+{
+ SECStatus rv;
+
+ rv = SEC_PKCS7PrepareForEncode(cinfo, bulkkey, pwfn, pwfnarg);
+ if (rv == SECSuccess) {
+ struct sec_pkcs7_encoder_output outputcx;
+
+ outputcx.outputfn = outputfn;
+ outputcx.outputarg = outputarg;
+
+ rv = SEC_ASN1Encode(cinfo, sec_PKCS7ContentInfoTemplate,
+ sec_pkcs7_encoder_out, &outputcx);
+ }
+
+ return rv;
+}
+
+/*
+ * Encode a PKCS7 object, in one shot. All necessary components
+ * of the object must already be specified. Either the data has
+ * already been included (via SetContent), or the data is detached,
+ * or there is no data at all (certs-only). The output, rather than
+ * being passed to an output function as is done above, is all put
+ * into a SECItem.
+ *
+ * "pool" specifies a pool from which to allocate the result.
+ * It can be NULL, in which case memory is allocated generically.
+ *
+ * "dest" specifies a SECItem in which to put the result data.
+ * It can be NULL, in which case the entire item is allocated, too.
+ *
+ * "cinfo" specifies the object to be encoded.
+ *
+ * "bulkkey" specifies the bulk encryption key to use. This argument
+ * can be NULL if no encryption is being done, or if the bulk key should
+ * be generated internally (usually the case for EnvelopedData but never
+ * for EncryptedData, which *must* provide a bulk encryption key).
+ *
+ * "pwfn" is a callback for getting the password which protects the
+ * private key of the signer. This argument can be NULL if it is known
+ * that no signing is going to be done.
+ *
+ * "pwfnarg" is an opaque argument to the above callback.
+ */
+SECItem *
+SEC_PKCS7EncodeItem(PLArenaPool *pool,
+ SECItem *dest,
+ SEC_PKCS7ContentInfo *cinfo,
+ PK11SymKey *bulkkey,
+ SECKEYGetPasswordKey pwfn,
+ void *pwfnarg)
+{
+ SECStatus rv;
+
+ rv = SEC_PKCS7PrepareForEncode(cinfo, bulkkey, pwfn, pwfnarg);
+ if (rv != SECSuccess)
+ return NULL;
+
+ return SEC_ASN1EncodeItem(pool, dest, cinfo, sec_PKCS7ContentInfoTemplate);
+}