/* 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 creation.
 */

#include "p7local.h"

#include "cert.h"
#include "secasn1.h"
#include "secitem.h"
#include "secoid.h"
#include "pk11func.h"
#include "prtime.h"
#include "secerr.h"
#include "secder.h"
#include "secpkcs5.h"

const int NSS_PBE_DEFAULT_ITERATION_COUNT = /* used in p12e.c too */
#ifdef DEBUG
    10000
#else
    600000
#endif
    ;

static SECStatus
sec_pkcs7_init_content_info(SEC_PKCS7ContentInfo *cinfo, PLArenaPool *poolp,
                            SECOidTag kind, PRBool detached)
{
    void *thing;
    int version;
    SECItem *versionp;
    SECStatus rv;

    PORT_Assert(cinfo != NULL && poolp != NULL);
    if (cinfo == NULL || poolp == NULL)
        return SECFailure;

    cinfo->contentTypeTag = SECOID_FindOIDByTag(kind);
    PORT_Assert(cinfo->contentTypeTag && cinfo->contentTypeTag->offset == kind);

    rv = SECITEM_CopyItem(poolp, &(cinfo->contentType),
                          &(cinfo->contentTypeTag->oid));
    if (rv != SECSuccess)
        return rv;

    if (detached)
        return SECSuccess;

    switch (kind) {
        default:
        case SEC_OID_PKCS7_DATA:
            thing = PORT_ArenaZAlloc(poolp, sizeof(SECItem));
            cinfo->content.data = (SECItem *)thing;
            versionp = NULL;
            version = -1;
            break;
        case SEC_OID_PKCS7_DIGESTED_DATA:
            thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7DigestedData));
            cinfo->content.digestedData = (SEC_PKCS7DigestedData *)thing;
            versionp = &(cinfo->content.digestedData->version);
            version = SEC_PKCS7_DIGESTED_DATA_VERSION;
            break;
        case SEC_OID_PKCS7_ENCRYPTED_DATA:
            thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7EncryptedData));
            cinfo->content.encryptedData = (SEC_PKCS7EncryptedData *)thing;
            versionp = &(cinfo->content.encryptedData->version);
            version = SEC_PKCS7_ENCRYPTED_DATA_VERSION;
            break;
        case SEC_OID_PKCS7_ENVELOPED_DATA:
            thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7EnvelopedData));
            cinfo->content.envelopedData =
                (SEC_PKCS7EnvelopedData *)thing;
            versionp = &(cinfo->content.envelopedData->version);
            version = SEC_PKCS7_ENVELOPED_DATA_VERSION;
            break;
        case SEC_OID_PKCS7_SIGNED_DATA:
            thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7SignedData));
            cinfo->content.signedData =
                (SEC_PKCS7SignedData *)thing;
            versionp = &(cinfo->content.signedData->version);
            version = SEC_PKCS7_SIGNED_DATA_VERSION;
            break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
            thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7SignedAndEnvelopedData));
            cinfo->content.signedAndEnvelopedData =
                (SEC_PKCS7SignedAndEnvelopedData *)thing;
            versionp = &(cinfo->content.signedAndEnvelopedData->version);
            version = SEC_PKCS7_SIGNED_AND_ENVELOPED_DATA_VERSION;
            break;
    }

    if (thing == NULL)
        return SECFailure;

    if (versionp != NULL) {
        SECItem *dummy;

        PORT_Assert(version >= 0);
        dummy = SEC_ASN1EncodeInteger(poolp, versionp, version);
        if (dummy == NULL)
            return SECFailure;
        PORT_Assert(dummy == versionp);
    }

    return SECSuccess;
}

static SEC_PKCS7ContentInfo *
sec_pkcs7_create_content_info(SECOidTag kind, PRBool detached,
                              SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
    SEC_PKCS7ContentInfo *cinfo;
    PLArenaPool *poolp;
    SECStatus rv;

    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_TRUE;
    cinfo->refCount = 1;

    rv = sec_pkcs7_init_content_info(cinfo, poolp, kind, detached);
    if (rv != SECSuccess) {
        PORT_FreeArena(poolp, PR_FALSE);
        return NULL;
    }

    return cinfo;
}

/*
 * Add a signer to a PKCS7 thing, verifying the signature cert first.
 * Any error returns SECFailure.
 *
 * XXX Right now this only adds the *first* signer.  It fails if you try
 * to add a second one -- this needs to be fixed.
 */
static SECStatus
sec_pkcs7_add_signer(SEC_PKCS7ContentInfo *cinfo,
                     CERTCertificate *cert,
                     SECCertUsage certusage,
                     CERTCertDBHandle *certdb,
                     SECOidTag digestalgtag,
                     SECItem *digestdata)
{
    SEC_PKCS7SignerInfo *signerinfo, **signerinfos, ***signerinfosp;
    SECAlgorithmID *digestalg, **digestalgs, ***digestalgsp;
    SECItem *digest, **digests, ***digestsp;
    SECItem *dummy;
    void *mark;
    SECStatus rv;
    SECOidTag kind;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        case SEC_OID_PKCS7_SIGNED_DATA: {
            SEC_PKCS7SignedData *sdp;

            sdp = cinfo->content.signedData;
            digestalgsp = &(sdp->digestAlgorithms);
            digestsp = &(sdp->digests);
            signerinfosp = &(sdp->signerInfos);
        } break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
            SEC_PKCS7SignedAndEnvelopedData *saedp;

            saedp = cinfo->content.signedAndEnvelopedData;
            digestalgsp = &(saedp->digestAlgorithms);
            digestsp = &(saedp->digests);
            signerinfosp = &(saedp->signerInfos);
        } break;
        default:
            return SECFailure; /* XXX set an error? */
    }

    /*
     * XXX I think that CERT_VerifyCert should do this if *it* is passed
     * a NULL database.
     */
    if (certdb == NULL) {
        certdb = CERT_GetDefaultCertDB();
        if (certdb == NULL)
            return SECFailure; /* XXX set an error? */
    }

    if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, PR_Now(),
                        cinfo->pwfn_arg, NULL) != SECSuccess) {
        /* XXX Did CERT_VerifyCert set an error? */
        return SECFailure;
    }

    /*
     * XXX This is the check that we do not already have a signer.
     * This is not what we really want -- we want to allow this
     * and *add* the new signer.
     */
    PORT_Assert(*signerinfosp == NULL && *digestalgsp == NULL && *digestsp == NULL);
    if (*signerinfosp != NULL || *digestalgsp != NULL || *digestsp != NULL)
        return SECFailure;

    mark = PORT_ArenaMark(cinfo->poolp);

    signerinfo = (SEC_PKCS7SignerInfo *)PORT_ArenaZAlloc(cinfo->poolp,
                                                         sizeof(SEC_PKCS7SignerInfo));
    if (signerinfo == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    dummy = SEC_ASN1EncodeInteger(cinfo->poolp, &signerinfo->version,
                                  SEC_PKCS7_SIGNER_INFO_VERSION);
    if (dummy == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }
    PORT_Assert(dummy == &signerinfo->version);

    signerinfo->cert = CERT_DupCertificate(cert);
    if (signerinfo->cert == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    signerinfo->issuerAndSN = CERT_GetCertIssuerAndSN(cinfo->poolp, cert);
    if (signerinfo->issuerAndSN == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    rv = SECOID_SetAlgorithmID(cinfo->poolp, &signerinfo->digestAlg,
                               digestalgtag, NULL);
    if (rv != SECSuccess) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    /*
     * Okay, now signerinfo is all set.  We just need to put it and its
     * companions (another copy of the digest algorithm, and the digest
     * itself if given) into the main structure.
     *
     * XXX If we are handling more than one signer, the following code
     * needs to look through the digest algorithms already specified
     * and see if the same one is there already.  If it is, it does not
     * need to be added again.  Also, if it is there *and* the digest
     * is not null, then the digest given should match the digest already
     * specified -- if not, that is an error.  Finally, the new signerinfo
     * should be *added* to the set already found.
     */

    signerinfos = (SEC_PKCS7SignerInfo **)PORT_ArenaAlloc(cinfo->poolp,
                                                          2 * sizeof(SEC_PKCS7SignerInfo *));
    if (signerinfos == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }
    signerinfos[0] = signerinfo;
    signerinfos[1] = NULL;

    digestalg = PORT_ArenaZAlloc(cinfo->poolp, sizeof(SECAlgorithmID));
    digestalgs = PORT_ArenaAlloc(cinfo->poolp, 2 * sizeof(SECAlgorithmID *));
    if (digestalg == NULL || digestalgs == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }
    rv = SECOID_SetAlgorithmID(cinfo->poolp, digestalg, digestalgtag, NULL);
    if (rv != SECSuccess) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }
    digestalgs[0] = digestalg;
    digestalgs[1] = NULL;

    if (digestdata != NULL) {
        digest = (SECItem *)PORT_ArenaAlloc(cinfo->poolp, sizeof(SECItem));
        digests = (SECItem **)PORT_ArenaAlloc(cinfo->poolp,
                                              2 * sizeof(SECItem *));
        if (digest == NULL || digests == NULL) {
            PORT_ArenaRelease(cinfo->poolp, mark);
            return SECFailure;
        }
        rv = SECITEM_CopyItem(cinfo->poolp, digest, digestdata);
        if (rv != SECSuccess) {
            PORT_ArenaRelease(cinfo->poolp, mark);
            return SECFailure;
        }
        digests[0] = digest;
        digests[1] = NULL;
    } else {
        digests = NULL;
    }

    *signerinfosp = signerinfos;
    *digestalgsp = digestalgs;
    *digestsp = digests;

    PORT_ArenaUnmark(cinfo->poolp, mark);
    return SECSuccess;
}

/*
 * Helper function for creating an empty signedData.
 */
static SEC_PKCS7ContentInfo *
sec_pkcs7_create_signed_data(SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
    SEC_PKCS7ContentInfo *cinfo;
    SEC_PKCS7SignedData *sigd;
    SECStatus rv;

    cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_SIGNED_DATA, PR_FALSE,
                                          pwfn, pwfn_arg);
    if (cinfo == NULL)
        return NULL;

    sigd = cinfo->content.signedData;
    PORT_Assert(sigd != NULL);

    /*
     * XXX Might we want to allow content types other than data?
     * If so, via what interface?
     */
    rv = sec_pkcs7_init_content_info(&(sigd->contentInfo), cinfo->poolp,
                                     SEC_OID_PKCS7_DATA, PR_TRUE);
    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    return cinfo;
}

/*
 * Start a PKCS7 signing context.
 *
 * "cert" is the cert that will be used to sign the data.  It will be
 * checked for validity.
 *
 * "certusage" describes the signing usage (e.g. certUsageEmailSigner)
 * XXX Maybe SECCertUsage should be split so that our caller just says
 * "email" and *we* add the "signing" part -- otherwise our caller
 * could be lying about the usage; we do not want to allow encryption
 * certs for signing or vice versa.
 *
 * "certdb" is the cert database to use for verifying the cert.
 * It can be NULL if a default database is available (like in the client).
 * 
 * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1).
 *
 * "digest" is the actual digest of the data.  It must be provided in
 * the case of detached data or NULL if the content will be included.
 *
 * The return value can be passed to functions which add things to
 * it like attributes, then eventually to SEC_PKCS7Encode() or to
 * SEC_PKCS7EncoderStart() to create the encoded data, and finally to
 * SEC_PKCS7DestroyContentInfo().
 *
 * An error results in a return value of NULL and an error set.
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
 */
SEC_PKCS7ContentInfo *
SEC_PKCS7CreateSignedData(CERTCertificate *cert,
                          SECCertUsage certusage,
                          CERTCertDBHandle *certdb,
                          SECOidTag digestalg,
                          SECItem *digest,
                          SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
    SEC_PKCS7ContentInfo *cinfo;
    SECStatus rv;

    cinfo = sec_pkcs7_create_signed_data(pwfn, pwfn_arg);
    if (cinfo == NULL)
        return NULL;

    rv = sec_pkcs7_add_signer(cinfo, cert, certusage, certdb,
                              digestalg, digest);
    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    return cinfo;
}

static SEC_PKCS7Attribute *
sec_pkcs7_create_attribute(PLArenaPool *poolp, SECOidTag oidtag,
                           SECItem *value, PRBool encoded)
{
    SEC_PKCS7Attribute *attr;
    SECItem **values;
    void *mark;

    PORT_Assert(poolp != NULL);
    mark = PORT_ArenaMark(poolp);

    attr = (SEC_PKCS7Attribute *)PORT_ArenaAlloc(poolp,
                                                 sizeof(SEC_PKCS7Attribute));
    if (attr == NULL)
        goto loser;

    attr->typeTag = SECOID_FindOIDByTag(oidtag);
    if (attr->typeTag == NULL)
        goto loser;

    if (SECITEM_CopyItem(poolp, &(attr->type),
                         &(attr->typeTag->oid)) != SECSuccess)
        goto loser;

    values = (SECItem **)PORT_ArenaAlloc(poolp, 2 * sizeof(SECItem *));
    if (values == NULL)
        goto loser;

    if (value != NULL) {
        SECItem *copy;

        copy = (SECItem *)PORT_ArenaAlloc(poolp, sizeof(SECItem));
        if (copy == NULL)
            goto loser;

        if (SECITEM_CopyItem(poolp, copy, value) != SECSuccess)
            goto loser;

        value = copy;
    }

    values[0] = value;
    values[1] = NULL;
    attr->values = values;
    attr->encoded = encoded;

    PORT_ArenaUnmark(poolp, mark);
    return attr;

loser:
    PORT_Assert(mark != NULL);
    PORT_ArenaRelease(poolp, mark);
    return NULL;
}

static SECStatus
sec_pkcs7_add_attribute(SEC_PKCS7ContentInfo *cinfo,
                        SEC_PKCS7Attribute ***attrsp,
                        SEC_PKCS7Attribute *attr)
{
    SEC_PKCS7Attribute **attrs;
    SECItem *ct_value;
    void *mark;

    PORT_Assert(SEC_PKCS7ContentType(cinfo) == SEC_OID_PKCS7_SIGNED_DATA);
    if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA)
        return SECFailure;

    attrs = *attrsp;
    if (attrs != NULL) {
        int count;

        /*
	 * We already have some attributes, and just need to add this
	 * new one.
	 */

        /*
	 * We should already have the *required* attributes, which were
	 * created/added at the same time the first attribute was added.
	 */
        PORT_Assert(sec_PKCS7FindAttribute(attrs,
                                           SEC_OID_PKCS9_CONTENT_TYPE,
                                           PR_FALSE) != NULL);
        PORT_Assert(sec_PKCS7FindAttribute(attrs,
                                           SEC_OID_PKCS9_MESSAGE_DIGEST,
                                           PR_FALSE) != NULL);

        for (count = 0; attrs[count] != NULL; count++)
            ;
        attrs = (SEC_PKCS7Attribute **)PORT_ArenaGrow(cinfo->poolp, attrs,
                                                      (count + 1) * sizeof(SEC_PKCS7Attribute *),
                                                      (count + 2) * sizeof(SEC_PKCS7Attribute *));
        if (attrs == NULL)
            return SECFailure;

        attrs[count] = attr;
        attrs[count + 1] = NULL;
        *attrsp = attrs;

        return SECSuccess;
    }

    /*
     * This is the first time an attribute is going in.
     * We need to create and add the required attributes, and then
     * we will also add in the one our caller gave us.
     */

    /*
     * There are 2 required attributes, plus the one our caller wants
     * to add, plus we always end with a NULL one.  Thus, four slots.
     */
    attrs = (SEC_PKCS7Attribute **)PORT_ArenaAlloc(cinfo->poolp,
                                                   4 * sizeof(SEC_PKCS7Attribute *));
    if (attrs == NULL)
        return SECFailure;

    mark = PORT_ArenaMark(cinfo->poolp);

    /*
     * First required attribute is the content type of the data
     * being signed.
     */
    ct_value = &(cinfo->content.signedData->contentInfo.contentType);
    attrs[0] = sec_pkcs7_create_attribute(cinfo->poolp,
                                          SEC_OID_PKCS9_CONTENT_TYPE,
                                          ct_value, PR_FALSE);
    /*
     * Second required attribute is the message digest of the data
     * being signed; we leave the value NULL for now (just create
     * the place for it to go), and the encoder will fill it in later.
     */
    attrs[1] = sec_pkcs7_create_attribute(cinfo->poolp,
                                          SEC_OID_PKCS9_MESSAGE_DIGEST,
                                          NULL, PR_FALSE);
    if (attrs[0] == NULL || attrs[1] == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    attrs[2] = attr;
    attrs[3] = NULL;
    *attrsp = attrs;

    PORT_ArenaUnmark(cinfo->poolp, mark);
    return SECSuccess;
}

/*
 * Add the signing time to the authenticated (i.e. signed) attributes
 * of "cinfo".  This is expected to be included in outgoing signed
 * messages for email (S/MIME) but is likely useful in other situations.
 *
 * This should only be added once; a second call will either do
 * nothing or replace an old signing time with a newer one.
 *
 * XXX This will probably just shove the current time into "cinfo"
 * but it will not actually get signed until the entire item is
 * processed for encoding.  Is this (expected to be small) delay okay?
 *
 * "cinfo" should be of type signedData (the only kind of pkcs7 data
 * that is allowed authenticated attributes); SECFailure will be returned
 * if it is not.
 */
SECStatus
SEC_PKCS7AddSigningTime(SEC_PKCS7ContentInfo *cinfo)
{
    SEC_PKCS7SignerInfo **signerinfos;
    SEC_PKCS7Attribute *attr;
    SECItem stime;
    SECStatus rv;
    int si;

    PORT_Assert(SEC_PKCS7ContentType(cinfo) == SEC_OID_PKCS7_SIGNED_DATA);
    if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA)
        return SECFailure;

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

    /* There has to be a signer, or it makes no sense. */
    if (signerinfos == NULL || signerinfos[0] == NULL)
        return SECFailure;

    rv = DER_EncodeTimeChoice(NULL, &stime, PR_Now());
    if (rv != SECSuccess)
        return rv;

    attr = sec_pkcs7_create_attribute(cinfo->poolp,
                                      SEC_OID_PKCS9_SIGNING_TIME,
                                      &stime, PR_FALSE);
    SECITEM_FreeItem(&stime, PR_FALSE);

    if (attr == NULL)
        return SECFailure;

    rv = SECSuccess;
    for (si = 0; signerinfos[si] != NULL; si++) {
        SEC_PKCS7Attribute *oattr;

        oattr = sec_PKCS7FindAttribute(signerinfos[si]->authAttr,
                                       SEC_OID_PKCS9_SIGNING_TIME, PR_FALSE);
        PORT_Assert(oattr == NULL);
        if (oattr != NULL)
            continue; /* XXX or would it be better to replace it? */

        rv = sec_pkcs7_add_attribute(cinfo, &(signerinfos[si]->authAttr),
                                     attr);
        if (rv != SECSuccess)
            break; /* could try to continue, but may as well give up now */
    }

    return rv;
}

/*
 * Add the specified attribute to the authenticated (i.e. signed) attributes
 * of "cinfo" -- "oidtag" describes the attribute and "value" is the
 * value to be associated with it.  NOTE! "value" must already be encoded;
 * no interpretation of "oidtag" is done.  Also, it is assumed that this
 * signedData has only one signer -- if we ever need to add attributes
 * when there is more than one signature, we need a way to specify *which*
 * signature should get the attribute.
 *
 * XXX Technically, a signed attribute can have multiple values; if/when
 * we ever need to support an attribute which takes multiple values, we
 * either need to change this interface or create an AddSignedAttributeValue
 * which can be called subsequently, and would then append a value.
 *
 * "cinfo" should be of type signedData (the only kind of pkcs7 data
 * that is allowed authenticated attributes); SECFailure will be returned
 * if it is not.
 */
SECStatus
SEC_PKCS7AddSignedAttribute(SEC_PKCS7ContentInfo *cinfo,
                            SECOidTag oidtag,
                            SECItem *value)
{
    SEC_PKCS7SignerInfo **signerinfos;
    SEC_PKCS7Attribute *attr;

    PORT_Assert(SEC_PKCS7ContentType(cinfo) == SEC_OID_PKCS7_SIGNED_DATA);
    if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA)
        return SECFailure;

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

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

    attr = sec_pkcs7_create_attribute(cinfo->poolp, oidtag, value, PR_TRUE);
    if (attr == NULL)
        return SECFailure;

    return sec_pkcs7_add_attribute(cinfo, &(signerinfos[0]->authAttr), attr);
}

/*
 * Mark that the signer certificates and their issuing chain should
 * be included in the encoded data.  This is expected to be used
 * in outgoing signed messages for email (S/MIME).
 *
 * "certdb" is the cert database to use for finding the chain.
 * It can be NULL, meaning use the default database.
 *
 * "cinfo" should be of type signedData or signedAndEnvelopedData;
 * SECFailure will be returned if it is not.
 */
SECStatus
SEC_PKCS7IncludeCertChain(SEC_PKCS7ContentInfo *cinfo,
                          CERTCertDBHandle *certdb)
{
    SECOidTag kind;
    SEC_PKCS7SignerInfo *signerinfo, **signerinfos;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        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;
        default:
            return SECFailure; /* XXX set an error? */
    }

    if (signerinfos == NULL) /* no signer, no certs? */
        return SECFailure;   /* XXX set an error? */

    if (certdb == NULL) {
        certdb = CERT_GetDefaultCertDB();
        if (certdb == NULL) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            return SECFailure;
        }
    }

    /* XXX Should it be an error if we find no signerinfo or no certs? */
    while ((signerinfo = *signerinfos++) != NULL) {
        if (signerinfo->cert != NULL)
            /* get the cert chain.  don't send the root to avoid contamination
	     * of old clients with a new root that they don't trust
	     */
            signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert,
                                                          certUsageEmailSigner,
                                                          PR_FALSE);
    }

    return SECSuccess;
}

/*
 * Helper function to add a certificate chain for inclusion in the
 * bag of certificates in a signedData.
 */
static SECStatus
sec_pkcs7_add_cert_chain(SEC_PKCS7ContentInfo *cinfo,
                         CERTCertificate *cert,
                         CERTCertDBHandle *certdb)
{
    SECOidTag kind;
    CERTCertificateList *certlist, **certlists, ***certlistsp;
    int count;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        case SEC_OID_PKCS7_SIGNED_DATA: {
            SEC_PKCS7SignedData *sdp;

            sdp = cinfo->content.signedData;
            certlistsp = &(sdp->certLists);
        } break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
            SEC_PKCS7SignedAndEnvelopedData *saedp;

            saedp = cinfo->content.signedAndEnvelopedData;
            certlistsp = &(saedp->certLists);
        } break;
        default:
            return SECFailure; /* XXX set an error? */
    }

    if (certdb == NULL) {
        certdb = CERT_GetDefaultCertDB();
        if (certdb == NULL) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            return SECFailure;
        }
    }

    certlist = CERT_CertChainFromCert(cert, certUsageEmailSigner, PR_FALSE);
    if (certlist == NULL)
        return SECFailure;

    certlists = *certlistsp;
    if (certlists == NULL) {
        count = 0;
        certlists = (CERTCertificateList **)PORT_ArenaAlloc(cinfo->poolp,
                                                            2 * sizeof(CERTCertificateList *));
    } else {
        for (count = 0; certlists[count] != NULL; count++)
            ;
        PORT_Assert(count); /* should be at least one already */
        certlists = (CERTCertificateList **)PORT_ArenaGrow(cinfo->poolp,
                                                           certlists,
                                                           (count + 1) * sizeof(CERTCertificateList *),
                                                           (count + 2) * sizeof(CERTCertificateList *));
    }

    if (certlists == NULL) {
        CERT_DestroyCertificateList(certlist);
        return SECFailure;
    }

    certlists[count] = certlist;
    certlists[count + 1] = NULL;

    *certlistsp = certlists;

    return SECSuccess;
}

/*
 * Helper function to add a certificate for inclusion in the bag of
 * certificates in a signedData.
 */
static SECStatus
sec_pkcs7_add_certificate(SEC_PKCS7ContentInfo *cinfo,
                          CERTCertificate *cert)
{
    SECOidTag kind;
    CERTCertificate **certs, ***certsp;
    int count;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        case SEC_OID_PKCS7_SIGNED_DATA: {
            SEC_PKCS7SignedData *sdp;

            sdp = cinfo->content.signedData;
            certsp = &(sdp->certs);
        } break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
            SEC_PKCS7SignedAndEnvelopedData *saedp;

            saedp = cinfo->content.signedAndEnvelopedData;
            certsp = &(saedp->certs);
        } break;
        default:
            return SECFailure; /* XXX set an error? */
    }

    cert = CERT_DupCertificate(cert);
    if (cert == NULL)
        return SECFailure;

    certs = *certsp;
    if (certs == NULL) {
        count = 0;
        certs = (CERTCertificate **)PORT_ArenaAlloc(cinfo->poolp,
                                                    2 * sizeof(CERTCertificate *));
    } else {
        for (count = 0; certs[count] != NULL; count++)
            ;
        PORT_Assert(count); /* should be at least one already */
        certs = (CERTCertificate **)PORT_ArenaGrow(cinfo->poolp, certs,
                                                   (count + 1) * sizeof(CERTCertificate *),
                                                   (count + 2) * sizeof(CERTCertificate *));
    }

    if (certs == NULL) {
        CERT_DestroyCertificate(cert);
        return SECFailure;
    }

    certs[count] = cert;
    certs[count + 1] = NULL;

    *certsp = certs;

    return SECSuccess;
}

/*
 * Create a PKCS7 certs-only container.
 *
 * "cert" is the (first) cert that will be included.
 *
 * "include_chain" specifies whether the entire chain for "cert" should
 * be included.
 *
 * "certdb" is the cert database to use for finding the chain.
 * It can be NULL in when "include_chain" is false, or when meaning
 * use the default database.
 *
 * More certs and chains can be added via AddCertificate and AddCertChain.
 *
 * An error results in a return value of NULL and an error set.
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
 */
SEC_PKCS7ContentInfo *
SEC_PKCS7CreateCertsOnly(CERTCertificate *cert,
                         PRBool include_chain,
                         CERTCertDBHandle *certdb)
{
    SEC_PKCS7ContentInfo *cinfo;
    SECStatus rv;

    cinfo = sec_pkcs7_create_signed_data(NULL, NULL);
    if (cinfo == NULL)
        return NULL;

    if (include_chain)
        rv = sec_pkcs7_add_cert_chain(cinfo, cert, certdb);
    else
        rv = sec_pkcs7_add_certificate(cinfo, cert);

    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    return cinfo;
}

/*
 * Add "cert" and its entire chain to the set of certs included in "cinfo".
 *
 * "certdb" is the cert database to use for finding the chain.
 * It can be NULL, meaning use the default database.
 *
 * "cinfo" should be of type signedData or signedAndEnvelopedData;
 * SECFailure will be returned if it is not.
 */
SECStatus
SEC_PKCS7AddCertChain(SEC_PKCS7ContentInfo *cinfo,
                      CERTCertificate *cert,
                      CERTCertDBHandle *certdb)
{
    SECOidTag kind;

    kind = SEC_PKCS7ContentType(cinfo);
    if (kind != SEC_OID_PKCS7_SIGNED_DATA && kind != SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA)
        return SECFailure; /* XXX set an error? */

    return sec_pkcs7_add_cert_chain(cinfo, cert, certdb);
}

/*
 * Add "cert" to the set of certs included in "cinfo".
 *
 * "cinfo" should be of type signedData or signedAndEnvelopedData;
 * SECFailure will be returned if it is not.
 */
SECStatus
SEC_PKCS7AddCertificate(SEC_PKCS7ContentInfo *cinfo, CERTCertificate *cert)
{
    SECOidTag kind;

    kind = SEC_PKCS7ContentType(cinfo);
    if (kind != SEC_OID_PKCS7_SIGNED_DATA && kind != SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA)
        return SECFailure; /* XXX set an error? */

    return sec_pkcs7_add_certificate(cinfo, cert);
}

static SECStatus
sec_pkcs7_init_encrypted_content_info(SEC_PKCS7EncryptedContentInfo *enccinfo,
                                      PLArenaPool *poolp,
                                      SECOidTag kind, PRBool detached,
                                      SECOidTag encalg, int keysize)
{
    SECStatus rv;

    PORT_Assert(enccinfo != NULL && poolp != NULL);
    if (enccinfo == NULL || poolp == NULL)
        return SECFailure;

    /*
     * XXX Some day we may want to allow for other kinds.  That needs
     * more work and modifications to the creation interface, etc.
     * For now, allow but notice callers who pass in other kinds.
     * They are responsible for creating the inner type and encoding,
     * if it is other than DATA.
     */
    PORT_Assert(kind == SEC_OID_PKCS7_DATA);

    enccinfo->contentTypeTag = SECOID_FindOIDByTag(kind);
    PORT_Assert(enccinfo->contentTypeTag && enccinfo->contentTypeTag->offset == kind);

    rv = SECITEM_CopyItem(poolp, &(enccinfo->contentType),
                          &(enccinfo->contentTypeTag->oid));
    if (rv != SECSuccess)
        return rv;

    /* Save keysize and algorithm for later. */
    enccinfo->keysize = keysize;
    enccinfo->encalg = encalg;

    return SECSuccess;
}

/*
 * Add a recipient to a PKCS7 thing, verifying their cert first.
 * Any error returns SECFailure.
 */
static SECStatus
sec_pkcs7_add_recipient(SEC_PKCS7ContentInfo *cinfo,
                        CERTCertificate *cert,
                        SECCertUsage certusage,
                        CERTCertDBHandle *certdb)
{
    SECOidTag kind;
    SEC_PKCS7RecipientInfo *recipientinfo, **recipientinfos, ***recipientinfosp;
    SECItem *dummy;
    void *mark;
    int count;

    kind = SEC_PKCS7ContentType(cinfo);
    switch (kind) {
        case SEC_OID_PKCS7_ENVELOPED_DATA: {
            SEC_PKCS7EnvelopedData *edp;

            edp = cinfo->content.envelopedData;
            recipientinfosp = &(edp->recipientInfos);
        } break;
        case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: {
            SEC_PKCS7SignedAndEnvelopedData *saedp;

            saedp = cinfo->content.signedAndEnvelopedData;
            recipientinfosp = &(saedp->recipientInfos);
        } break;
        default:
            return SECFailure; /* XXX set an error? */
    }

    /*
     * XXX I think that CERT_VerifyCert should do this if *it* is passed
     * a NULL database.
     */
    if (certdb == NULL) {
        certdb = CERT_GetDefaultCertDB();
        if (certdb == NULL)
            return SECFailure; /* XXX set an error? */
    }

    if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, PR_Now(),
                        cinfo->pwfn_arg, NULL) != SECSuccess) {
        /* XXX Did CERT_VerifyCert set an error? */
        return SECFailure;
    }

    mark = PORT_ArenaMark(cinfo->poolp);

    recipientinfo = (SEC_PKCS7RecipientInfo *)PORT_ArenaZAlloc(cinfo->poolp,
                                                               sizeof(SEC_PKCS7RecipientInfo));
    if (recipientinfo == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    dummy = SEC_ASN1EncodeInteger(cinfo->poolp, &recipientinfo->version,
                                  SEC_PKCS7_RECIPIENT_INFO_VERSION);
    if (dummy == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }
    PORT_Assert(dummy == &recipientinfo->version);

    recipientinfo->cert = CERT_DupCertificate(cert);
    if (recipientinfo->cert == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    recipientinfo->issuerAndSN = CERT_GetCertIssuerAndSN(cinfo->poolp, cert);
    if (recipientinfo->issuerAndSN == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    /*
     * Okay, now recipientinfo is all set.  We just need to put it into
     * the main structure.
     *
     * If this is the first recipient, allocate a new recipientinfos array;
     * otherwise, reallocate the array, making room for the new entry.
     */
    recipientinfos = *recipientinfosp;
    if (recipientinfos == NULL) {
        count = 0;
        recipientinfos = (SEC_PKCS7RecipientInfo **)PORT_ArenaAlloc(
            cinfo->poolp,
            2 * sizeof(SEC_PKCS7RecipientInfo *));
    } else {
        for (count = 0; recipientinfos[count] != NULL; count++)
            ;
        PORT_Assert(count); /* should be at least one already */
        recipientinfos = (SEC_PKCS7RecipientInfo **)PORT_ArenaGrow(
            cinfo->poolp, recipientinfos,
            (count + 1) * sizeof(SEC_PKCS7RecipientInfo *),
            (count + 2) * sizeof(SEC_PKCS7RecipientInfo *));
    }

    if (recipientinfos == NULL) {
        PORT_ArenaRelease(cinfo->poolp, mark);
        return SECFailure;
    }

    recipientinfos[count] = recipientinfo;
    recipientinfos[count + 1] = NULL;

    *recipientinfosp = recipientinfos;

    PORT_ArenaUnmark(cinfo->poolp, mark);
    return SECSuccess;
}

/*
 * Start a PKCS7 enveloping context.
 *
 * "cert" is the cert for the recipient.  It will be checked for validity.
 *
 * "certusage" describes the encryption usage (e.g. certUsageEmailRecipient)
 * XXX Maybe SECCertUsage should be split so that our caller just says
 * "email" and *we* add the "recipient" part -- otherwise our caller
 * could be lying about the usage; we do not want to allow encryption
 * certs for signing or vice versa.
 *
 * "certdb" is the cert database to use for verifying the cert.
 * It can be NULL if a default database is available (like in the client).
 *
 * "encalg" specifies the bulk encryption algorithm to use (e.g. SEC_OID_RC2).
 *
 * "keysize" specifies the bulk encryption key size, in bits.
 *
 * The return value can be passed to functions which add things to
 * it like more recipients, then eventually to SEC_PKCS7Encode() or to
 * SEC_PKCS7EncoderStart() to create the encoded data, and finally to
 * SEC_PKCS7DestroyContentInfo().
 *
 * An error results in a return value of NULL and an error set.
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
 */
extern SEC_PKCS7ContentInfo *
SEC_PKCS7CreateEnvelopedData(CERTCertificate *cert,
                             SECCertUsage certusage,
                             CERTCertDBHandle *certdb,
                             SECOidTag encalg,
                             int keysize,
                             SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
    SEC_PKCS7ContentInfo *cinfo;
    SEC_PKCS7EnvelopedData *envd;
    SECStatus rv;

    cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_ENVELOPED_DATA,
                                          PR_FALSE, pwfn, pwfn_arg);
    if (cinfo == NULL)
        return NULL;

    rv = sec_pkcs7_add_recipient(cinfo, cert, certusage, certdb);
    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    envd = cinfo->content.envelopedData;
    PORT_Assert(envd != NULL);

    /*
     * XXX Might we want to allow content types other than data?
     * If so, via what interface?
     */
    rv = sec_pkcs7_init_encrypted_content_info(&(envd->encContentInfo),
                                               cinfo->poolp,
                                               SEC_OID_PKCS7_DATA, PR_FALSE,
                                               encalg, keysize);
    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    /* XXX Anything more to do here? */

    return cinfo;
}

/*
 * Add another recipient to an encrypted message.
 *
 * "cinfo" should be of type envelopedData or signedAndEnvelopedData;
 * SECFailure will be returned if it is not.
 *
 * "cert" is the cert for the recipient.  It will be checked for validity.
 *
 * "certusage" describes the encryption usage (e.g. certUsageEmailRecipient)
 * XXX Maybe SECCertUsage should be split so that our caller just says
 * "email" and *we* add the "recipient" part -- otherwise our caller
 * could be lying about the usage; we do not want to allow encryption
 * certs for signing or vice versa.
 *
 * "certdb" is the cert database to use for verifying the cert.
 * It can be NULL if a default database is available (like in the client).
 */
SECStatus
SEC_PKCS7AddRecipient(SEC_PKCS7ContentInfo *cinfo,
                      CERTCertificate *cert,
                      SECCertUsage certusage,
                      CERTCertDBHandle *certdb)
{
    return sec_pkcs7_add_recipient(cinfo, cert, certusage, certdb);
}

/*
 * Create an empty PKCS7 data content info.
 *
 * An error results in a return value of NULL and an error set.
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
 */
SEC_PKCS7ContentInfo *
SEC_PKCS7CreateData(void)
{
    return sec_pkcs7_create_content_info(SEC_OID_PKCS7_DATA, PR_FALSE,
                                         NULL, NULL);
}

/*
 * Create an empty PKCS7 encrypted content info.
 *
 * "algorithm" specifies the bulk encryption algorithm to use.
 * 
 * An error results in a return value of NULL and an error set.
 * (Retrieve specific errors via PORT_GetError()/XP_GetError().)
 */
SEC_PKCS7ContentInfo *
SEC_PKCS7CreateEncryptedData(SECOidTag algorithm, int keysize,
                             SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
    SEC_PKCS7ContentInfo *cinfo;
    SECAlgorithmID *algid;
    SEC_PKCS7EncryptedData *enc_data;
    SECStatus rv;

    cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_ENCRYPTED_DATA,
                                          PR_FALSE, pwfn, pwfn_arg);
    if (cinfo == NULL)
        return NULL;

    enc_data = cinfo->content.encryptedData;
    algid = &(enc_data->encContentInfo.contentEncAlg);

    if (!SEC_PKCS5IsAlgorithmPBEAlgTag(algorithm)) {
        rv = SECOID_SetAlgorithmID(cinfo->poolp, algid, algorithm, NULL);
    } else {
        /* Assume password-based-encryption.  
         * Note: we can't generate pkcs5v2 from this interface.
         * PK11_CreateBPEAlgorithmID generates pkcs5v2 by accepting
         * non-PBE oids and assuming that they are pkcs5v2 oids, but
         * NSS_CMSEncryptedData_Create accepts non-PBE oids as regular
         * CMS encrypted data, so we can't tell SEC_PKCS7CreateEncryptedtedData
         * to create pkcs5v2 PBEs */
        SECAlgorithmID *pbe_algid;
        pbe_algid = PK11_CreatePBEAlgorithmID(algorithm,
                                              NSS_PBE_DEFAULT_ITERATION_COUNT,
                                              NULL);
        if (pbe_algid == NULL) {
            rv = SECFailure;
        } else {
            rv = SECOID_CopyAlgorithmID(cinfo->poolp, algid, pbe_algid);
            SECOID_DestroyAlgorithmID(pbe_algid, PR_TRUE);
        }
    }

    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    rv = sec_pkcs7_init_encrypted_content_info(&(enc_data->encContentInfo),
                                               cinfo->poolp,
                                               SEC_OID_PKCS7_DATA, PR_FALSE,
                                               algorithm, keysize);
    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    return cinfo;
}

SEC_PKCS7ContentInfo *
SEC_PKCS7CreateEncryptedDataWithPBEV2(SECOidTag pbe_algorithm,
                                      SECOidTag cipher_algorithm,
                                      SECOidTag prf_algorithm,
                                      int keysize,
                                      SECKEYGetPasswordKey pwfn, void *pwfn_arg)
{
    SEC_PKCS7ContentInfo *cinfo;
    SECAlgorithmID *algid;
    SEC_PKCS7EncryptedData *enc_data;
    SECStatus rv;

    PORT_Assert(SEC_PKCS5IsAlgorithmPBEAlgTag(pbe_algorithm));

    cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_ENCRYPTED_DATA,
                                          PR_FALSE, pwfn, pwfn_arg);
    if (cinfo == NULL)
        return NULL;

    enc_data = cinfo->content.encryptedData;
    algid = &(enc_data->encContentInfo.contentEncAlg);

    SECAlgorithmID *pbe_algid;
    pbe_algid = PK11_CreatePBEV2AlgorithmID(pbe_algorithm,
                                            cipher_algorithm,
                                            prf_algorithm,
                                            keysize,
                                            NSS_PBE_DEFAULT_ITERATION_COUNT,
                                            NULL);
    if (pbe_algid == NULL) {
        rv = SECFailure;
    } else {
        rv = SECOID_CopyAlgorithmID(cinfo->poolp, algid, pbe_algid);
        SECOID_DestroyAlgorithmID(pbe_algid, PR_TRUE);
    }

    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    rv = sec_pkcs7_init_encrypted_content_info(&(enc_data->encContentInfo),
                                               cinfo->poolp,
                                               SEC_OID_PKCS7_DATA, PR_FALSE,
                                               cipher_algorithm, keysize);
    if (rv != SECSuccess) {
        SEC_PKCS7DestroyContentInfo(cinfo);
        return NULL;
    }

    return cinfo;
}
