/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

/*
 * PKCS7 encoding.
 *
 * $Id: p7encode.c,v 1.3 2001/01/30 21:02:14 wtc%netscape.com Exp $
 */

#include "nssrenam.h"

#include "p7local.h"

#include "cert.h"
#include "cryptohi.h"
#include "keyhi.h"
#include "secasn1.h"
#include "secoid.h"
#include "secitem.h"
#include "pk11func.h"
#include "secerr.h"
#include "sechash.h"	/* for HASH_GetHashObject() */

struct sec_pkcs7_encoder_output {
    SEC_PKCS7EncoderOutputCallback outputfn;
    void *outputarg;
};

struct SEC_PKCS7EncoderContextStr {
    SEC_ASN1EncoderContext *ecx;
    SEC_PKCS7ContentInfo *cinfo;
    struct sec_pkcs7_encoder_output output;
    sec_PKCS7CipherObject *encryptobj;
    const SECHashObject *digestobj;
    void *digestcx;
};


/*
 * The little output function that the ASN.1 encoder calls to hand
 * us bytes which we in turn hand back to our caller (via the callback
 * they gave us).
 */
static void
sec_pkcs7_encoder_out(void *arg, const char *buf, unsigned long len,
		      int depth, SEC_ASN1EncodingPart data_kind)
{
    struct sec_pkcs7_encoder_output *output;

    output = (struct sec_pkcs7_encoder_output*)arg;
    output->outputfn (output->outputarg, buf, len);
}

static sec_PKCS7CipherObject *
sec_pkcs7_encoder_start_encrypt (SEC_PKCS7ContentInfo *cinfo,
						 PK11SymKey *orig_bulkkey)
{
    SECOidTag kind;
    sec_PKCS7CipherObject *encryptobj;
    SEC_PKCS7RecipientInfo **recipientinfos, *ri;
    SEC_PKCS7EncryptedContentInfo *enccinfo;
    SEC_PKCS7SMIMEKEAParameters   keaParams;
    SECKEYPublicKey *publickey = NULL;
    SECKEYPrivateKey *ourPrivKey = NULL;
    PK11SymKey  *bulkkey;
    void *mark, *wincx;
    int i;
    PRArenaPool *arena = NULL;
    unsigned char zero = 0;

    /* Get the context in case we need it below. */
    wincx = cinfo->pwfn_arg;

    /* Clear keaParams, since cleanup code checks the lengths */
    (void) memset(&keaParams, 0, sizeof(keaParams));

    kind = SEC_PKCS7ContentType (cinfo);
    switch (kind) {
      default:
      case SEC_OID_PKCS7_DATA:
      case SEC_OID_PKCS7_DIGESTED_DATA:
      case SEC_OID_PKCS7_SIGNED_DATA:
	recipientinfos = NULL;
	enccinfo = NULL;
	break;
      case SEC_OID_PKCS7_ENCRYPTED_DATA:
	{
	    SEC_PKCS7EncryptedData *encdp;

	    /* To do EncryptedData we *must* be given a bulk key. */
	    PORT_Assert (orig_bulkkey != NULL);
	    if (orig_bulkkey == NULL) {
		/* XXX error? */
		return NULL;
	    }

	    encdp = cinfo->content.encryptedData;
	    recipientinfos = NULL;
	    enccinfo = &(encdp->encContentInfo);
	}
	break;
      case SEC_OID_PKCS7_ENVELOPED_DATA:
	{
	    SEC_PKCS7EnvelopedData *envdp;

	    envdp = cinfo->content.envelopedData;
	    recipientinfos = envdp->recipientInfos;
	    enccinfo = &(envdp->encContentInfo);
	}
	break;
      case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
	{
	    SEC_PKCS7SignedAndEnvelopedData *saedp;

	    saedp = cinfo->content.signedAndEnvelopedData;
	    recipientinfos = saedp->recipientInfos;
	    enccinfo = &(saedp->encContentInfo);
	}
	break;
    }

    if (enccinfo == NULL)
	return NULL;

    bulkkey = orig_bulkkey;
    if (bulkkey == NULL) {
	CK_MECHANISM_TYPE type = PK11_AlgtagToMechanism(enccinfo->encalg);
	PK11SlotInfo *slot;


	slot = PK11_GetBestSlot(type,cinfo->pwfn_arg);
	if (slot == NULL) {
	    return NULL;
	}
	bulkkey = PK11_KeyGen(slot,type,NULL, enccinfo->keysize/8,
			      cinfo->pwfn_arg);
	PK11_FreeSlot(slot);
	if (bulkkey == NULL) {
	    return NULL;
	}
    }

    encryptobj = NULL;
    mark = PORT_ArenaMark (cinfo->poolp);

    /*
     * Encrypt the bulk key with the public key of each recipient.
     */
    for (i = 0; recipientinfos && (ri = recipientinfos[i]) != NULL; i++) {
	CERTCertificate *cert;
	SECOidTag certalgtag, encalgtag;
	SECStatus rv;
	int data_len;
	SECItem *params = NULL;

	cert = ri->cert;
	PORT_Assert (cert != NULL);
	if (cert == NULL)
	    continue;

	/*
	 * XXX Want an interface that takes a cert and some data and
	 * fills in an algorithmID and encrypts the data with the public
	 * key from the cert.  Or, give me two interfaces -- one which
	 * gets the algorithm tag from a cert (I should not have to go
	 * down into the subjectPublicKeyInfo myself) and another which
	 * takes a public key and algorithm tag and data and encrypts
	 * the data.  Or something like that.  The point is that all
	 * of the following hardwired RSA and KEA stuff should be done
	 * elsewhere.
	 */

	certalgtag=SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));

	switch (certalgtag) {
	case SEC_OID_PKCS1_RSA_ENCRYPTION:
	    encalgtag = certalgtag;
	    publickey = CERT_ExtractPublicKey (cert);
	    if (publickey == NULL) goto loser;
		
	    data_len = SECKEY_PublicKeyStrength(publickey);
	    ri->encKey.data = 
	        (unsigned char*)PORT_ArenaAlloc(cinfo->poolp ,data_len);
	    ri->encKey.len = data_len;
	    if (ri->encKey.data == NULL) goto loser;

	    rv = PK11_PubWrapSymKey(PK11_AlgtagToMechanism(certalgtag),publickey,
				bulkkey,&ri->encKey);

	    SECKEY_DestroyPublicKey(publickey);
	    publickey = NULL;
	    if (rv != SECSuccess) goto loser;
	    params = NULL; /* paranoia */
	    break;
	/* ### mwelch -- KEA */ 
      case SEC_OID_MISSI_KEA_DSS_OLD:
      case SEC_OID_MISSI_KEA_DSS:
      case SEC_OID_MISSI_KEA:
	    {
#define SMIME_FORTEZZA_RA_LENGTH 128
#define SMIME_FORTEZZA_IV_LENGTH 24
#define SMIME_FORTEZZA_MAX_KEY_SIZE 256
		SECStatus err;
		PK11SymKey *tek;
		CERTCertificate *ourCert;
		SECKEYPublicKey *ourPubKey;
		SECKEATemplateSelector whichKEA;

		/* We really want to show our KEA tag as the
		   key exchange algorithm tag. */
		encalgtag = SEC_OID_NETSCAPE_SMIME_KEA;

		/* Get the public key of the recipient. */
		publickey = CERT_ExtractPublicKey(cert);
		if (publickey == NULL) goto loser;

		/* Find our own cert, and extract its keys. */
		ourCert = PK11_FindBestKEAMatch(cert,wincx);
		if (ourCert == NULL) goto loser;

		arena = PORT_NewArena(1024);
		if (arena == NULL) goto loser;

		ourPubKey = CERT_ExtractPublicKey(ourCert);
		if (ourPubKey == NULL)
		{
		    CERT_DestroyCertificate(ourCert);
		    goto loser;
		}

		/* While we're here, copy the public key into the outgoing
		 * KEA parameters. */
		SECITEM_CopyItem(arena, &(keaParams.originatorKEAKey),
				 &(ourPubKey->u.fortezza.KEAKey));
		SECKEY_DestroyPublicKey(ourPubKey);
		ourPubKey = NULL;

		/* Extract our private key in order to derive the 
		 * KEA key. */
		ourPrivKey = PK11_FindKeyByAnyCert(ourCert,wincx);
		CERT_DestroyCertificate(ourCert); /* we're done with this */
		if (!ourPrivKey) goto loser;

		/* Prepare raItem with 128 bytes (filled with zeros). */
		keaParams.originatorRA.data = 
		  (unsigned char*)PORT_ArenaAlloc(arena,SMIME_FORTEZZA_RA_LENGTH);
		keaParams.originatorRA.len = SMIME_FORTEZZA_RA_LENGTH;


		/* Generate the TEK (token exchange key) which we use
		 * to wrap the bulk encryption key. (raItem) will be
		 * filled with a random seed which we need to send to
		 * the recipient. */
		tek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE,
				     &keaParams.originatorRA, NULL,
				     CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP,
				     CKA_WRAP, 0, wincx);

		    SECKEY_DestroyPublicKey(publickey);
		    SECKEY_DestroyPrivateKey(ourPrivKey);
		    publickey = NULL;
		    ourPrivKey = NULL;
		
		if (!tek)
		    goto loser;

		ri->encKey.data = (unsigned char*)PORT_ArenaAlloc(cinfo->poolp,
						  SMIME_FORTEZZA_MAX_KEY_SIZE);
		ri->encKey.len = SMIME_FORTEZZA_MAX_KEY_SIZE;

		if (ri->encKey.data == NULL)
		{
		    PK11_FreeSymKey(tek);
		    goto loser;
		}

		/* Wrap the bulk key. What we do with the resulting data
		   depends on whether we're using Skipjack to wrap the key. */
		switch(PK11_AlgtagToMechanism(enccinfo->encalg))
		{
		case CKM_SKIPJACK_CBC64:
		case CKM_SKIPJACK_ECB64:
		case CKM_SKIPJACK_OFB64:
		case CKM_SKIPJACK_CFB64:
		case CKM_SKIPJACK_CFB32:
		case CKM_SKIPJACK_CFB16:
		case CKM_SKIPJACK_CFB8:
		    /* do SKIPJACK, we use the wrap mechanism */
		    err = PK11_WrapSymKey(CKM_SKIPJACK_WRAP, NULL, 
				      tek, bulkkey, &ri->encKey);
		    whichKEA = SECKEAUsesSkipjack;
		    break;
		default:
		    /* Not SKIPJACK, we encrypt the raw key data */
		    keaParams.nonSkipjackIV .data = 
		      (unsigned char*)PORT_ArenaAlloc(arena,
						     SMIME_FORTEZZA_IV_LENGTH);
		    keaParams.nonSkipjackIV.len = SMIME_FORTEZZA_IV_LENGTH;
		    err = PK11_WrapSymKey(CKM_SKIPJACK_CBC64,
					  &keaParams.nonSkipjackIV, 
				          tek, bulkkey, &ri->encKey);
		    if (err != SECSuccess)
			goto loser;

		    if (ri->encKey.len != PK11_GetKeyLength(bulkkey))
		    {
			/* The size of the encrypted key is not the same as
			   that of the original bulk key, presumably due to
			   padding. Encode and store the real size of the
			   bulk key. */
			if (SEC_ASN1EncodeInteger(arena, 
						  &keaParams.bulkKeySize,
						  PK11_GetKeyLength(bulkkey))
			    == NULL)
			    err = (SECStatus)PORT_GetError();
			else
			    /* use full template for encoding */
			    whichKEA = SECKEAUsesNonSkipjackWithPaddedEncKey;
		    }
		    else
			/* enc key length == bulk key length */
			whichKEA = SECKEAUsesNonSkipjack; 
		    break;
		}

		PK11_FreeSymKey(tek);
		if (err != SECSuccess)
		    goto loser;

		/* Encode the KEA parameters into the recipient info. */
		params = SEC_ASN1EncodeItem(arena,NULL, &keaParams, 
				      sec_pkcs7_get_kea_template(whichKEA));
		if (params == NULL) goto loser;
		break;
	    }
	default:
	    PORT_SetError (SEC_ERROR_INVALID_ALGORITHM);
	    goto loser;
	}

	rv = SECOID_SetAlgorithmID(cinfo->poolp, &ri->keyEncAlg, encalgtag, 
			params);
	if (rv != SECSuccess)
	    goto loser;
	if (arena) PORT_FreeArena(arena,PR_FALSE);
	arena = NULL;
    }

    encryptobj = sec_PKCS7CreateEncryptObject (cinfo->poolp, bulkkey,
					       enccinfo->encalg,
					       &(enccinfo->contentEncAlg));
    if (encryptobj != NULL) {
	PORT_ArenaUnmark (cinfo->poolp, mark);
	mark = NULL;		/* good one; do not want to release */
    }
    /* fallthru */

loser:
    if (arena) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    if (publickey) {
        SECKEY_DestroyPublicKey(publickey);
    }
    if (ourPrivKey) {
        SECKEY_DestroyPrivateKey(ourPrivKey);
    }
    if (mark != NULL) {
	PORT_ArenaRelease (cinfo->poolp, mark);
    }
    if (orig_bulkkey == NULL) {
	if (bulkkey) PK11_FreeSymKey(bulkkey);
    }

    return encryptobj;
}


static void
sec_pkcs7_encoder_notify (void *arg, PRBool before, void *dest, int depth)
{
    SEC_PKCS7EncoderContext *p7ecx;
    SEC_PKCS7ContentInfo *cinfo;
    SECOidTag kind;
    PRBool before_content;

    /*
     * We want to notice just before the content field.  After fields are
     * not interesting to us.
     */
    if (!before)
	return;

    p7ecx = (SEC_PKCS7EncoderContext*)arg;
    cinfo = p7ecx->cinfo;

    before_content = PR_FALSE;

    /*
     * Watch for the content field, at which point we want to instruct
     * the ASN.1 encoder to start taking bytes from the buffer.
     *
     * XXX The following assumes the inner content type is data;
     * if/when we want to handle fully nested types, this will have
     * to recurse until reaching the innermost data content.
     */
    kind = SEC_PKCS7ContentType (cinfo);
    switch (kind) {
      default:
      case SEC_OID_PKCS7_DATA:
	if (dest == &(cinfo->content.data))
	    before_content = PR_TRUE;
	break;

      case SEC_OID_PKCS7_DIGESTED_DATA:
	{
	    SEC_PKCS7DigestedData *digd;

	    digd = cinfo->content.digestedData;
	    if (digd == NULL)
		break;

	    if (dest == &(digd->contentInfo.content))
		before_content = PR_TRUE;
	}
	break;

      case SEC_OID_PKCS7_ENCRYPTED_DATA:
	{
	    SEC_PKCS7EncryptedData *encd;

	    encd = cinfo->content.encryptedData;
	    if (encd == NULL)
		break;

	    if (dest == &(encd->encContentInfo.encContent))
		before_content = PR_TRUE;
	}
	break;

      case SEC_OID_PKCS7_ENVELOPED_DATA:
	{
	    SEC_PKCS7EnvelopedData *envd;

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

	    if (dest == &(envd->encContentInfo.encContent))
		before_content = PR_TRUE;
	}
	break;

      case SEC_OID_PKCS7_SIGNED_DATA:
	{
	    SEC_PKCS7SignedData *sigd;

	    sigd = cinfo->content.signedData;
	    if (sigd == NULL)
		break;

	    if (dest == &(sigd->contentInfo.content))
		before_content = PR_TRUE;
	}
	break;

      case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
	{
	    SEC_PKCS7SignedAndEnvelopedData *saed;

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

	    if (dest == &(saed->encContentInfo.encContent))
		before_content = PR_TRUE;
	}
	break;
    }

    if (before_content) {
	/*
	 * This will cause the next SEC_ASN1EncoderUpdate to take the
	 * contents bytes from the passed-in buffer.
	 */
	SEC_ASN1EncoderSetTakeFromBuf (p7ecx->ecx);
	/*
	 * And that is all we needed this notify function for.
	 */
	SEC_ASN1EncoderClearNotifyProc (p7ecx->ecx);
    }
}


static SEC_PKCS7EncoderContext *
sec_pkcs7_encoder_start_contexts (SEC_PKCS7ContentInfo *cinfo,
				  PK11SymKey *bulkkey)
{
    SEC_PKCS7EncoderContext *p7ecx;
    SECOidTag kind;
    PRBool encrypt;
    SECItem **digests;
    SECAlgorithmID *digestalg, **digestalgs;

    p7ecx = 
      (SEC_PKCS7EncoderContext*)PORT_ZAlloc (sizeof(SEC_PKCS7EncoderContext));
    if (p7ecx == NULL)
	return NULL;

    digests = NULL;
    digestalg = NULL;
    digestalgs = NULL;
    encrypt = PR_FALSE;

    kind = SEC_PKCS7ContentType (cinfo);
    switch (kind) {
      default:
      case SEC_OID_PKCS7_DATA:
	break;
      case SEC_OID_PKCS7_DIGESTED_DATA:
	digestalg = &(cinfo->content.digestedData->digestAlg);
	break;
      case SEC_OID_PKCS7_SIGNED_DATA:
	digests = cinfo->content.signedData->digests;
	digestalgs = cinfo->content.signedData->digestAlgorithms;
	break;
      case SEC_OID_PKCS7_ENCRYPTED_DATA:
      case SEC_OID_PKCS7_ENVELOPED_DATA:
	encrypt = PR_TRUE;
	break;
      case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
	digests = cinfo->content.signedAndEnvelopedData->digests;
	digestalgs = cinfo->content.signedAndEnvelopedData->digestAlgorithms;
	encrypt = PR_TRUE;
	break;
    }

    if (encrypt) {
	p7ecx->encryptobj = sec_pkcs7_encoder_start_encrypt (cinfo, bulkkey);
	if (p7ecx->encryptobj == NULL) {
	    PORT_Free (p7ecx);
	    return NULL;
	}
    }

    if (digestalgs != NULL) {
	if (digests != NULL) {
	    /* digests already created (probably for detached data) */
	    digestalg = NULL;
	} else {
	    /*
	     * XXX Some day we should handle multiple digests; for now,
	     * assume only one will be done.
	     */
	    PORT_Assert (digestalgs[0] != NULL && digestalgs[1] == NULL);
	    digestalg = digestalgs[0];
	}
    }

    if (digestalg != NULL) {
	SECOidData *oiddata;

	oiddata = SECOID_FindOID (&(digestalg->algorithm));
	if (oiddata != NULL) {
	    switch (oiddata->offset) {
	      case SEC_OID_MD2:
		p7ecx->digestobj = HASH_GetHashObject(HASH_AlgMD2);
		break;
	      case SEC_OID_MD5:
		p7ecx->digestobj = HASH_GetHashObject(HASH_AlgMD5);
		break;
	      case SEC_OID_SHA1:
		p7ecx->digestobj = HASH_GetHashObject(HASH_AlgSHA1);
		break;
	      default:
		/* XXX right error? */
		PORT_SetError (SEC_ERROR_INVALID_ALGORITHM);
		break;
	    }
	}
	if (p7ecx->digestobj != NULL) {
	    p7ecx->digestcx = (* p7ecx->digestobj->create) ();
	    if (p7ecx->digestcx == NULL)
		p7ecx->digestobj = NULL;
	    else
		(* p7ecx->digestobj->begin) (p7ecx->digestcx);
	}
	if (p7ecx->digestobj == NULL) {
	    if (p7ecx->encryptobj != NULL)
		sec_PKCS7DestroyEncryptObject (p7ecx->encryptobj);
	    PORT_Free (p7ecx);
	    return NULL;
	}
    }

    p7ecx->cinfo = cinfo;
    return p7ecx;
}


SEC_PKCS7EncoderContext *
SEC_PKCS7EncoderStart (SEC_PKCS7ContentInfo *cinfo,
		       SEC_PKCS7EncoderOutputCallback outputfn,
		       void *outputarg,
		       PK11SymKey *bulkkey)
{
    SEC_PKCS7EncoderContext *p7ecx;
    SECStatus rv;

    p7ecx = sec_pkcs7_encoder_start_contexts (cinfo, bulkkey);
    if (p7ecx == NULL)
	return NULL;

    p7ecx->output.outputfn = outputfn;
    p7ecx->output.outputarg = outputarg;

    /*
     * Initialize the BER encoder.
     */
    p7ecx->ecx = SEC_ASN1EncoderStart (cinfo, sec_PKCS7ContentInfoTemplate,
				       sec_pkcs7_encoder_out, &(p7ecx->output));
    if (p7ecx->ecx == NULL) {
	PORT_Free (p7ecx);
	return NULL;
    }

    /*
     * Indicate that we are streaming.  We will be streaming until we
     * get past the contents bytes.
     */
    SEC_ASN1EncoderSetStreaming (p7ecx->ecx);

    /*
     * The notify function will watch for the contents field.
     */
    SEC_ASN1EncoderSetNotifyProc (p7ecx->ecx, sec_pkcs7_encoder_notify, p7ecx);

    /*
     * This will encode everything up to the content bytes.  (The notify
     * function will then cause the encoding to stop there.)  Then our
     * caller can start passing contents bytes to our Update, which we
     * will pass along.
     */
    rv = SEC_ASN1EncoderUpdate (p7ecx->ecx, NULL, 0);
    if (rv != SECSuccess) {
	PORT_Free (p7ecx);
	return NULL;
    }

    return p7ecx;
}


/*
 * XXX If/when we support nested contents, this needs to be revised.
 */
static SECStatus
sec_pkcs7_encoder_work_data (SEC_PKCS7EncoderContext *p7ecx, SECItem *dest,
			     const unsigned char *data, unsigned long len,
			     PRBool final)
{
    unsigned char *buf = NULL;
    SECStatus rv;


    rv = SECSuccess;		/* may as well be optimistic */

    /*
     * We should really have data to process, or we should be trying
     * to finish/flush the last block.  (This is an overly paranoid
     * check since all callers are in this file and simple inspection
     * proves they do it right.  But it could find a bug in future
     * modifications/development, that is why it is here.)
     */
    PORT_Assert ((data != NULL && len) || final);

    /*
     * Update the running digest.
     * XXX This needs modification if/when we handle multiple digests.
     */
    if (len && p7ecx->digestobj != NULL) {
	(* p7ecx->digestobj->update) (p7ecx->digestcx, data, len);
    }

    /*
     * Encrypt this chunk.
     */
    if (p7ecx->encryptobj != NULL) {
	/* XXX the following lengths should all be longs? */
	unsigned int inlen;	/* length of data being encrypted */
	unsigned int outlen;	/* length of encrypted data */
	unsigned int buflen;	/* length available for encrypted data */

	inlen = len;
	buflen = sec_PKCS7EncryptLength (p7ecx->encryptobj, inlen, final);
	if (buflen == 0) {
	    /*
	     * No output is expected, but the input data may be buffered
	     * so we still have to call Encrypt.
	     */
	    rv = sec_PKCS7Encrypt (p7ecx->encryptobj, NULL, NULL, 0,
				   data, inlen, final);
	    if (final) {
		len = 0;
		goto done;
	    }
	    return rv;
	}

	if (dest != NULL)
	    buf = (unsigned char*)PORT_ArenaAlloc(p7ecx->cinfo->poolp, buflen);
	else
	    buf = (unsigned char*)PORT_Alloc (buflen);

	if (buf == NULL) {
	    rv = SECFailure;
	} else {
	    rv = sec_PKCS7Encrypt (p7ecx->encryptobj, buf, &outlen, buflen,
				   data, inlen, final);
	    data = buf;
	    len = outlen;
	}
	if (rv != SECSuccess) {
	    if (final)
		goto done;
	    return rv;
	}
    }

    if (p7ecx->ecx != NULL) {
	/*
	 * Encode the contents bytes.
	 */
	if(len) {
	    rv = SEC_ASN1EncoderUpdate (p7ecx->ecx, (const char *)data, len);
	}
    }

done:
    if (p7ecx->encryptobj != NULL) {
	if (final)
	    sec_PKCS7DestroyEncryptObject (p7ecx->encryptobj);
	if (dest != NULL) {
	    dest->data = buf;
	    dest->len = len;
	} else if (buf != NULL) {
	    PORT_Free (buf);
	}
    }

    if (final && p7ecx->digestobj != NULL) {
	SECItem *digest, **digests, ***digestsp;
	unsigned char *digdata;
	SECOidTag kind;

	kind = SEC_PKCS7ContentType (p7ecx->cinfo);
	switch (kind) {
	  default:
	    PORT_Assert (0);
	    return SECFailure;
	  case SEC_OID_PKCS7_DIGESTED_DATA:
	    digest = &(p7ecx->cinfo->content.digestedData->digest);
	    digestsp = NULL;
	    break;
	  case SEC_OID_PKCS7_SIGNED_DATA:
	    digest = NULL;
	    digestsp = &(p7ecx->cinfo->content.signedData->digests);
	    break;
	  case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
	    digest = NULL;
	    digestsp = &(p7ecx->cinfo->content.signedAndEnvelopedData->digests);
	    break;
	}

	digdata = (unsigned char*)PORT_ArenaAlloc (p7ecx->cinfo->poolp,
				   p7ecx->digestobj->length);
	if (digdata == NULL)
	    return SECFailure;

	if (digestsp != NULL) {
	    PORT_Assert (digest == NULL);

	    digest = (SECItem*)PORT_ArenaAlloc (p7ecx->cinfo->poolp, 
						sizeof(SECItem));
	    digests = (SECItem**)PORT_ArenaAlloc (p7ecx->cinfo->poolp,
				       2 * sizeof(SECItem *));
	    if (digests == NULL || digest == NULL)
		return SECFailure;

	    digests[0] = digest;
	    digests[1] = NULL;

	    *digestsp = digests;
	}

	PORT_Assert (digest != NULL);

	digest->data = digdata;
	digest->len = p7ecx->digestobj->length;

	(* p7ecx->digestobj->end) (p7ecx->digestcx, digest->data,
				   &(digest->len), digest->len);
	(* p7ecx->digestobj->destroy) (p7ecx->digestcx, PR_TRUE);
    }

    return rv;
}


SECStatus
SEC_PKCS7EncoderUpdate (SEC_PKCS7EncoderContext *p7ecx,
			const char *data, unsigned long len)
{
    /* XXX Error handling needs help.  Return what?  Do "Finish" on failure? */
    return sec_pkcs7_encoder_work_data (p7ecx, NULL,
					(const unsigned char *)data, len,
					PR_FALSE);
}


/*
 * XXX I would *really* like to not have to do this, but the current
 * signing interface gives me little choice.
 */
static SECOidTag
sec_pkcs7_pick_sign_alg (SECOidTag hashalg, SECOidTag encalg)
{
    switch (encalg) {
      case SEC_OID_PKCS1_RSA_ENCRYPTION:
	switch (hashalg) {
	  case SEC_OID_MD2:
	    return SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION;
	  case SEC_OID_MD5:
	    return SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION;
	  case SEC_OID_SHA1:
	    return SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
	  default:
	    return SEC_OID_UNKNOWN;
	}
      case SEC_OID_ANSIX9_DSA_SIGNATURE:
      case SEC_OID_MISSI_KEA_DSS:
      case SEC_OID_MISSI_DSS:
	switch (hashalg) {
	  case SEC_OID_SHA1:
	    return SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST;
	  default:
	    return SEC_OID_UNKNOWN;
	}
      default:
	break;
    }

    return encalg;		/* maybe it is already the right algid */
}


static SECStatus
sec_pkcs7_encoder_sig_and_certs (SEC_PKCS7ContentInfo *cinfo,
				 SECKEYGetPasswordKey pwfn, void *pwfnarg)
{
    SECOidTag kind;
    CERTCertificate **certs;
    CERTCertificateList **certlists;
    SECAlgorithmID **digestalgs;
    SECItem **digests;
    SEC_PKCS7SignerInfo *signerinfo, **signerinfos;
    SECItem **rawcerts, ***rawcertsp;
    PRArenaPool *poolp;
    int certcount;
    int ci, cli, rci, si;

    kind = SEC_PKCS7ContentType (cinfo);
    switch (kind) {
      default:
      case SEC_OID_PKCS7_DATA:
      case SEC_OID_PKCS7_DIGESTED_DATA:
      case SEC_OID_PKCS7_ENCRYPTED_DATA:
      case SEC_OID_PKCS7_ENVELOPED_DATA:
	certs = NULL;
	certlists = NULL;
	digestalgs = NULL;
	digests = NULL;
	signerinfos = NULL;
	rawcertsp = NULL;
	break;
      case SEC_OID_PKCS7_SIGNED_DATA:
	{
	    SEC_PKCS7SignedData *sdp;

	    sdp = cinfo->content.signedData;
	    certs = sdp->certs;
	    certlists = sdp->certLists;
	    digestalgs = sdp->digestAlgorithms;
	    digests = sdp->digests;
	    signerinfos = sdp->signerInfos;
	    rawcertsp = &(sdp->rawCerts);
	}
	break;
      case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
	{
	    SEC_PKCS7SignedAndEnvelopedData *saedp;

	    saedp = cinfo->content.signedAndEnvelopedData;
	    certs = saedp->certs;
	    certlists = saedp->certLists;
	    digestalgs = saedp->digestAlgorithms;
	    digests = saedp->digests;
	    signerinfos = saedp->signerInfos;
	    rawcertsp = &(saedp->rawCerts);
	}
	break;
    }

    if (certs == NULL && certlists == NULL && signerinfos == NULL)
	return SECSuccess;		/* nothing for us to do! */

    poolp = cinfo->poolp;
    certcount = 0;

    if (signerinfos != NULL) {
	SECOidTag digestalgtag;
	int di;
	SECStatus rv;
	CERTCertificate *cert;
	SECKEYPrivateKey *privkey;
	SECItem signature;
	SECOidTag signalgtag;

	PORT_Assert (digestalgs != NULL && digests != NULL);

	/*
	 * If one fails, we bail right then.  If we want to continue and
	 * try to do subsequent signatures, this loop, and the departures
	 * from it, will need to be reworked.
	 */
	for (si = 0; signerinfos[si] != NULL; si++) {

	    signerinfo = signerinfos[si];

	    /* find right digest */
	    digestalgtag = SECOID_GetAlgorithmTag (&(signerinfo->digestAlg));
	    for (di = 0; digestalgs[di] != NULL; di++) {
		/* XXX Should I be comparing more than the tag? */
		if (digestalgtag == SECOID_GetAlgorithmTag (digestalgs[di]))
		    break;
	    }
	    if (digestalgs[di] == NULL) {
		/* XXX oops; do what? set an error? */
		return SECFailure;
	    }
	    PORT_Assert (digests[di] != NULL);

	    cert = signerinfo->cert;
	    privkey = PK11_FindKeyByAnyCert (cert, pwfnarg);
	    if (privkey == NULL)
		return SECFailure;

	    /*
	     * XXX I think there should be a cert-level interface for this,
	     * so that I do not have to know about subjectPublicKeyInfo...
	     */
	    signalgtag = SECOID_GetAlgorithmTag (&(cert->subjectPublicKeyInfo.algorithm));

	    /* Fortezza MISSI have weird signature formats.  Map them
	     * to standard DSA formats */
	    signalgtag = PK11_FortezzaMapSig(signalgtag);

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

		/*
		 * First, find and fill in the message digest attribute.
		 */
		attr = sec_PKCS7FindAttribute (signerinfo->authAttr,
					       SEC_OID_PKCS9_MESSAGE_DIGEST,
					       PR_TRUE);
		PORT_Assert (attr != NULL);
		if (attr == NULL) {
		    SECKEY_DestroyPrivateKey (privkey);
		    return SECFailure;
		}

		/*
		 * XXX The second half of the following assertion prevents
		 * the encoder from being called twice on the same content.
		 * Either just remove the second half the assertion, or
		 * change the code to check if the value already there is
		 * the same as digests[di], whichever seems more right.
		 */
		PORT_Assert (attr->values != NULL && attr->values[0] == NULL);
		attr->values[0] = digests[di];

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

		encoded_attrs.data = NULL;
		encoded_attrs.len = 0;
		dummy = sec_PKCS7EncodeAttributes (NULL, &encoded_attrs,
						   &(signerinfo->authAttr));
		if (dummy == NULL) {
		    SECKEY_DestroyPrivateKey (privkey);
		    return SECFailure;
		}

		rv = SEC_SignData (&signature,
				   encoded_attrs.data, encoded_attrs.len,
				   privkey,
				   sec_pkcs7_pick_sign_alg (digestalgtag,
							    signalgtag));
		SECITEM_FreeItem (&encoded_attrs, PR_FALSE);
	    } else {
		rv = SGN_Digest (privkey, digestalgtag, &signature,
				 digests[di]);
	    }

	    SECKEY_DestroyPrivateKey (privkey);

	    if (rv != SECSuccess)
		return rv;

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

	    SECITEM_FreeItem (&signature, PR_FALSE);

	    rv = SECOID_SetAlgorithmID (poolp, &(signerinfo->digestEncAlg),
					signalgtag, NULL);
	    if (rv != SECSuccess)
		return SECFailure;

	    /*
	     * Count the cert chain for this signer.
	     */
	    if (signerinfo->certList != NULL)
		certcount += signerinfo->certList->len;
	}
    }

    if (certs != NULL) {
	for (ci = 0; certs[ci] != NULL; ci++)
	    certcount++;
    }

    if (certlists != NULL) {
	for (cli = 0; certlists[cli] != NULL; cli++)
	    certcount += certlists[cli]->len;
    }

    if (certcount == 0)
	return SECSuccess;		/* signing done; no certs */

    /*
     * Combine all of the certs and cert chains into rawcerts.
     * Note: certcount is an upper bound; we may not need that many slots
     * but we will allocate anyway to avoid having to do another pass.
     * (The temporary space saving is not worth it.)
     */
    rawcerts = (SECItem**)PORT_ArenaAlloc (poolp, 
					(certcount + 1) * sizeof(SECItem *));
    if (rawcerts == NULL)
	return SECFailure;

    /*
     * XXX Want to check for duplicates and not add *any* cert that is
     * already in the set.  This will be more important when we start
     * dealing with larger sets of certs, dual-key certs (signing and
     * encryption), etc.  For the time being we can slide by...
     */
    rci = 0;
    if (signerinfos != NULL) {
	for (si = 0; signerinfos[si] != NULL; si++) {
	    signerinfo = signerinfos[si];
	    for (ci = 0; ci < signerinfo->certList->len; ci++)
		rawcerts[rci++] = &(signerinfo->certList->certs[ci]);
	}

    }

    if (certs != NULL) {
	for (ci = 0; certs[ci] != NULL; ci++)
	    rawcerts[rci++] = &(certs[ci]->derCert);
    }

    if (certlists != NULL) {
	for (cli = 0; certlists[cli] != NULL; cli++) {
	    for (ci = 0; ci < certlists[cli]->len; ci++)
		rawcerts[rci++] = &(certlists[cli]->certs[ci]);
	}
    }

    rawcerts[rci] = NULL;
    *rawcertsp = rawcerts;

    return SECSuccess;
}


SECStatus
SEC_PKCS7EncoderFinish (SEC_PKCS7EncoderContext *p7ecx,
			SECKEYGetPasswordKey pwfn, void *pwfnarg)
{
    SECStatus rv;

    /*
     * Flush out any remaining data.
     */
    rv = sec_pkcs7_encoder_work_data (p7ecx, NULL, NULL, 0, PR_TRUE);

    /*
     * Turn off streaming stuff.
     */
    SEC_ASN1EncoderClearTakeFromBuf (p7ecx->ecx);
    SEC_ASN1EncoderClearStreaming (p7ecx->ecx);

    if (rv != SECSuccess)
	goto loser;

    rv = sec_pkcs7_encoder_sig_and_certs (p7ecx->cinfo, pwfn, pwfnarg);
    if (rv != SECSuccess)
	goto loser;

    rv = SEC_ASN1EncoderUpdate (p7ecx->ecx, NULL, 0);

loser:
    SEC_ASN1EncoderFinish (p7ecx->ecx);
    PORT_Free (p7ecx);
    return rv;
}


/*
 * After this routine is called, the entire PKCS7 contentInfo is ready
 * to be encoded.  This is used internally, but can also be called from
 * elsewhere for those who want to be able to just have pointers to
 * the ASN1 template for pkcs7 contentInfo built into their own encodings.
 */
SECStatus
SEC_PKCS7PrepareForEncode (SEC_PKCS7ContentInfo *cinfo,
			   PK11SymKey *bulkkey,
			   SECKEYGetPasswordKey pwfn,
			   void *pwfnarg)
{
    SEC_PKCS7EncoderContext *p7ecx;
    SECItem *content, *enc_content;
    SECStatus rv;

    p7ecx = sec_pkcs7_encoder_start_contexts (cinfo, bulkkey);
    if (p7ecx == NULL)
	return SECFailure;

    content = SEC_PKCS7GetContent (cinfo);

    if (p7ecx->encryptobj != NULL) {
	SECOidTag kind;
	SEC_PKCS7EncryptedContentInfo *enccinfo;

	kind = SEC_PKCS7ContentType (p7ecx->cinfo);
	switch (kind) {
	  default:
	    PORT_Assert (0);
	    rv = SECFailure;
	    goto loser;
	  case SEC_OID_PKCS7_ENCRYPTED_DATA:
	    enccinfo = &(p7ecx->cinfo->content.encryptedData->encContentInfo);
	    break;
	  case SEC_OID_PKCS7_ENVELOPED_DATA:
	    enccinfo = &(p7ecx->cinfo->content.envelopedData->encContentInfo);
	    break;
	  case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
	    enccinfo = &(p7ecx->cinfo->content.signedAndEnvelopedData->encContentInfo);
	    break;
	}
	enc_content = &(enccinfo->encContent);
    } else {
	enc_content = NULL;
    }

    if (content != NULL && content->data != NULL && content->len) {
	rv = sec_pkcs7_encoder_work_data (p7ecx, enc_content,
					  content->data, content->len, PR_TRUE);
	if (rv != SECSuccess)
	    goto loser;
    }

    rv = sec_pkcs7_encoder_sig_and_certs (cinfo, pwfn, pwfnarg);

loser:
    PORT_Free (p7ecx);
    return rv;
}


/*
 * Encode a PKCS7 object, in one shot.  All necessary components
 * of the object must already be specified.  Either the data has
 * already been included (via SetContent), or the data is detached,
 * or there is no data at all (certs-only).
 *
 * "cinfo" specifies the object to be encoded.
 *
 * "outputfn" is where the encoded bytes will be passed.
 *
 * "outputarg" is an opaque argument to the above callback.
 *
 * "bulkkey" specifies the bulk encryption key to use.   This argument
 * can be NULL if no encryption is being done, or if the bulk key should
 * be generated internally (usually the case for EnvelopedData but never
 * for EncryptedData, which *must* provide a bulk encryption key).
 *
 * "pwfn" is a callback for getting the password which protects the
 * private key of the signer.  This argument can be NULL if it is known
 * that no signing is going to be done.
 *
 * "pwfnarg" is an opaque argument to the above callback.
 */
SECStatus
SEC_PKCS7Encode (SEC_PKCS7ContentInfo *cinfo,
		 SEC_PKCS7EncoderOutputCallback outputfn,
		 void *outputarg,
		 PK11SymKey *bulkkey,
		 SECKEYGetPasswordKey pwfn,
		 void *pwfnarg)
{
    SECStatus rv;

    rv = SEC_PKCS7PrepareForEncode (cinfo, bulkkey, pwfn, pwfnarg);
    if (rv == SECSuccess) {
	struct sec_pkcs7_encoder_output outputcx;

	outputcx.outputfn = outputfn;
	outputcx.outputarg = outputarg;

	rv = SEC_ASN1Encode (cinfo, sec_PKCS7ContentInfoTemplate,
			     sec_pkcs7_encoder_out, &outputcx);
    }

    return rv;
}


/*
 * Encode a PKCS7 object, in one shot.  All necessary components
 * of the object must already be specified.  Either the data has
 * already been included (via SetContent), or the data is detached,
 * or there is no data at all (certs-only).  The output, rather than
 * being passed to an output function as is done above, is all put
 * into a SECItem.
 *
 * "pool" specifies a pool from which to allocate the result.
 * It can be NULL, in which case memory is allocated generically.
 *
 * "dest" specifies a SECItem in which to put the result data.
 * It can be NULL, in which case the entire item is allocated, too.
 *
 * "cinfo" specifies the object to be encoded.
 *
 * "bulkkey" specifies the bulk encryption key to use.   This argument
 * can be NULL if no encryption is being done, or if the bulk key should
 * be generated internally (usually the case for EnvelopedData but never
 * for EncryptedData, which *must* provide a bulk encryption key).
 *
 * "pwfn" is a callback for getting the password which protects the
 * private key of the signer.  This argument can be NULL if it is known
 * that no signing is going to be done.
 *
 * "pwfnarg" is an opaque argument to the above callback.
 */
SECItem *
SEC_PKCS7EncodeItem (PRArenaPool *pool,
		     SECItem *dest,
		     SEC_PKCS7ContentInfo *cinfo,
		     PK11SymKey *bulkkey,
		     SECKEYGetPasswordKey pwfn,
		     void *pwfnarg)
{
    SECStatus rv;

    rv = SEC_PKCS7PrepareForEncode (cinfo, bulkkey, pwfn, pwfnarg);
    if (rv != SECSuccess)
	return NULL;

    return SEC_ASN1EncodeItem (pool, dest, cinfo, sec_PKCS7ContentInfoTemplate);
}

