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

/*
 * CMS signerInfo methods.
 */

#include "cmslocal.h"

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

#include "smime.h"

/* =============================================================================
 * SIGNERINFO
 */
NSSCMSSignerInfo *
nss_cmssignerinfo_create(NSSCMSMessage *cmsg, NSSCMSSignerIDSelector type,
                         CERTCertificate *cert, SECItem *subjKeyID, SECKEYPublicKey *pubKey,
                         SECKEYPrivateKey *signingKey, SECOidTag digestalgtag);

NSSCMSSignerInfo *
NSS_CMSSignerInfo_CreateWithSubjKeyID(NSSCMSMessage *cmsg, SECItem *subjKeyID,
                                      SECKEYPublicKey *pubKey,
                                      SECKEYPrivateKey *signingKey, SECOidTag digestalgtag)
{
    return nss_cmssignerinfo_create(cmsg, NSSCMSSignerID_SubjectKeyID, NULL,
                                    subjKeyID, pubKey, signingKey, digestalgtag);
}

NSSCMSSignerInfo *
NSS_CMSSignerInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert, SECOidTag digestalgtag)
{
    return nss_cmssignerinfo_create(cmsg, NSSCMSSignerID_IssuerSN, cert, NULL,
                                    NULL, NULL, digestalgtag);
}

NSSCMSSignerInfo *
nss_cmssignerinfo_create(NSSCMSMessage *cmsg, NSSCMSSignerIDSelector type,
                         CERTCertificate *cert, SECItem *subjKeyID, SECKEYPublicKey *pubKey,
                         SECKEYPrivateKey *signingKey, SECOidTag digestalgtag)
{
    void *mark;
    NSSCMSSignerInfo *signerinfo;
    int version;
    PLArenaPool *poolp;
    SECStatus rv;

    poolp = cmsg->poolp;

    mark = PORT_ArenaMark(poolp);

    signerinfo = (NSSCMSSignerInfo *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSSignerInfo));
    if (signerinfo == NULL) {
        PORT_ArenaRelease(poolp, mark);
        return NULL;
    }

    signerinfo->cmsg = cmsg;

    switch (type) {
        case NSSCMSSignerID_IssuerSN:
            signerinfo->signerIdentifier.identifierType = NSSCMSSignerID_IssuerSN;
            if ((signerinfo->cert = CERT_DupCertificate(cert)) == NULL)
                goto loser;
            if ((signerinfo->signerIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert)) == NULL)
                goto loser;
            break;
        case NSSCMSSignerID_SubjectKeyID:
            signerinfo->signerIdentifier.identifierType = NSSCMSSignerID_SubjectKeyID;
            PORT_Assert(subjKeyID);
            if (!subjKeyID)
                goto loser;

            signerinfo->signerIdentifier.id.subjectKeyID = PORT_ArenaNew(poolp, SECItem);
            rv = SECITEM_CopyItem(poolp, signerinfo->signerIdentifier.id.subjectKeyID,
                                  subjKeyID);
            if (rv != SECSuccess) {
                goto loser;
            }
            signerinfo->signingKey = SECKEY_CopyPrivateKey(signingKey);
            if (!signerinfo->signingKey)
                goto loser;
            signerinfo->pubKey = SECKEY_CopyPublicKey(pubKey);
            if (!signerinfo->pubKey)
                goto loser;
            break;
        default:
            goto loser;
    }

    /* set version right now */
    version = NSS_CMS_SIGNER_INFO_VERSION_ISSUERSN;
    /* RFC2630 5.3 "version is the syntax version number. If the .... " */
    if (signerinfo->signerIdentifier.identifierType == NSSCMSSignerID_SubjectKeyID)
        version = NSS_CMS_SIGNER_INFO_VERSION_SUBJKEY;
    (void)SEC_ASN1EncodeInteger(poolp, &(signerinfo->version), (long)version);

    if (SECOID_SetAlgorithmID(poolp, &signerinfo->digestAlg, digestalgtag, NULL) != SECSuccess)
        goto loser;

    PORT_ArenaUnmark(poolp, mark);
    return signerinfo;

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

/*
 * NSS_CMSSignerInfo_Destroy - destroy a SignerInfo data structure
 */
void
NSS_CMSSignerInfo_Destroy(NSSCMSSignerInfo *si)
{
    if (si->cert != NULL)
        CERT_DestroyCertificate(si->cert);

    if (si->certList != NULL)
        CERT_DestroyCertificateList(si->certList);

    /* XXX storage ??? */
}

/*
 * NSS_CMSSignerInfo_Sign - sign something
 *
 */
SECStatus
NSS_CMSSignerInfo_Sign(NSSCMSSignerInfo *signerinfo, SECItem *digest,
                       SECItem *contentType)
{
    CERTCertificate *cert;
    SECKEYPrivateKey *privkey = NULL;
    SECOidTag digestalgtag;
    SECOidTag pubkAlgTag;
    SECItem signature = { 0 };
    SECStatus rv;
    PLArenaPool *poolp, *tmppoolp = NULL;
    SECAlgorithmID *algID, freeAlgID;
    CERTSubjectPublicKeyInfo *spki;

    PORT_Assert(digest != NULL);

    poolp = signerinfo->cmsg->poolp;

    switch (signerinfo->signerIdentifier.identifierType) {
        case NSSCMSSignerID_IssuerSN:
            cert = signerinfo->cert;

            privkey = PK11_FindKeyByAnyCert(cert, signerinfo->cmsg->pwfn_arg);
            if (privkey == NULL)
                goto loser;
            algID = &cert->subjectPublicKeyInfo.algorithm;
            break;
        case NSSCMSSignerID_SubjectKeyID:
            privkey = signerinfo->signingKey;
            signerinfo->signingKey = NULL;
            spki = SECKEY_CreateSubjectPublicKeyInfo(signerinfo->pubKey);
            SECKEY_DestroyPublicKey(signerinfo->pubKey);
            signerinfo->pubKey = NULL;
            SECOID_CopyAlgorithmID(NULL, &freeAlgID, &spki->algorithm);
            SECKEY_DestroySubjectPublicKeyInfo(spki);
            algID = &freeAlgID;
            break;
        default:
            goto loser;
    }
    digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo);
    /*
     * XXX I think there should be a cert-level interface for this,
     * so that I do not have to know about subjectPublicKeyInfo...
     */
    pubkAlgTag = SECOID_GetAlgorithmTag(algID);
    if (signerinfo->signerIdentifier.identifierType == NSSCMSSignerID_SubjectKeyID) {
        SECOID_DestroyAlgorithmID(&freeAlgID, PR_FALSE);
    }

    if (signerinfo->authAttr != NULL) {
        SECOidTag signAlgTag;
        SECItem encoded_attrs;

        /* find and fill in the message digest attribute. */
        rv = NSS_CMSAttributeArray_SetAttr(poolp, &(signerinfo->authAttr),
                                           SEC_OID_PKCS9_MESSAGE_DIGEST, digest, PR_FALSE);
        if (rv != SECSuccess)
            goto loser;

        if (contentType != NULL) {
            /* if the caller wants us to, find and fill in the content type attribute. */
            rv = NSS_CMSAttributeArray_SetAttr(poolp, &(signerinfo->authAttr),
                                               SEC_OID_PKCS9_CONTENT_TYPE, contentType, PR_FALSE);
            if (rv != SECSuccess)
                goto loser;
        }

        if ((tmppoolp = PORT_NewArena(1024)) == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }

        /*
     * Before encoding, reorder the attributes so that when they
     * are encoded, they will be conforming DER, which is required
     * to have a specific order and that is what must be used for
     * the hash/signature.  We do this here, rather than building
     * it into EncodeAttributes, because we do not want to do
     * such reordering on incoming messages (which also uses
     * EncodeAttributes) or our old signatures (and other "broken"
     * implementations) will not verify.  So, we want to guarantee
     * that we send out good DER encodings of attributes, but not
     * to expect to receive them.
     */
        if (NSS_CMSAttributeArray_Reorder(signerinfo->authAttr) != SECSuccess)
            goto loser;

        encoded_attrs.data = NULL;
        encoded_attrs.len = 0;
        if (NSS_CMSAttributeArray_Encode(tmppoolp, &(signerinfo->authAttr),
                                         &encoded_attrs) == NULL)
            goto loser;

        signAlgTag = SEC_GetSignatureAlgorithmOidTag(privkey->keyType,
                                                     digestalgtag);
        if (signAlgTag == SEC_OID_UNKNOWN) {
            PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
            goto loser;
        }

        rv = SEC_SignData(&signature, encoded_attrs.data, encoded_attrs.len,
                          privkey, signAlgTag);
        PORT_FreeArena(tmppoolp, PR_FALSE); /* awkward memory management :-( */
        tmppoolp = 0;
    } else {
        rv = SGN_Digest(privkey, digestalgtag, &signature, digest);
    }
    SECKEY_DestroyPrivateKey(privkey);
    privkey = NULL;

    if (rv != SECSuccess)
        goto loser;

    if (SECITEM_CopyItem(poolp, &(signerinfo->encDigest), &signature) != SECSuccess)
        goto loser;

    SECITEM_FreeItem(&signature, PR_FALSE);

    if (SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg), pubkAlgTag,
                              NULL) != SECSuccess)
        goto loser;

    return SECSuccess;

loser:
    if (signature.len != 0)
        SECITEM_FreeItem(&signature, PR_FALSE);
    if (privkey)
        SECKEY_DestroyPrivateKey(privkey);
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    return SECFailure;
}

SECStatus
NSS_CMSSignerInfo_VerifyCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb,
                                    SECCertUsage certusage)
{
    CERTCertificate *cert;
    PRTime stime;

    if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, certdb)) == NULL) {
        signerinfo->verificationStatus = NSSCMSVS_SigningCertNotFound;
        return SECFailure;
    }

    /*
     * Get and convert the signing time; if available, it will be used
     * both on the cert verification and for importing the sender
     * email profile.
     */
    if (NSS_CMSSignerInfo_GetSigningTime(signerinfo, &stime) != SECSuccess)
        stime = PR_Now(); /* not found or conversion failed, so check against now */

    /*
     * 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 (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, stime,
                        signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) {
        signerinfo->verificationStatus = NSSCMSVS_SigningCertNotTrusted;
        return SECFailure;
    }
    return SECSuccess;
}

/*
 * NSS_CMSSignerInfo_Verify - verify the signature of a single SignerInfo
 *
 * Just verifies the signature. The assumption is that verification of
 * the certificate is done already.
 */
SECStatus
NSS_CMSSignerInfo_Verify(NSSCMSSignerInfo *signerinfo,
                         SECItem *digest,      /* may be NULL */
                         SECItem *contentType) /* may be NULL */
{
    SECKEYPublicKey *publickey = NULL;
    NSSCMSAttribute *attr;
    SECItem encoded_attrs;
    CERTCertificate *cert;
    NSSCMSVerificationStatus vs = NSSCMSVS_Unverified;
    PLArenaPool *poolp;
    SECOidTag digestalgtag;
    SECOidTag pubkAlgTag;

    if (signerinfo == NULL)
        return SECFailure;

    /* NSS_CMSSignerInfo_GetSigningCertificate will fail if 2nd parm is NULL
    ** and cert has not been verified
    */
    cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, NULL);
    if (cert == NULL) {
        vs = NSSCMSVS_SigningCertNotFound;
        goto loser;
    }

    if ((publickey = CERT_ExtractPublicKey(cert)) == NULL) {
        vs = NSSCMSVS_ProcessingError;
        goto loser;
    }

    digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo);
    pubkAlgTag = SECOID_GetAlgorithmTag(&(signerinfo->digestEncAlg));
    if ((pubkAlgTag == SEC_OID_UNKNOWN) || (digestalgtag == SEC_OID_UNKNOWN)) {
        vs = NSSCMSVS_SignatureAlgorithmUnknown;
        goto loser;
    }

    if (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr)) {
        if (contentType) {
            /*
         * Check content type
         *
         * RFC2630 sez 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.
         */
            attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr,
                                                          SEC_OID_PKCS9_CONTENT_TYPE, PR_TRUE);
            if (attr == NULL) {
                vs = NSSCMSVS_MalformedSignature;
                goto loser;
            }

            if (NSS_CMSAttribute_CompareValue(attr, contentType) == PR_FALSE) {
                vs = NSSCMSVS_MalformedSignature;
                goto loser;
            }
        }

        /*
     * Check digest
     */
        attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr,
                                                      SEC_OID_PKCS9_MESSAGE_DIGEST, PR_TRUE);
        if (attr == NULL) {
            vs = NSSCMSVS_MalformedSignature;
            goto loser;
        }
        if (!digest ||
            NSS_CMSAttribute_CompareValue(attr, digest) == PR_FALSE) {
            vs = NSSCMSVS_DigestMismatch;
            goto loser;
        }

        if ((poolp = PORT_NewArena(1024)) == NULL) {
            vs = NSSCMSVS_ProcessingError;
            goto loser;
        }

        /*
     * Check signature
     *
     * The signature is based on a digest of the DER-encoded authenticated
     * attributes.  So, first we encode and then we digest/verify.
     * we trust the decoder to have the attributes in the right (sorted)
     * order
     */
        encoded_attrs.data = NULL;
        encoded_attrs.len = 0;

        if (NSS_CMSAttributeArray_Encode(poolp, &(signerinfo->authAttr),
                                         &encoded_attrs) == NULL ||
            encoded_attrs.data == NULL || encoded_attrs.len == 0) {
            PORT_FreeArena(poolp, PR_FALSE);
            vs = NSSCMSVS_ProcessingError;
            goto loser;
        }

        vs = (VFY_VerifyDataDirect(encoded_attrs.data, encoded_attrs.len,
                                   publickey, &(signerinfo->encDigest), pubkAlgTag,
                                   digestalgtag, NULL, signerinfo->cmsg->pwfn_arg) != SECSuccess)
                 ? NSSCMSVS_BadSignature
                 : NSSCMSVS_GoodSignature;

        PORT_FreeArena(poolp, PR_FALSE); /* awkward memory management :-( */

    } else {
        SECItem *sig;

        /* No authenticated attributes.
    ** The signature is based on the plain message digest.
    */
        sig = &(signerinfo->encDigest);
        if (sig->len == 0)
            goto loser;

        vs = (!digest ||
              VFY_VerifyDigestDirect(digest, publickey, sig, pubkAlgTag,
                                     digestalgtag, signerinfo->cmsg->pwfn_arg) != SECSuccess)
                 ? NSSCMSVS_BadSignature
                 : NSSCMSVS_GoodSignature;
    }

    if (vs == NSSCMSVS_BadSignature) {
        int error = PORT_GetError();
        /*
     * 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 the PSM 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 (error == SEC_ERROR_BAD_SIGNATURE)
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
        /*
     * map algorithm failures to NSSCMSVS values
     */
        if ((error == SEC_ERROR_PKCS7_KEYALG_MISMATCH) ||
            (error == SEC_ERROR_INVALID_ALGORITHM)) {
            /* keep the same error code as 3.11 and before */
            PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
            vs = NSSCMSVS_SignatureAlgorithmUnsupported;
        }
    }

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

    signerinfo->verificationStatus = vs;

    return (vs == NSSCMSVS_GoodSignature) ? SECSuccess : SECFailure;

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

    signerinfo->verificationStatus = vs;

    PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE);
    return SECFailure;
}

NSSCMSVerificationStatus
NSS_CMSSignerInfo_GetVerificationStatus(NSSCMSSignerInfo *signerinfo)
{
    return signerinfo->verificationStatus;
}

SECOidData *
NSS_CMSSignerInfo_GetDigestAlg(NSSCMSSignerInfo *signerinfo)
{
    SECOidData *algdata;
    SECOidTag algtag;

    algdata = SECOID_FindOID(&(signerinfo->digestAlg.algorithm));
    if (algdata == NULL) {
        return algdata;
    }
    /* Windows may have given us a signer algorithm oid instead of a digest
     * algorithm oid. This call will map to a signer oid to a digest one,
     * otherwise it leaves the oid alone and let the chips fall as they may
     * if it's not a digest oid.
     */
    algtag = NSS_CMSUtil_MapSignAlgs(algdata->offset);
    if (algtag != algdata->offset) {
        /* if the tags don't match, then we must have received a signer
     * algorithID. Now we need to get the oid data for the digest
     * oid, which the rest of the code is expecting */
        algdata = SECOID_FindOIDByTag(algtag);
    }

    return algdata;
}

SECOidTag
NSS_CMSSignerInfo_GetDigestAlgTag(NSSCMSSignerInfo *signerinfo)
{
    SECOidData *algdata;

    if (!signerinfo) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SEC_OID_UNKNOWN;
    }

    algdata = NSS_CMSSignerInfo_GetDigestAlg(signerinfo);
    if (algdata != NULL)
        return algdata->offset;
    else
        return SEC_OID_UNKNOWN;
}

CERTCertificateList *
NSS_CMSSignerInfo_GetCertList(NSSCMSSignerInfo *signerinfo)
{
    return signerinfo->certList;
}

int
NSS_CMSSignerInfo_GetVersion(NSSCMSSignerInfo *signerinfo)
{
    unsigned long version;

    /* always take apart the SECItem */
    if (SEC_ASN1DecodeInteger(&(signerinfo->version), &version) != SECSuccess)
        return 0;
    else
        return (int)version;
}

/*
 * NSS_CMSSignerInfo_GetSigningTime - return the signing time,
 *                    in UTCTime or GeneralizedTime format,
 *                                    of a CMS signerInfo.
 *
 * sinfo - signerInfo data for this signer
 *
 * Returns a pointer to XXXX (what?)
 * A return value of NULL is an error.
 */
SECStatus
NSS_CMSSignerInfo_GetSigningTime(NSSCMSSignerInfo *sinfo, PRTime *stime)
{
    NSSCMSAttribute *attr;
    SECItem *value;

    if (sinfo == NULL)
        return SECFailure;

    if (sinfo->signingTime != 0) {
        *stime = sinfo->signingTime; /* cached copy */
        return SECSuccess;
    }

    attr = NSS_CMSAttributeArray_FindAttrByOidTag(sinfo->authAttr,
                                                  SEC_OID_PKCS9_SIGNING_TIME, PR_TRUE);
    /* XXXX multi-valued attributes NIH */
    if (attr == NULL || (value = NSS_CMSAttribute_GetValue(attr)) == NULL)
        return SECFailure;
    if (DER_DecodeTimeChoice(stime, value) != SECSuccess)
        return SECFailure;
    sinfo->signingTime = *stime; /* make cached copy */
    return SECSuccess;
}

/*
 * Return the signing cert of a CMS signerInfo.
 *
 * the certs in the enclosing SignedData must have been imported already
 */
CERTCertificate *
NSS_CMSSignerInfo_GetSigningCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb)
{
    CERTCertificate *cert;
    NSSCMSSignerIdentifier *sid;

    if (signerinfo->cert != NULL)
        return signerinfo->cert;

    /* no certdb, and cert hasn't been set yet? */
    if (certdb == NULL)
        return NULL;

    /*
     * 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.
     */
    sid = &signerinfo->signerIdentifier;
    switch (sid->identifierType) {
        case NSSCMSSignerID_IssuerSN:
            cert = CERT_FindCertByIssuerAndSN(certdb, sid->id.issuerAndSN);
            break;
        case NSSCMSSignerID_SubjectKeyID:
            cert = CERT_FindCertBySubjectKeyID(certdb, sid->id.subjectKeyID);
            break;
        default:
            cert = NULL;
            break;
    }

    /* cert can be NULL at that point */
    signerinfo->cert = cert; /* earmark it */

    return cert;
}

/*
 * NSS_CMSSignerInfo_GetSignerCommonName - return the common name of the signer
 *
 * sinfo - signerInfo data for this signer
 *
 * Returns a pointer to allocated memory, which must be freed with PORT_Free.
 * A return value of NULL is an error.
 */
char *
NSS_CMSSignerInfo_GetSignerCommonName(NSSCMSSignerInfo *sinfo)
{
    CERTCertificate *signercert;

    /* will fail if cert is not verified */
    if ((signercert = NSS_CMSSignerInfo_GetSigningCertificate(sinfo, NULL)) == NULL)
        return NULL;

    return (CERT_GetCommonName(&signercert->subject));
}

/*
 * NSS_CMSSignerInfo_GetSignerEmailAddress - return the common name of the signer
 *
 * sinfo - signerInfo data for this signer
 *
 * Returns a pointer to allocated memory, which must be freed.
 * A return value of NULL is an error.
 */
char *
NSS_CMSSignerInfo_GetSignerEmailAddress(NSSCMSSignerInfo *sinfo)
{
    CERTCertificate *signercert;

    if ((signercert = NSS_CMSSignerInfo_GetSigningCertificate(sinfo, NULL)) == NULL)
        return NULL;

    if (!signercert->emailAddr || !signercert->emailAddr[0])
        return NULL;

    return (PORT_Strdup(signercert->emailAddr));
}

/*
 * NSS_CMSSignerInfo_AddAuthAttr - add an attribute to the
 * authenticated (i.e. signed) attributes of "signerinfo".
 */
SECStatus
NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
{
    return NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr);
}

/*
 * NSS_CMSSignerInfo_AddUnauthAttr - add an attribute to the
 * unauthenticated attributes of "signerinfo".
 */
SECStatus
NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
{
    return NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->unAuthAttr), attr);
}

/*
 * NSS_CMSSignerInfo_AddSigningTime - add the signing time to the
 * authenticated (i.e. signed) attributes of "signerinfo".
 *
 * 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 do nothing.
 *
 * XXX This will probably just shove the current time into "signerinfo"
 * but it will not actually get signed until the entire item is
 * processed for encoding.  Is this (expected to be small) delay okay?
 */
SECStatus
NSS_CMSSignerInfo_AddSigningTime(NSSCMSSignerInfo *signerinfo, PRTime t)
{
    NSSCMSAttribute *attr;
    SECItem stime;
    void *mark;
    PLArenaPool *poolp;

    poolp = signerinfo->cmsg->poolp;

    mark = PORT_ArenaMark(poolp);

    /* create new signing time attribute */
    if (DER_EncodeTimeChoice(NULL, &stime, t) != SECSuccess)
        goto loser;

    if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_PKCS9_SIGNING_TIME, &stime, PR_FALSE)) == NULL) {
        SECITEM_FreeItem(&stime, PR_FALSE);
        goto loser;
    }

    SECITEM_FreeItem(&stime, PR_FALSE);

    if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess)
        goto loser;

    PORT_ArenaUnmark(poolp, mark);

    return SECSuccess;

loser:
    PORT_ArenaRelease(poolp, mark);
    return SECFailure;
}

/*
 * NSS_CMSSignerInfo_AddSMIMECaps - add a SMIMECapabilities attribute to the
 * authenticated (i.e. signed) attributes of "signerinfo".
 *
 * This is expected to be included in outgoing signed
 * messages for email (S/MIME).
 */
SECStatus
NSS_CMSSignerInfo_AddSMIMECaps(NSSCMSSignerInfo *signerinfo)
{
    NSSCMSAttribute *attr;
    SECItem *smimecaps = NULL;
    void *mark;
    PLArenaPool *poolp;

    poolp = signerinfo->cmsg->poolp;

    mark = PORT_ArenaMark(poolp);

    smimecaps = SECITEM_AllocItem(poolp, NULL, 0);
    if (smimecaps == NULL)
        goto loser;

    /* create new signing time attribute */
    if (NSS_SMIMEUtil_CreateSMIMECapabilities(poolp, smimecaps) != SECSuccess)
        goto loser;

    if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_PKCS9_SMIME_CAPABILITIES, smimecaps, PR_TRUE)) == NULL)
        goto loser;

    if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess)
        goto loser;

    PORT_ArenaUnmark(poolp, mark);
    return SECSuccess;

loser:
    PORT_ArenaRelease(poolp, mark);
    return SECFailure;
}

/*
 * NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs - add a SMIMEEncryptionKeyPreferences attribute to the
 * authenticated (i.e. signed) attributes of "signerinfo".
 *
 * This is expected to be included in outgoing signed messages for email (S/MIME).
 */
SECStatus
NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(NSSCMSSignerInfo *signerinfo, CERTCertificate *cert, CERTCertDBHandle *certdb)
{
    NSSCMSAttribute *attr;
    SECItem *smimeekp = NULL;
    void *mark;
    PLArenaPool *poolp;

    /* verify this cert for encryption */
    if (CERT_VerifyCert(certdb, cert, PR_TRUE, certUsageEmailRecipient, PR_Now(), signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) {
        return SECFailure;
    }

    poolp = signerinfo->cmsg->poolp;
    mark = PORT_ArenaMark(poolp);

    smimeekp = SECITEM_AllocItem(poolp, NULL, 0);
    if (smimeekp == NULL)
        goto loser;

    /* create new signing time attribute */
    if (NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs(poolp, smimeekp, cert) != SECSuccess)
        goto loser;

    if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE, smimeekp, PR_TRUE)) == NULL)
        goto loser;

    if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess)
        goto loser;

    PORT_ArenaUnmark(poolp, mark);
    return SECSuccess;

loser:
    PORT_ArenaRelease(poolp, mark);
    return SECFailure;
}

/*
 * NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs - add a SMIMEEncryptionKeyPreferences attribute to the
 * authenticated (i.e. signed) attributes of "signerinfo", using the OID preferred by Microsoft.
 *
 * This is expected to be included in outgoing signed messages for email (S/MIME),
 * if compatibility with Microsoft mail clients is wanted.
 */
SECStatus
NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(NSSCMSSignerInfo *signerinfo, CERTCertificate *cert, CERTCertDBHandle *certdb)
{
    NSSCMSAttribute *attr;
    SECItem *smimeekp = NULL;
    void *mark;
    PLArenaPool *poolp;

    /* verify this cert for encryption */
    if (CERT_VerifyCert(certdb, cert, PR_TRUE, certUsageEmailRecipient, PR_Now(), signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) {
        return SECFailure;
    }

    poolp = signerinfo->cmsg->poolp;
    mark = PORT_ArenaMark(poolp);

    smimeekp = SECITEM_AllocItem(poolp, NULL, 0);
    if (smimeekp == NULL)
        goto loser;

    /* create new signing time attribute */
    if (NSS_SMIMEUtil_CreateMSSMIMEEncKeyPrefs(poolp, smimeekp, cert) != SECSuccess)
        goto loser;

    if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE, smimeekp, PR_TRUE)) == NULL)
        goto loser;

    if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess)
        goto loser;

    PORT_ArenaUnmark(poolp, mark);
    return SECSuccess;

loser:
    PORT_ArenaRelease(poolp, mark);
    return SECFailure;
}

/*
 * NSS_CMSSignerInfo_AddCounterSignature - countersign a signerinfo
 *
 * 1. digest the DER-encoded signature value of the original signerinfo
 * 2. create new signerinfo with correct version, sid, digestAlg
 * 3. add message-digest authAttr, but NO content-type
 * 4. sign the authAttrs
 * 5. DER-encode the new signerInfo
 * 6. add the whole thing to original signerInfo's unAuthAttrs
 *    as a SEC_OID_PKCS9_COUNTER_SIGNATURE attribute
 *
 * XXXX give back the new signerinfo?
 */
SECStatus
NSS_CMSSignerInfo_AddCounterSignature(NSSCMSSignerInfo *signerinfo,
                                      SECOidTag digestalg, CERTCertificate signingcert)
{
    /* XXXX TBD XXXX */
    return SECFailure;
}

/*
 * XXXX the following needs to be done in the S/MIME layer code
 * after signature of a signerinfo is verified
 */
SECStatus
NSS_SMIMESignerInfo_SaveSMIMEProfile(NSSCMSSignerInfo *signerinfo)
{
    CERTCertificate *cert = NULL;
    SECItem *profile = NULL;
    NSSCMSAttribute *attr;
    SECItem *stime = NULL;
    SECItem *ekp;
    CERTCertDBHandle *certdb;
    int save_error;
    SECStatus rv;
    PRBool must_free_cert = PR_FALSE;

    certdb = CERT_GetDefaultCertDB();

    /* sanity check - see if verification status is ok (unverified does not count...) */
    if (signerinfo->verificationStatus != NSSCMSVS_GoodSignature)
        return SECFailure;

    /* find preferred encryption cert */
    if (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr) &&
        (attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr,
                                                       SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE, PR_TRUE)) != NULL) { /* we have a SMIME_ENCRYPTION_KEY_PREFERENCE attribute! */
        ekp = NSS_CMSAttribute_GetValue(attr);
        if (ekp == NULL)
            return SECFailure;

        /* we assume that all certs coming with the message have been imported to the */
        /* temporary database */
        cert = NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference(certdb, ekp);
        if (cert == NULL)
            return SECFailure;
        must_free_cert = PR_TRUE;
    }

    if (cert == NULL) {
        /* no preferred cert found?
     * find the cert the signerinfo is signed with instead */
        cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, certdb);
        if (cert == NULL || cert->emailAddr == NULL || !cert->emailAddr[0])
            return SECFailure;
    }

/* verify this cert for encryption (has been verified for signing so far) */
/* don't verify this cert for encryption. It may just be a signing cert.
     * that's OK, we can still save the S/MIME profile. The encryption cert
     * should have already been saved */
#ifdef notdef
    if (CERT_VerifyCert(certdb, cert, PR_TRUE, certUsageEmailRecipient, PR_Now(), signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) {
        if (must_free_cert)
            CERT_DestroyCertificate(cert);
        return SECFailure;
    }
#endif

    /* XXX store encryption cert permanently? */

    /*
     * 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 (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr)) {
        attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr,
                                                      SEC_OID_PKCS9_SMIME_CAPABILITIES,
                                                      PR_TRUE);
        profile = NSS_CMSAttribute_GetValue(attr);
        attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr,
                                                      SEC_OID_PKCS9_SIGNING_TIME,
                                                      PR_TRUE);
        stime = NSS_CMSAttribute_GetValue(attr);
    }

    rv = CERT_SaveSMimeProfile(cert, profile, stime);
    if (must_free_cert)
        CERT_DestroyCertificate(cert);

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

    return rv;
}

/*
 * NSS_CMSSignerInfo_IncludeCerts - set cert chain inclusion mode for this signer
 */
SECStatus
NSS_CMSSignerInfo_IncludeCerts(NSSCMSSignerInfo *signerinfo,
                               NSSCMSCertChainMode cm, SECCertUsage usage)
{
    if (signerinfo->cert == NULL)
        return SECFailure;

    /* don't leak if we get called twice */
    if (signerinfo->certList != NULL) {
        CERT_DestroyCertificateList(signerinfo->certList);
        signerinfo->certList = NULL;
    }

    switch (cm) {
        case NSSCMSCM_None:
            signerinfo->certList = NULL;
            break;
        case NSSCMSCM_CertOnly:
            signerinfo->certList = CERT_CertListFromCert(signerinfo->cert);
            break;
        case NSSCMSCM_CertChain:
            signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert,
                                                          usage, PR_FALSE);
            break;
        case NSSCMSCM_CertChainWithRoot:
            signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert,
                                                          usage, PR_TRUE);
            break;
    }

    if (cm != NSSCMSCM_None && signerinfo->certList == NULL)
        return SECFailure;

    return SECSuccess;
}
