/* 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);
}
