/* 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 decoding, verification.
 */

#include "p7local.h"

#include "cert.h"
/* XXX do not want to have to include */
#include "certdb.h" /* certdb.h -- the trust stuff needed by */
                    /* the add certificate code needs to get */
                    /* rewritten/abstracted and then this */
                    /* include should be removed! */
/*#include "cdbhdl.h" */
#include "cryptohi.h"
#include "key.h"
#include "secasn1.h"
#include "secitem.h"
#include "secoid.h"
#include "pk11func.h"
#include "prtime.h"
#include "secerr.h"
#include "sechash.h" /* for HASH_GetHashObject() */
#include "secder.h"
#include "secpkcs5.h"

struct sec_pkcs7_decoder_worker {
    int depth;
    int digcnt;
    void **digcxs;
    const SECHashObject **digobjs;
    sec_PKCS7CipherObject *decryptobj;
    PRBool saw_contents;
};

struct SEC_PKCS7DecoderContextStr {
    SEC_ASN1DecoderContext *dcx;
    SEC_PKCS7ContentInfo *cinfo;
    SEC_PKCS7DecoderContentCallback cb;
    void *cb_arg;
    SECKEYGetPasswordKey pwfn;
    void *pwfn_arg;
    struct sec_pkcs7_decoder_worker worker;
    PLArenaPool *tmp_poolp;
    int error;
    SEC_PKCS7GetDecryptKeyCallback dkcb;
    void *dkcb_arg;
    SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb;
};

/*
 * Handle one worker, decrypting and digesting the data as necessary.
 *
 * XXX If/when we support nested contents, this probably needs to be
 * revised somewhat to get passed the content-info (which unfortunately
 * can be two different types depending on whether it is encrypted or not)
 * corresponding to the given worker.
 */
static void
sec_pkcs7_decoder_work_data(SEC_PKCS7DecoderContext *p7dcx,
                            struct sec_pkcs7_decoder_worker *worker,
                            const unsigned char *data, unsigned long len,
                            PRBool final)
{
    unsigned char *buf = NULL;
    SECStatus rv;
    int i;

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

    /*
     * Decrypt this chunk.
     *
     * XXX If we get an error, we do not want to do the digest or callback,
     * but we want to keep decoding.  Or maybe we want to stop decoding
     * altogether if there is a callback, because obviously we are not
     * sending the data back and they want to know that.
     */
    if (worker->decryptobj != NULL) {
        /* XXX the following lengths should all be longs? */
        unsigned int inlen;  /* length of data being decrypted */
        unsigned int outlen; /* length of decrypted data */
        unsigned int buflen; /* length available for decrypted data */
        SECItem *plain;

        inlen = len;
        buflen = sec_PKCS7DecryptLength(worker->decryptobj, inlen, final);
        if (buflen == 0) {
            if (inlen == 0) /* no input and no output */
                return;
            /*
             * No output is expected, but the input data may be buffered
             * so we still have to call Decrypt.
             */
            rv = sec_PKCS7Decrypt(worker->decryptobj, NULL, NULL, 0,
                                  data, inlen, final);
            if (rv != SECSuccess) {
                p7dcx->error = PORT_GetError();
                return; /* XXX indicate error? */
            }
            return;
        }

        if (p7dcx->cb != NULL) {
            buf = (unsigned char *)PORT_Alloc(buflen);
            plain = NULL;
        } else {
            unsigned long oldlen;

            /*
             * XXX This assumes one level of content only.
             * See comment above about nested content types.
             * XXX Also, it should work for signedAndEnvelopedData, too!
             */
            plain = &(p7dcx->cinfo->content.envelopedData->encContentInfo.plainContent);

            oldlen = plain->len;
            if (oldlen == 0) {
                buf = (unsigned char *)PORT_ArenaAlloc(p7dcx->cinfo->poolp,
                                                       buflen);
            } else {
                buf = (unsigned char *)PORT_ArenaGrow(p7dcx->cinfo->poolp,
                                                      plain->data,
                                                      oldlen, oldlen + buflen);
                if (buf != NULL)
                    buf += oldlen;
            }
            plain->data = buf;
        }
        if (buf == NULL) {
            p7dcx->error = SEC_ERROR_NO_MEMORY;
            return; /* XXX indicate error? */
        }
        rv = sec_PKCS7Decrypt(worker->decryptobj, buf, &outlen, buflen,
                              data, inlen, final);
        if (rv != SECSuccess) {
            p7dcx->error = PORT_GetError();
            return; /* XXX indicate error? */
        }
        if (plain != NULL) {
            PORT_Assert(final || outlen == buflen);
            plain->len += outlen;
        }
        data = buf;
        len = outlen;
    }

    /*
     * Update the running digests.
     */
    if (len) {
        for (i = 0; i < worker->digcnt; i++) {
            (*worker->digobjs[i]->update)(worker->digcxs[i], data, len);
        }
    }

    /*
     * Pass back the contents bytes, and free the temporary buffer.
     */
    if (p7dcx->cb != NULL) {
        if (len)
            (*p7dcx->cb)(p7dcx->cb_arg, (const char *)data, len);
        if (worker->decryptobj != NULL) {
            PORT_Assert(buf != NULL);
            PORT_Free(buf);
        }
    }
}

static void
sec_pkcs7_decoder_filter(void *arg, const char *data, unsigned long len,
                         int depth, SEC_ASN1EncodingPart data_kind)
{
    SEC_PKCS7DecoderContext *p7dcx;
    struct sec_pkcs7_decoder_worker *worker;

    /*
     * Since we do not handle any nested contents, the only bytes we
     * are really interested in are the actual contents bytes (not
     * the identifier, length, or end-of-contents bytes).  If we were
     * handling nested types we would probably need to do something
     * smarter based on depth and data_kind.
     */
    if (data_kind != SEC_ASN1_Contents)
        return;

    /*
     * The ASN.1 decoder should not even call us with a length of 0.
     * Just being paranoid.
     */
    PORT_Assert(len);
    if (len == 0)
        return;

    p7dcx = (SEC_PKCS7DecoderContext *)arg;

    /*
     * Handling nested contents would mean that there is a chain
     * of workers -- one per each level of content.  The following
     * would start with the first worker and loop over them.
     */
    worker = &(p7dcx->worker);

    worker->saw_contents = PR_TRUE;

    sec_pkcs7_decoder_work_data(p7dcx, worker,
                                (const unsigned char *)data, len, PR_FALSE);
}

/*
 * Create digest contexts for each algorithm in "digestalgs".
 * No algorithms is not an error, we just do not do anything.
 * An error (like trouble allocating memory), marks the error
 * in "p7dcx" and returns SECFailure, which means that our caller
 * should just give up altogether.
 */
static SECStatus
sec_pkcs7_decoder_start_digests(SEC_PKCS7DecoderContext *p7dcx, int depth,
                                SECAlgorithmID **digestalgs)
{
    int i, digcnt;

    if (digestalgs == NULL)
        return SECSuccess;

    /*
     * Count the algorithms.
     */
    digcnt = 0;
    while (digestalgs[digcnt] != NULL)
        digcnt++;

    /*
     * No algorithms means no work to do.
     * Just act as if there were no algorithms specified.
     */
    if (digcnt == 0)
        return SECSuccess;

    p7dcx->worker.digcxs = (void **)PORT_ArenaAlloc(p7dcx->tmp_poolp,
                                                    digcnt * sizeof(void *));
    p7dcx->worker.digobjs = (const SECHashObject **)PORT_ArenaAlloc(p7dcx->tmp_poolp,
                                                                    digcnt * sizeof(SECHashObject *));
    if (p7dcx->worker.digcxs == NULL || p7dcx->worker.digobjs == NULL) {
        p7dcx->error = SEC_ERROR_NO_MEMORY;
        return SECFailure;
    }

    p7dcx->worker.depth = depth;
    p7dcx->worker.digcnt = 0;

    /*
     * Create a digest context for each algorithm.
     */
    for (i = 0; i < digcnt; i++) {
        SECAlgorithmID *algid = digestalgs[i];
        SECOidTag oidTag = SECOID_FindOIDTag(&(algid->algorithm));
        const SECHashObject *digobj = HASH_GetHashObjectByOidTag(oidTag);
        void *digcx;

        /*
         * Skip any algorithm we do not even recognize; obviously,
         * this could be a problem, but if it is critical then the
         * result will just be that the signature does not verify.
         * We do not necessarily want to error out here, because
         * the particular algorithm may not actually be important,
         * but we cannot know that until later.
         */
        if (digobj == NULL) {
            p7dcx->worker.digcnt--;
            continue;
        }

        digcx = (*digobj->create)();
        if (digcx != NULL) {
            (*digobj->begin)(digcx);
            p7dcx->worker.digobjs[p7dcx->worker.digcnt] = digobj;
            p7dcx->worker.digcxs[p7dcx->worker.digcnt] = digcx;
            p7dcx->worker.digcnt++;
        }
    }

    if (p7dcx->worker.digcnt != 0)
        SEC_ASN1DecoderSetFilterProc(p7dcx->dcx,
                                     sec_pkcs7_decoder_filter,
                                     p7dcx,
                                     (PRBool)(p7dcx->cb != NULL));
    return SECSuccess;
}

/*
 * Close out all of the digest contexts, storing the results in "digestsp".
 */
static SECStatus
sec_pkcs7_decoder_finish_digests(SEC_PKCS7DecoderContext *p7dcx,
                                 PLArenaPool *poolp,
                                 SECItem ***digestsp)
{
    struct sec_pkcs7_decoder_worker *worker;
    const SECHashObject *digobj;
    void *digcx;
    SECItem **digests, *digest;
    int i;
    void *mark;

    /*
     * XXX Handling nested contents would mean that there is a chain
     * of workers -- one per each level of content.  The following
     * would want to find the last worker in the chain.
     */
    worker = &(p7dcx->worker);

    /*
     * If no digests, then we have nothing to do.
     */
    if (worker->digcnt == 0)
        return SECSuccess;

    /*
     * No matter what happens after this, we want to stop filtering.
     * XXX If we handle nested contents, we only want to stop filtering
     * if we are finishing off the *last* worker.
     */
    SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);

    /*
     * If we ended up with no contents, just destroy each
     * digest context -- they are meaningless and potentially
     * confusing, because their presence would imply some content
     * was digested.
     */
    if (!worker->saw_contents) {
        for (i = 0; i < worker->digcnt; i++) {
            digcx = worker->digcxs[i];
            digobj = worker->digobjs[i];
            (*digobj->destroy)(digcx, PR_TRUE);
        }
        return SECSuccess;
    }

    mark = PORT_ArenaMark(poolp);

    /*
     * Close out each digest context, saving digest away.
     */
    digests =
        (SECItem **)PORT_ArenaAlloc(poolp, (worker->digcnt + 1) * sizeof(SECItem *));
    digest = (SECItem *)PORT_ArenaAlloc(poolp, worker->digcnt * sizeof(SECItem));
    if (digests == NULL || digest == NULL) {
        p7dcx->error = PORT_GetError();
        PORT_ArenaRelease(poolp, mark);
        return SECFailure;
    }

    for (i = 0; i < worker->digcnt; i++, digest++) {
        digcx = worker->digcxs[i];
        digobj = worker->digobjs[i];

        digest->data = (unsigned char *)PORT_ArenaAlloc(poolp, digobj->length);
        if (digest->data == NULL) {
            p7dcx->error = PORT_GetError();
            PORT_ArenaRelease(poolp, mark);
            return SECFailure;
        }

        digest->len = digobj->length;
        (*digobj->end)(digcx, digest->data, &(digest->len), digest->len);
        (*digobj->destroy)(digcx, PR_TRUE);

        digests[i] = digest;
    }
    digests[i] = NULL;
    *digestsp = digests;

    PORT_ArenaUnmark(poolp, mark);
    return SECSuccess;
}

/*
 * XXX Need comment explaining following helper function (which is used
 * by sec_pkcs7_decoder_start_decrypt).
 */

static PK11SymKey *
sec_pkcs7_decoder_get_recipient_key(SEC_PKCS7DecoderContext *p7dcx,
                                    SEC_PKCS7RecipientInfo **recipientinfos,
                                    SEC_PKCS7EncryptedContentInfo *enccinfo)
{
    SEC_PKCS7RecipientInfo *ri;
    CERTCertificate *cert = NULL;
    SECKEYPrivateKey *privkey = NULL;
    PK11SymKey *bulkkey = NULL;
    SECOidTag keyalgtag, bulkalgtag, encalgtag;
    PK11SlotInfo *slot = NULL;

    if (recipientinfos == NULL || recipientinfos[0] == NULL) {
        p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT;
        goto no_key_found;
    }

    cert = PK11_FindCertAndKeyByRecipientList(&slot, recipientinfos, &ri,
                                              &privkey, p7dcx->pwfn_arg);
    if (cert == NULL) {
        p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT;
        goto no_key_found;
    }

    ri->cert = cert; /* so we can find it later */
    PORT_Assert(privkey != NULL);

    keyalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
    encalgtag = SECOID_GetAlgorithmTag(&(ri->keyEncAlg));
    if (keyalgtag != encalgtag) {
        p7dcx->error = SEC_ERROR_PKCS7_KEYALG_MISMATCH;
        goto no_key_found;
    }
    bulkalgtag = SECOID_GetAlgorithmTag(&(enccinfo->contentEncAlg));

    switch (encalgtag) {
        case SEC_OID_PKCS1_RSA_ENCRYPTION:
            bulkkey = PK11_PubUnwrapSymKey(privkey, &ri->encKey,
                                           PK11_AlgtagToMechanism(bulkalgtag),
                                           CKA_DECRYPT, 0);
            if (bulkkey == NULL) {
                p7dcx->error = PORT_GetError();
                PORT_SetError(0);
                goto no_key_found;
            }
            break;
        default:
            p7dcx->error = SEC_ERROR_UNSUPPORTED_KEYALG;
            break;
    }

no_key_found:
    if (privkey != NULL)
        SECKEY_DestroyPrivateKey(privkey);
    if (slot != NULL)
        PK11_FreeSlot(slot);

    return bulkkey;
}

/*
 * XXX The following comment is old -- the function used to only handle
 * EnvelopedData or SignedAndEnvelopedData but now handles EncryptedData
 * as well (and it had all of the code of the helper function above
 * built into it), though the comment was left as is.  Fix it...
 *
 * We are just about to decode the content of an EnvelopedData.
 * Set up a decryption context so we can decrypt as we go.
 * Presumably we are one of the recipients listed in "recipientinfos".
 * (XXX And if we are not, or if we have trouble, what should we do?
 *  It would be nice to let the decoding still work.  Maybe it should
 *  be an error if there is a content callback, but not an error otherwise?)
 * The encryption key and related information can be found in "enccinfo".
 */
static SECStatus
sec_pkcs7_decoder_start_decrypt(SEC_PKCS7DecoderContext *p7dcx, int depth,
                                SEC_PKCS7RecipientInfo **recipientinfos,
                                SEC_PKCS7EncryptedContentInfo *enccinfo,
                                PK11SymKey **copy_key_for_signature)
{
    PK11SymKey *bulkkey = NULL;
    sec_PKCS7CipherObject *decryptobj;

    /*
     * If a callback is supplied to retrieve the encryption key,
     * for instance, for Encrypted Content infos, then retrieve
     * the bulkkey from the callback.  Otherwise, assume that
     * we are processing Enveloped or SignedAndEnveloped data
     * content infos.
     *
     * XXX Put an assert here?
     */
    if (SEC_PKCS7ContentType(p7dcx->cinfo) == SEC_OID_PKCS7_ENCRYPTED_DATA) {
        if (p7dcx->dkcb != NULL) {
            bulkkey = (*p7dcx->dkcb)(p7dcx->dkcb_arg,
                                     &(enccinfo->contentEncAlg));
        }
        enccinfo->keysize = 0;
    } else {
        bulkkey = sec_pkcs7_decoder_get_recipient_key(p7dcx, recipientinfos,
                                                      enccinfo);
        if (bulkkey == NULL)
            goto no_decryption;
        enccinfo->keysize = PK11_GetKeyStrength(bulkkey,
                                                &(enccinfo->contentEncAlg));
    }

    /*
     * XXX I think following should set error in p7dcx and clear set error
     * (as used to be done here, or as is done in get_receipient_key above.
     */
    if (bulkkey == NULL) {
        goto no_decryption;
    }

    /*
     * We want to make sure decryption is allowed.  This is done via
     * a callback specified in SEC_PKCS7DecoderStart().
     */
    if (p7dcx->decrypt_allowed_cb) {
        if ((*p7dcx->decrypt_allowed_cb)(&(enccinfo->contentEncAlg),
                                         bulkkey) == PR_FALSE) {
            p7dcx->error = SEC_ERROR_DECRYPTION_DISALLOWED;
            goto no_decryption;
        }
    } else {
        p7dcx->error = SEC_ERROR_DECRYPTION_DISALLOWED;
        goto no_decryption;
    }

    /*
     * When decrypting a signedAndEnvelopedData, the signature also has
     * to be decrypted with the bulk encryption key; to avoid having to
     * get it all over again later (and do another potentially expensive
     * RSA operation), copy it for later signature verification to use.
     */
    if (copy_key_for_signature != NULL)
        *copy_key_for_signature = PK11_ReferenceSymKey(bulkkey);

    /*
     * Now we have the bulk encryption key (in bulkkey) and the
     * the algorithm (in enccinfo->contentEncAlg).  Using those,
     * create a decryption context.
     */
    decryptobj = sec_PKCS7CreateDecryptObject(bulkkey,
                                              &(enccinfo->contentEncAlg));

    /*
     * We are done with (this) bulkkey now.
     */
    PK11_FreeSymKey(bulkkey);

    if (decryptobj == NULL) {
        p7dcx->error = PORT_GetError();
        PORT_SetError(0);
        goto no_decryption;
    }

    SEC_ASN1DecoderSetFilterProc(p7dcx->dcx,
                                 sec_pkcs7_decoder_filter,
                                 p7dcx,
                                 (PRBool)(p7dcx->cb != NULL));

    p7dcx->worker.depth = depth;
    p7dcx->worker.decryptobj = decryptobj;

    return SECSuccess;

no_decryption:
    /*
     * For some reason (error set already, if appropriate), we cannot
     * decrypt the content.  I am not sure what exactly is the right
     * thing to do here; in some cases we want to just stop, and in
     * others we want to let the decoding finish even though we cannot
     * decrypt the content.  My current thinking is that if the caller
     * set up a content callback, then they are really interested in
     * getting (decrypted) content, and if they cannot they will want
     * to know about it.  However, if no callback was specified, then
     * maybe it is not important that the decryption failed.
     */
    if (p7dcx->cb != NULL)
        return SECFailure;
    else
        return SECSuccess; /* Let the decoding continue. */
}

static SECStatus
sec_pkcs7_decoder_finish_decrypt(SEC_PKCS7DecoderContext *p7dcx,
                                 PLArenaPool *poolp,
                                 SEC_PKCS7EncryptedContentInfo *enccinfo)
{
    struct sec_pkcs7_decoder_worker *worker;

    /*
     * XXX Handling nested contents would mean that there is a chain
     * of workers -- one per each level of content.  The following
     * would want to find the last worker in the chain.
     */
    worker = &(p7dcx->worker);

    /*
     * If no decryption context, then we have nothing to do.
     */
    if (worker->decryptobj == NULL)
        return SECSuccess;

    /*
     * No matter what happens after this, we want to stop filtering.
     * XXX If we handle nested contents, we only want to stop filtering
     * if we are finishing off the *last* worker.
     */
    SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);

    /*
     * Handle the last block.
     */
    sec_pkcs7_decoder_work_data(p7dcx, worker, NULL, 0, PR_TRUE);

    /*
     * All done, destroy it.
     */
    sec_PKCS7DestroyDecryptObject(worker->decryptobj);
    worker->decryptobj = NULL;

    return SECSuccess;
}

static void
sec_pkcs7_decoder_notify(void *arg, PRBool before, void *dest, int depth)
{
    SEC_PKCS7DecoderContext *p7dcx;
    SEC_PKCS7ContentInfo *cinfo;
    SEC_PKCS7SignedData *sigd;
    SEC_PKCS7EnvelopedData *envd;
    SEC_PKCS7SignedAndEnvelopedData *saed;
    SEC_PKCS7EncryptedData *encd;
    SEC_PKCS7DigestedData *digd;
    PRBool after;
    SECStatus rv;

    /*
     * Just to make the code easier to read, create an "after" variable
     * that is equivalent to "not before".
     * (This used to be just the statement "after = !before", but that
     * causes a warning on the mac; to avoid that, we do it the long way.)
     */
    if (before)
        after = PR_FALSE;
    else
        after = PR_TRUE;

    p7dcx = (SEC_PKCS7DecoderContext *)arg;
    if (!p7dcx) {
        return;
    }

    cinfo = p7dcx->cinfo;

    if (!cinfo) {
        return;
    }

    if (cinfo->contentTypeTag == NULL) {
        if (after && dest == &(cinfo->contentType))
            cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType));
        return;
    }

    switch (cinfo->contentTypeTag->offset) {
        case SEC_OID_PKCS7_SIGNED_DATA:
            sigd = cinfo->content.signedData;
            if (sigd == NULL)
                break;

            if (sigd->contentInfo.contentTypeTag == NULL) {
                if (after && dest == &(sigd->contentInfo.contentType))
                    sigd->contentInfo.contentTypeTag =
                        SECOID_FindOID(&(sigd->contentInfo.contentType));
                break;
            }

            /*
             * We only set up a filtering digest if the content is
             * plain DATA; anything else needs more work because a
             * second pass is required to produce a DER encoding from
             * an input that can be BER encoded.  (This is a requirement
             * of PKCS7 that is unfortunate, but there you have it.)
             *
             * XXX Also, since we stop here if this is not DATA, the
             * inner content is not getting processed at all.  Someday
             * we may want to fix that.
             */
            if (sigd->contentInfo.contentTypeTag->offset != SEC_OID_PKCS7_DATA) {
                /* XXX Set an error in p7dcx->error */
                SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
                break;
            }

            /*
             * Just before the content, we want to set up a digest context
             * for each digest algorithm listed, and start a filter which
             * will run all of the contents bytes through that digest.
             */
            if (before && dest == &(sigd->contentInfo.content)) {
                rv = sec_pkcs7_decoder_start_digests(p7dcx, depth,
                                                     sigd->digestAlgorithms);
                if (rv != SECSuccess)
                    SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);

                break;
            }

            /*
             * XXX To handle nested types, here is where we would want
             * to check for inner boundaries that need handling.
             */

            /*
             * Are we done?
             */
            if (after && dest == &(sigd->contentInfo.content)) {
                /*
                 * Close out the digest contexts.  We ignore any error
                 * because we are stopping anyway; the error status left
                 * behind in p7dcx will be seen by outer functions.
                 */
                (void)sec_pkcs7_decoder_finish_digests(p7dcx, cinfo->poolp,
                                                       &(sigd->digests));

                /*
                 * XXX To handle nested contents, we would need to remove
                 * the worker from the chain (and free it).
                 */

                /*
                 * Stop notify.
                 */
                SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
            }
            break;

        case SEC_OID_PKCS7_ENVELOPED_DATA:
            envd = cinfo->content.envelopedData;
            if (envd == NULL)
                break;

            if (envd->encContentInfo.contentTypeTag == NULL) {
                if (after && dest == &(envd->encContentInfo.contentType))
                    envd->encContentInfo.contentTypeTag =
                        SECOID_FindOID(&(envd->encContentInfo.contentType));
                break;
            }

            /*
             * Just before the content, we want to set up a decryption
             * context, and start a filter which will run all of the
             * contents bytes through it to determine the plain content.
             */
            if (before && dest == &(envd->encContentInfo.encContent)) {
                rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth,
                                                     envd->recipientInfos,
                                                     &(envd->encContentInfo),
                                                     NULL);
                if (rv != SECSuccess)
                    SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);

                break;
            }

            /*
             * Are we done?
             */
            if (after && dest == &(envd->encContentInfo.encContent)) {
                /*
             * Close out the decryption context.  We ignore any error
             * because we are stopping anyway; the error status left
             * behind in p7dcx will be seen by outer functions.
             */
                (void)sec_pkcs7_decoder_finish_decrypt(p7dcx, cinfo->poolp,
                                                       &(envd->encContentInfo));

                /*
                 * XXX To handle nested contents, we would need to remove
                 * the worker from the chain (and free it).
                 */

                /*
                 * Stop notify.
                 */
                SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
            }
            break;

        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
            saed = cinfo->content.signedAndEnvelopedData;
            if (saed == NULL)
                break;

            if (saed->encContentInfo.contentTypeTag == NULL) {
                if (after && dest == &(saed->encContentInfo.contentType))
                    saed->encContentInfo.contentTypeTag =
                        SECOID_FindOID(&(saed->encContentInfo.contentType));
                break;
            }

            /*
             * Just before the content, we want to set up a decryption
             * context *and* digest contexts, and start a filter which
             * will run all of the contents bytes through both.
             */
            if (before && dest == &(saed->encContentInfo.encContent)) {
                rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth,
                                                     saed->recipientInfos,
                                                     &(saed->encContentInfo),
                                                     &(saed->sigKey));
                if (rv == SECSuccess)
                    rv = sec_pkcs7_decoder_start_digests(p7dcx, depth,
                                                         saed->digestAlgorithms);
                if (rv != SECSuccess)
                    SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);

                break;
            }

            /*
             * Are we done?
             */
            if (after && dest == &(saed->encContentInfo.encContent)) {
                /*
                 * Close out the decryption and digests contexts.
                 * We ignore any errors because we are stopping anyway;
                 * the error status left behind in p7dcx will be seen by
                 * outer functions.
                 *
                 * Note that the decrypt stuff must be called first;
                 * it may have a last buffer to do which in turn has
                 * to be added to the digest.
                 */
                (void)sec_pkcs7_decoder_finish_decrypt(p7dcx, cinfo->poolp,
                                                       &(saed->encContentInfo));
                (void)sec_pkcs7_decoder_finish_digests(p7dcx, cinfo->poolp,
                                                       &(saed->digests));

                /*
                 * XXX To handle nested contents, we would need to remove
                 * the worker from the chain (and free it).
                 */

                /*
                 * Stop notify.
                 */
                SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
            }
            break;

        case SEC_OID_PKCS7_DIGESTED_DATA:
            digd = cinfo->content.digestedData;

            /*
             * XXX Want to do the digest or not?  Maybe future enhancement...
             */
            if (before && dest == &(digd->contentInfo.content.data)) {
                SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, sec_pkcs7_decoder_filter,
                                             p7dcx,
                                             (PRBool)(p7dcx->cb != NULL));
                break;
            }

            /*
             * Are we done?
             */
            if (after && dest == &(digd->contentInfo.content.data)) {
                SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
            }
            break;

        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            encd = cinfo->content.encryptedData;

            if (!encd) {
                break;
            }

            /*
             * XXX If the decryption key callback is set, we want to start
             * the decryption.  If the callback is not set, we will treat the
             * content as plain data, since we do not have the key.
             *
             * Is this the proper thing to do?
             */
            if (before && dest == &(encd->encContentInfo.encContent)) {
                /*
                 * Start the encryption process if the decryption key callback
                 * is present.  Otherwise, treat the content like plain data.
                 */
                rv = SECSuccess;
                if (p7dcx->dkcb != NULL) {
                    rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth, NULL,
                                                         &(encd->encContentInfo),
                                                         NULL);
                }

                if (rv != SECSuccess)
                    SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);

                break;
            }

            /*
             * Are we done?
             */
            if (after && dest == &(encd->encContentInfo.encContent)) {
                /*
                 * Close out the decryption context.  We ignore any error
                 * because we are stopping anyway; the error status left
                 * behind in p7dcx will be seen by outer functions.
                 */
                (void)sec_pkcs7_decoder_finish_decrypt(p7dcx, cinfo->poolp,
                                                       &(encd->encContentInfo));

                /*
                 * Stop notify.
                 */
                SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
            }
            break;

        case SEC_OID_PKCS7_DATA:
            /*
             * If a output callback has been specified, we want to set the filter
             * to call the callback.  This is taken care of in
             * sec_pkcs7_decoder_start_decrypt() or
             * sec_pkcs7_decoder_start_digests() for the other content types.
             */

            if (before && dest == &(cinfo->content.data)) {

                /*
                 * Set the filter proc up.
                 */
                SEC_ASN1DecoderSetFilterProc(p7dcx->dcx,
                                             sec_pkcs7_decoder_filter,
                                             p7dcx,
                                             (PRBool)(p7dcx->cb != NULL));
                break;
            }

            if (after && dest == &(cinfo->content.data)) {
                /*
                 * Time to clean up after ourself, stop the Notify and Filter
                 * procedures.
                 */
                SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
                SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
            }
            break;

        default:
            SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx);
            break;
    }
}

SEC_PKCS7DecoderContext *
SEC_PKCS7DecoderStart(SEC_PKCS7DecoderContentCallback cb, void *cb_arg,
                      SECKEYGetPasswordKey pwfn, void *pwfn_arg,
                      SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb,
                      void *decrypt_key_cb_arg,
                      SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb)
{
    SEC_PKCS7DecoderContext *p7dcx;
    SEC_ASN1DecoderContext *dcx;
    SEC_PKCS7ContentInfo *cinfo;
    PLArenaPool *poolp;

    poolp = PORT_NewArena(1024); /* XXX what is right value? */
    if (poolp == NULL)
        return NULL;

    cinfo = (SEC_PKCS7ContentInfo *)PORT_ArenaZAlloc(poolp, sizeof(*cinfo));
    if (cinfo == NULL) {
        PORT_FreeArena(poolp, PR_FALSE);
        return NULL;
    }

    cinfo->poolp = poolp;
    cinfo->pwfn = pwfn;
    cinfo->pwfn_arg = pwfn_arg;
    cinfo->created = PR_FALSE;
    cinfo->refCount = 1;

    p7dcx =
        (SEC_PKCS7DecoderContext *)PORT_ZAlloc(sizeof(SEC_PKCS7DecoderContext));
    if (p7dcx == NULL) {
        PORT_FreeArena(poolp, PR_FALSE);
        return NULL;
    }

    p7dcx->tmp_poolp = PORT_NewArena(1024); /* XXX what is right value? */
    if (p7dcx->tmp_poolp == NULL) {
        PORT_Free(p7dcx);
        PORT_FreeArena(poolp, PR_FALSE);
        return NULL;
    }

    dcx = SEC_ASN1DecoderStart(poolp, cinfo, sec_PKCS7ContentInfoTemplate);
    if (dcx == NULL) {
        PORT_FreeArena(p7dcx->tmp_poolp, PR_FALSE);
        PORT_Free(p7dcx);
        PORT_FreeArena(poolp, PR_FALSE);
        return NULL;
    }

    SEC_ASN1DecoderSetNotifyProc(dcx, sec_pkcs7_decoder_notify, p7dcx);

    p7dcx->dcx = dcx;
    p7dcx->cinfo = cinfo;
    p7dcx->cb = cb;
    p7dcx->cb_arg = cb_arg;
    p7dcx->pwfn = pwfn;
    p7dcx->pwfn_arg = pwfn_arg;
    p7dcx->dkcb = decrypt_key_cb;
    p7dcx->dkcb_arg = decrypt_key_cb_arg;
    p7dcx->decrypt_allowed_cb = decrypt_allowed_cb;

    return p7dcx;
}

/*
 * Do the next chunk of PKCS7 decoding.  If there is a problem, set
 * an error and return a failure status.  Note that in the case of
 * an error, this routine is still prepared to be called again and
 * again in case that is the easiest route for our caller to take.
 * We simply detect it and do not do anything except keep setting
 * that error in case our caller has not noticed it yet...
 */
SECStatus
SEC_PKCS7DecoderUpdate(SEC_PKCS7DecoderContext *p7dcx,
                       const char *buf, unsigned long len)
{
    if (p7dcx->cinfo != NULL && p7dcx->dcx != NULL) {
        PORT_Assert(p7dcx->error == 0);
        if (p7dcx->error == 0) {
            if (SEC_ASN1DecoderUpdate(p7dcx->dcx, buf, len) != SECSuccess) {
                p7dcx->error = PORT_GetError();
                PORT_Assert(p7dcx->error);
                if (p7dcx->error == 0)
                    p7dcx->error = -1;
            }
        }
    }

    if (p7dcx->error) {
        if (p7dcx->dcx != NULL) {
            (void)SEC_ASN1DecoderFinish(p7dcx->dcx);
            p7dcx->dcx = NULL;
        }
        if (p7dcx->cinfo != NULL) {
            SEC_PKCS7DestroyContentInfo(p7dcx->cinfo);
            p7dcx->cinfo = NULL;
        }
        PORT_SetError(p7dcx->error);
        return SECFailure;
    }

    return SECSuccess;
}

SEC_PKCS7ContentInfo *
SEC_PKCS7DecoderFinish(SEC_PKCS7DecoderContext *p7dcx)
{
    SEC_PKCS7ContentInfo *cinfo;

    cinfo = p7dcx->cinfo;
    if (p7dcx->dcx != NULL) {
        if (SEC_ASN1DecoderFinish(p7dcx->dcx) != SECSuccess) {
            SEC_PKCS7DestroyContentInfo(cinfo);
            cinfo = NULL;
        }
    }
    /* free any NSS data structures */
    if (p7dcx->worker.decryptobj) {
        sec_PKCS7DestroyDecryptObject(p7dcx->worker.decryptobj);
    }
    PORT_FreeArena(p7dcx->tmp_poolp, PR_FALSE);
    PORT_Free(p7dcx);
    return cinfo;
}

SEC_PKCS7ContentInfo *
SEC_PKCS7DecodeItem(SECItem *p7item,
                    SEC_PKCS7DecoderContentCallback cb, void *cb_arg,
                    SECKEYGetPasswordKey pwfn, void *pwfn_arg,
                    SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb,
                    void *decrypt_key_cb_arg,
                    SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb)
{
    SEC_PKCS7DecoderContext *p7dcx;

    p7dcx = SEC_PKCS7DecoderStart(cb, cb_arg, pwfn, pwfn_arg, decrypt_key_cb,
                                  decrypt_key_cb_arg, decrypt_allowed_cb);
    if (!p7dcx) {
        /* error code is set */
        return NULL;
    }
    (void)SEC_PKCS7DecoderUpdate(p7dcx, (char *)p7item->data, p7item->len);
    return SEC_PKCS7DecoderFinish(p7dcx);
}

/*
 * Abort the ASN.1 stream. Used by pkcs 12
 */
void
SEC_PKCS7DecoderAbort(SEC_PKCS7DecoderContext *p7dcx, int error)
{
    PORT_Assert(p7dcx);
    SEC_ASN1DecoderAbort(p7dcx->dcx, error);
}

/*
 * If the thing contains any certs or crls return true; false otherwise.
 */
PRBool
SEC_PKCS7ContainsCertsOrCrls(SEC_PKCS7ContentInfo *cinfo)
{
    SECOidTag kind;
    SECItem **certs;
    CERTSignedCrl **crls;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        default:
        case SEC_OID_PKCS7_DATA:
        case SEC_OID_PKCS7_DIGESTED_DATA:
        case SEC_OID_PKCS7_ENVELOPED_DATA:
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            return PR_FALSE;
        case SEC_OID_PKCS7_SIGNED_DATA:
            certs = cinfo->content.signedData->rawCerts;
            crls = cinfo->content.signedData->crls;
            break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
            certs = cinfo->content.signedAndEnvelopedData->rawCerts;
            crls = cinfo->content.signedAndEnvelopedData->crls;
            break;
    }

    /*
     * I know this could be collapsed, but I was in a mood to be explicit.
     */
    if (certs != NULL && certs[0] != NULL)
        return PR_TRUE;
    else if (crls != NULL && crls[0] != NULL)
        return PR_TRUE;
    else
        return PR_FALSE;
}

/* return the content length...could use GetContent, however we
 * need the encrypted content length
 */
PRBool
SEC_PKCS7IsContentEmpty(SEC_PKCS7ContentInfo *cinfo, unsigned int minLen)
{
    SECItem *item = NULL;

    if (cinfo == NULL) {
        return PR_TRUE;
    }

    switch (SEC_PKCS7ContentType(cinfo)) {
        case SEC_OID_PKCS7_DATA:
            item = cinfo->content.data;
            break;
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            item = &cinfo->content.encryptedData->encContentInfo.encContent;
            break;
        default:
            /* add other types */
            return PR_FALSE;
    }

    if (!item) {
        return PR_TRUE;
    } else if (item->len <= minLen) {
        return PR_TRUE;
    }

    return PR_FALSE;
}

PRBool
SEC_PKCS7ContentIsEncrypted(SEC_PKCS7ContentInfo *cinfo)
{
    SECOidTag kind;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        default:
        case SEC_OID_PKCS7_DATA:
        case SEC_OID_PKCS7_DIGESTED_DATA:
        case SEC_OID_PKCS7_SIGNED_DATA:
            return PR_FALSE;
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
        case SEC_OID_PKCS7_ENVELOPED_DATA:
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
            return PR_TRUE;
    }
}

/*
 * If the PKCS7 content has a signature (not just *could* have a signature)
 * return true; false otherwise.  This can/should be called before calling
 * VerifySignature, which will always indicate failure if no signature is
 * present, but that does not mean there even was a signature!
 * Note that the content itself can be empty (detached content was sent
 * another way); it is the presence of the signature that matters.
 */
PRBool
SEC_PKCS7ContentIsSigned(SEC_PKCS7ContentInfo *cinfo)
{
    SECOidTag kind;
    SEC_PKCS7SignerInfo **signerinfos;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        default:
        case SEC_OID_PKCS7_DATA:
        case SEC_OID_PKCS7_DIGESTED_DATA:
        case SEC_OID_PKCS7_ENVELOPED_DATA:
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            return PR_FALSE;
        case SEC_OID_PKCS7_SIGNED_DATA:
            signerinfos = cinfo->content.signedData->signerInfos;
            break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
            signerinfos = cinfo->content.signedAndEnvelopedData->signerInfos;
            break;
    }

    /*
     * I know this could be collapsed; but I kind of think it will get
     * more complicated before I am finished, so...
     */
    if (signerinfos != NULL && signerinfos[0] != NULL)
        return PR_TRUE;
    else
        return PR_FALSE;
}

/*
 * sec_pkcs7_verify_signature
 *
 *      Look at a PKCS7 contentInfo and check if the signature is good.
 *      The digest was either calculated earlier (and is stored in the
 *      contentInfo itself) or is passed in via "detached_digest".
 *
 *      The verification checks that the signing cert is valid and trusted
 *      for the purpose specified by "certusage" at
 *      - "*atTime" if "atTime" is not null, or
 *      - the signing time if the signing time is available in "cinfo", or
 *      - the current time (as returned by PR_Now).
 *
 *      In addition, if "keepcerts" is true, add any new certificates found
 *      into our local database.
 *
 * XXX Each place which returns PR_FALSE should be sure to have a good
 * error set for inspection by the caller.  Alternatively, we could create
 * an enumeration of success and each type of failure and return that
 * instead of a boolean.  For now, the default in a bad situation is to
 * set the error to SEC_ERROR_PKCS7_BAD_SIGNATURE.  But this should be
 * reviewed; better (more specific) errors should be possible (to distinguish
 * a signature failure from a badly-formed pkcs7 signedData, for example).
 * Some of the errors should probably just be SEC_ERROR_BAD_SIGNATURE,
 * but that has a less helpful error string associated with it right now;
 * if/when that changes, review and change these as needed.
 *
 * XXX This is broken wrt signedAndEnvelopedData.  In that case, the
 * message digest is doubly encrypted -- first encrypted with the signer
 * private key but then again encrypted with the bulk encryption key used
 * to encrypt the content.  So before we can pass the digest to VerifyDigest,
 * we need to decrypt it with the bulk encryption key.  Also, in this case,
 * there should be NO authenticatedAttributes (signerinfo->authAttr should
 * be NULL).
 */
static PRBool
sec_pkcs7_verify_signature(SEC_PKCS7ContentInfo *cinfo,
                           SECCertUsage certusage,
                           const SECItem *detached_digest,
                           HASH_HashType digest_type,
                           PRBool keepcerts,
                           const PRTime *atTime)
{
    SECAlgorithmID **digestalgs, *bulkid;
    const SECItem *digest;
    SECItem **digests;
    SECItem **rawcerts;
    SEC_PKCS7SignerInfo **signerinfos, *signerinfo;
    CERTCertificate *cert, **certs;
    PRBool goodsig;
    CERTCertDBHandle *certdb, *defaultdb;
    SECOidTag encTag, digestTag;
    HASH_HashType found_type;
    int i, certcount;
    SECKEYPublicKey *publickey;
    SECItem *content_type;
    PK11SymKey *sigkey;
    SECItem *encoded_stime;
    PRTime stime;
    PRTime verificationTime;
    SECStatus rv;

    /*
     * Everything needed in order to "goto done" safely.
     */
    goodsig = PR_FALSE;
    certcount = 0;
    cert = NULL;
    certs = NULL;
    certdb = NULL;
    defaultdb = CERT_GetDefaultCertDB();
    publickey = NULL;

    if (!SEC_PKCS7ContentIsSigned(cinfo)) {
        PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
        goto done;
    }

    PORT_Assert(cinfo->contentTypeTag != NULL);

    switch (cinfo->contentTypeTag->offset) {
        default:
        case SEC_OID_PKCS7_DATA:
        case SEC_OID_PKCS7_DIGESTED_DATA:
        case SEC_OID_PKCS7_ENVELOPED_DATA:
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            /* Could only get here if SEC_PKCS7ContentIsSigned is broken. */
            PORT_Assert(0);
        case SEC_OID_PKCS7_SIGNED_DATA: {
            SEC_PKCS7SignedData *sdp;

            sdp = cinfo->content.signedData;
            digestalgs = sdp->digestAlgorithms;
            digests = sdp->digests;
            rawcerts = sdp->rawCerts;
            signerinfos = sdp->signerInfos;
            content_type = &(sdp->contentInfo.contentType);
            sigkey = NULL;
            bulkid = NULL;
        } break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
            SEC_PKCS7SignedAndEnvelopedData *saedp;

            saedp = cinfo->content.signedAndEnvelopedData;
            digestalgs = saedp->digestAlgorithms;
            digests = saedp->digests;
            rawcerts = saedp->rawCerts;
            signerinfos = saedp->signerInfos;
            content_type = &(saedp->encContentInfo.contentType);
            sigkey = saedp->sigKey;
            bulkid = &(saedp->encContentInfo.contentEncAlg);
        } break;
    }

    if ((signerinfos == NULL) || (signerinfos[0] == NULL)) {
        PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
        goto done;
    }

    /*
     * XXX Need to handle multiple signatures; checking them is easy,
     * but what should be the semantics here (like, return value)?
     */
    if (signerinfos[1] != NULL) {
        PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
        goto done;
    }

    signerinfo = signerinfos[0];

    /*
     * XXX I would like to just pass the issuerAndSN, along with the rawcerts
     * and crls, to some function that did all of this certificate stuff
     * (open/close the database if necessary, verifying the certs, etc.)
     * and gave me back a cert pointer if all was good.
     */
    certdb = defaultdb;
    if (certdb == NULL) {
        goto done;
    }

    certcount = 0;
    if (rawcerts != NULL) {
        for (; rawcerts[certcount] != NULL; certcount++) {
            /* just counting */
        }
    }

    /*
     * Note that the result of this is that each cert in "certs"
     * needs to be destroyed.
     */
    rv = CERT_ImportCerts(certdb, certusage, certcount, rawcerts, &certs,
                          keepcerts, PR_FALSE, NULL);
    if (rv != SECSuccess) {
        goto done;
    }

    /*
     * This cert will also need to be freed, but since we save it
     * in signerinfo for later, we do not want to destroy it when
     * we leave this function -- we let the clean-up of the entire
     * cinfo structure later do the destroy of this cert.
     */
    cert = CERT_FindCertByIssuerAndSN(certdb, signerinfo->issuerAndSN);
    if (cert == NULL) {
        goto done;
    }

    signerinfo->cert = cert;

    /*
     * Get and convert the signing time; if available, it will be used
     * both on the cert verification and for importing the sender
     * email profile.
     */
    encoded_stime = SEC_PKCS7GetSigningTime(cinfo);
    if (encoded_stime != NULL) {
        if (DER_DecodeTimeChoice(&stime, encoded_stime) != SECSuccess)
            encoded_stime = NULL; /* conversion failed, so pretend none */
    }

    /*
     * XXX  This uses the signing time, if available.  Additionally, we
     * might want to, if there is no signing time, get the message time
     * from the mail header itself, and use that.  That would require
     * a change to our interface though, and for S/MIME callers to pass
     * in a time (and for non-S/MIME callers to pass in nothing, or
     * maybe make them pass in the current time, always?).
     */
    if (atTime) {
        verificationTime = *atTime;
    } else if (encoded_stime != NULL) {
        verificationTime = stime;
    } else {
        verificationTime = PR_Now();
    }
    if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, verificationTime,
                        cinfo->pwfn_arg, NULL) != SECSuccess) {
        /*
         * XXX Give the user an option to check the signature anyway?
         * If we want to do this, need to give a way to leave and display
         * some dialog and get the answer and come back through (or do
         * the rest of what we do below elsewhere, maybe by putting it
         * in a function that we call below and could call from a dialog
         * finish handler).
         */
        goto savecert;
    }

    publickey = CERT_ExtractPublicKey(cert);
    if (publickey == NULL)
        goto done;

    /*
     * XXX No!  If digests is empty, see if we can create it now by
     * digesting the contents.  This is necessary if we want to allow
     * somebody to do a simple decode (without filtering, etc.) and
     * then later call us here to do the verification.
     * OR, we can just specify that the interface to this routine
     * *requires* that the digest(s) be done before calling and either
     * stashed in the struct itself or passed in explicitly (as would
     * be done for detached contents).
     */
    if ((digests == NULL || digests[0] == NULL) && (detached_digest == NULL || detached_digest->data == NULL))
        goto done;

    /*
     * Find and confirm digest algorithm.
     */
    digestTag = SECOID_FindOIDTag(&(signerinfo->digestAlg.algorithm));

    /* make sure we understand the digest type first */
    found_type = HASH_GetHashTypeByOidTag(digestTag);
    if ((digestTag == SEC_OID_UNKNOWN) || (found_type == HASH_AlgNULL)) {
        PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
        goto done;
    }

    if (detached_digest != NULL) {
        unsigned int hashLen = HASH_ResultLen(found_type);

        if (digest_type != found_type ||
            detached_digest->len != hashLen) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }
        digest = detached_digest;
    } else {
        PORT_Assert(digestalgs != NULL && digestalgs[0] != NULL);
        if (digestalgs == NULL || digestalgs[0] == NULL) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        /*
         * pick digest matching signerinfo->digestAlg from digests
         */
        for (i = 0; digestalgs[i] != NULL; i++) {
            if (SECOID_FindOIDTag(&(digestalgs[i]->algorithm)) == digestTag)
                break;
        }
        if (digestalgs[i] == NULL) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        digest = digests[i];
    }

    encTag = SECOID_FindOIDTag(&(signerinfo->digestEncAlg.algorithm));
    if (encTag == SEC_OID_UNKNOWN) {
        PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
        goto done;
    }

    if (signerinfo->authAttr != NULL) {
        SEC_PKCS7Attribute *attr;
        SECItem *value;
        SECItem encoded_attrs;

        /*
         * We have a sigkey only for signedAndEnvelopedData, which is
         * not supposed to have any authenticated attributes.
         */
        if (sigkey != NULL) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        /*
         * PKCS #7 says that if there are any authenticated attributes,
         * then there must be one for content type which matches the
         * content type of the content being signed, and there must
         * be one for message digest which matches our message digest.
         * So check these things first.
         * XXX Might be nice to have a compare-attribute-value function
         * which could collapse the following nicely.
         */
        attr = sec_PKCS7FindAttribute(signerinfo->authAttr,
                                      SEC_OID_PKCS9_CONTENT_TYPE, PR_TRUE);
        value = sec_PKCS7AttributeValue(attr);
        if (value == NULL || value->len != content_type->len) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }
        if (PORT_Memcmp(value->data, content_type->data, value->len) != 0) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        attr = sec_PKCS7FindAttribute(signerinfo->authAttr,
                                      SEC_OID_PKCS9_MESSAGE_DIGEST, PR_TRUE);
        value = sec_PKCS7AttributeValue(attr);
        if (value == NULL || value->len != digest->len) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }
        if (PORT_Memcmp(value->data, digest->data, value->len) != 0) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        /*
         * Okay, we met the constraints of the basic attributes.
         * Now check the signature, which is based on a digest of
         * the DER-encoded authenticated attributes.  So, first we
         * encode and then we digest/verify.
         */
        encoded_attrs.data = NULL;
        encoded_attrs.len = 0;
        if (sec_PKCS7EncodeAttributes(NULL, &encoded_attrs,
                                      &(signerinfo->authAttr)) == NULL)
            goto done;

        if (encoded_attrs.data == NULL || encoded_attrs.len == 0) {
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        goodsig = (PRBool)(VFY_VerifyDataDirect(encoded_attrs.data,
                                                encoded_attrs.len,
                                                publickey, &(signerinfo->encDigest),
                                                encTag, digestTag, NULL,
                                                cinfo->pwfn_arg) == SECSuccess);
        PORT_Free(encoded_attrs.data);
    } else {
        SECItem *sig;
        SECItem holder;
        SECStatus rv;

        /*
         * No authenticated attributes.
         * The signature is based on the plain message digest.
         */

        sig = &(signerinfo->encDigest);
        if (sig->len == 0) { /* bad signature */
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            goto done;
        }

        if (sigkey != NULL) {
            sec_PKCS7CipherObject *decryptobj;
            unsigned int buflen;

            /*
             * For signedAndEnvelopedData, we first must decrypt the encrypted
             * digest with the bulk encryption key.  The result is the normal
             * encrypted digest (aka the signature).
             */
            decryptobj = sec_PKCS7CreateDecryptObject(sigkey, bulkid);
            if (decryptobj == NULL)
                goto done;

            buflen = sec_PKCS7DecryptLength(decryptobj, sig->len, PR_TRUE);
            PORT_Assert(buflen);
            if (buflen == 0) { /* something is wrong */
                sec_PKCS7DestroyDecryptObject(decryptobj);
                goto done;
            }

            holder.data = (unsigned char *)PORT_Alloc(buflen);
            if (holder.data == NULL) {
                sec_PKCS7DestroyDecryptObject(decryptobj);
                goto done;
            }

            rv = sec_PKCS7Decrypt(decryptobj, holder.data, &holder.len, buflen,
                                  sig->data, sig->len, PR_TRUE);
            sec_PKCS7DestroyDecryptObject(decryptobj);
            if (rv != SECSuccess) {
                goto done;
            }

            sig = &holder;
        }

        goodsig = (PRBool)(VFY_VerifyDigestDirect(digest, publickey, sig,
                                                  encTag, digestTag, cinfo->pwfn_arg) == SECSuccess);

        if (sigkey != NULL) {
            PORT_Assert(sig == &holder);
            PORT_ZFree(holder.data, holder.len);
        }
    }

    if (!goodsig) {
        /*
         * XXX Change the generic error into our specific one, because
         * in that case we get a better explanation out of the Security
         * Advisor.  This is really a bug in our error strings (the
         * "generic" error has a lousy/wrong message associated with it
         * which assumes the signature verification was done for the
         * purposes of checking the issuer signature on a certificate)
         * but this is at least an easy workaround and/or in the
         * Security Advisor, which specifically checks for the error
         * SEC_ERROR_PKCS7_BAD_SIGNATURE and gives more explanation
         * in that case but does not similarly check for
         * SEC_ERROR_BAD_SIGNATURE.  It probably should, but then would
         * probably say the wrong thing in the case that it *was* the
         * certificate signature check that failed during the cert
         * verification done above.  Our error handling is really a mess.
         */
        if (PORT_GetError() == SEC_ERROR_BAD_SIGNATURE)
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
    }

savecert:
    /*
     * Only save the smime profile if we are checking an email message and
     * the cert has an email address in it.
     */
    if (cert->emailAddr && cert->emailAddr[0] &&
        ((certusage == certUsageEmailSigner) ||
         (certusage == certUsageEmailRecipient))) {
        SECItem *profile = NULL;
        int save_error;

        /*
         * Remember the current error set because we do not care about
         * anything set by the functions we are about to call.
         */
        save_error = PORT_GetError();

        if (goodsig && (signerinfo->authAttr != NULL)) {
            /*
             * If the signature is good, then we can save the S/MIME profile,
             * if we have one.
             */
            SEC_PKCS7Attribute *attr;

            attr = sec_PKCS7FindAttribute(signerinfo->authAttr,
                                          SEC_OID_PKCS9_SMIME_CAPABILITIES,
                                          PR_TRUE);
            profile = sec_PKCS7AttributeValue(attr);
        }

        rv = CERT_SaveSMimeProfile(cert, profile, encoded_stime);

        /*
         * Restore the saved error in case the calls above set a new
         * one that we do not actually care about.
         */
        PORT_SetError(save_error);

        /*
         * XXX Failure is not indicated anywhere -- the signature
         * verification itself is unaffected by whether or not the
         * profile was successfully saved.
         */
    }

done:

    /*
     * See comment above about why we do not want to destroy cert
     * itself here.
     */

    if (certs != NULL)
        CERT_DestroyCertArray(certs, certcount);

    if (publickey != NULL)
        SECKEY_DestroyPublicKey(publickey);

    return goodsig;
}

/*
 * SEC_PKCS7VerifySignature
 *      Look at a PKCS7 contentInfo and check if the signature is good.
 *      The verification checks that the signing cert is valid and trusted
 *      for the purpose specified by "certusage".
 *
 *      In addition, if "keepcerts" is true, add any new certificates found
 *      into our local database.
 */
PRBool
SEC_PKCS7VerifySignature(SEC_PKCS7ContentInfo *cinfo,
                         SECCertUsage certusage,
                         PRBool keepcerts)
{
    return sec_pkcs7_verify_signature(cinfo, certusage,
                                      NULL, HASH_AlgNULL, keepcerts, NULL);
}

/*
 * SEC_PKCS7VerifyDetachedSignature
 *      Look at a PKCS7 contentInfo and check if the signature matches
 *      a passed-in digest (calculated, supposedly, from detached contents).
 *      The verification checks that the signing cert is valid and trusted
 *      for the purpose specified by "certusage".
 *
 *      In addition, if "keepcerts" is true, add any new certificates found
 *      into our local database.
 */
PRBool
SEC_PKCS7VerifyDetachedSignature(SEC_PKCS7ContentInfo *cinfo,
                                 SECCertUsage certusage,
                                 const SECItem *detached_digest,
                                 HASH_HashType digest_type,
                                 PRBool keepcerts)
{
    return sec_pkcs7_verify_signature(cinfo, certusage,
                                      detached_digest, digest_type,
                                      keepcerts, NULL);
}

/*
 * SEC_PKCS7VerifyDetachedSignatureAtTime
 *      Look at a PKCS7 contentInfo and check if the signature matches
 *      a passed-in digest (calculated, supposedly, from detached contents).
 *      The verification checks that the signing cert is valid and trusted
 *      for the purpose specified by "certusage" at time "atTime".
 *
 *      In addition, if "keepcerts" is true, add any new certificates found
 *      into our local database.
 */
PRBool
SEC_PKCS7VerifyDetachedSignatureAtTime(SEC_PKCS7ContentInfo *cinfo,
                                       SECCertUsage certusage,
                                       const SECItem *detached_digest,
                                       HASH_HashType digest_type,
                                       PRBool keepcerts,
                                       PRTime atTime)
{
    return sec_pkcs7_verify_signature(cinfo, certusage,
                                      detached_digest, digest_type,
                                      keepcerts, &atTime);
}

/*
 * Return the asked-for portion of the name of the signer of a PKCS7
 * signed object.
 *
 * Returns a pointer to allocated memory, which must be freed.
 * A NULL return value is an error.
 */

#define sec_common_name 1
#define sec_email_address 2

static char *
sec_pkcs7_get_signer_cert_info(SEC_PKCS7ContentInfo *cinfo, int selector)
{
    SECOidTag kind;
    SEC_PKCS7SignerInfo **signerinfos;
    CERTCertificate *signercert;
    char *container;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        default:
        case SEC_OID_PKCS7_DATA:
        case SEC_OID_PKCS7_DIGESTED_DATA:
        case SEC_OID_PKCS7_ENVELOPED_DATA:
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            PORT_Assert(0);
            return NULL;
        case SEC_OID_PKCS7_SIGNED_DATA: {
            SEC_PKCS7SignedData *sdp;

            sdp = cinfo->content.signedData;
            signerinfos = sdp->signerInfos;
        } break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
            SEC_PKCS7SignedAndEnvelopedData *saedp;

            saedp = cinfo->content.signedAndEnvelopedData;
            signerinfos = saedp->signerInfos;
        } break;
    }

    if (signerinfos == NULL || signerinfos[0] == NULL)
        return NULL;

    signercert = signerinfos[0]->cert;

    /*
     * No cert there; see if we can find one by calling verify ourselves.
     */
    if (signercert == NULL) {
        /*
         * The cert usage does not matter in this case, because we do not
         * actually care about the verification itself, but we have to pick
         * some valid usage to pass in.
         */
        (void)sec_pkcs7_verify_signature(cinfo, certUsageEmailSigner,
                                         NULL, HASH_AlgNULL, PR_FALSE, NULL);
        signercert = signerinfos[0]->cert;
        if (signercert == NULL)
            return NULL;
    }

    switch (selector) {
        case sec_common_name:
            container = CERT_GetCommonName(&signercert->subject);
            break;
        case sec_email_address:
            if (signercert->emailAddr && signercert->emailAddr[0]) {
                container = PORT_Strdup(signercert->emailAddr);
            } else {
                container = NULL;
            }
            break;
        default:
            PORT_Assert(0);
            container = NULL;
            break;
    }

    return container;
}

char *
SEC_PKCS7GetSignerCommonName(SEC_PKCS7ContentInfo *cinfo)
{
    return sec_pkcs7_get_signer_cert_info(cinfo, sec_common_name);
}

char *
SEC_PKCS7GetSignerEmailAddress(SEC_PKCS7ContentInfo *cinfo)
{
    return sec_pkcs7_get_signer_cert_info(cinfo, sec_email_address);
}

/*
 * Return the signing time, in UTCTime format, of a PKCS7 contentInfo.
 */
SECItem *
SEC_PKCS7GetSigningTime(SEC_PKCS7ContentInfo *cinfo)
{
    SEC_PKCS7SignerInfo **signerinfos;
    SEC_PKCS7Attribute *attr;

    if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA)
        return NULL;

    signerinfos = cinfo->content.signedData->signerInfos;

    /*
     * No signature, or more than one, means no deal.
     */
    if (signerinfos == NULL || signerinfos[0] == NULL || signerinfos[1] != NULL)
        return NULL;

    attr = sec_PKCS7FindAttribute(signerinfos[0]->authAttr,
                                  SEC_OID_PKCS9_SIGNING_TIME, PR_TRUE);
    return sec_PKCS7AttributeValue(attr);
}
