/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * 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 the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * CMS recipientInfo methods.
 *
 * $Id: cmsrecinfo.c,v 1.14.18.2 2006/05/15 17:45:43 wtchang%redhat.com Exp $
 */

#include "cmslocal.h"

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

PRBool
nss_cmsrecipientinfo_usessubjectkeyid(NSSCMSRecipientInfo *ri)
{
    if (ri->recipientInfoType == NSSCMSRecipientInfoID_KeyTrans) {
	NSSCMSRecipientIdentifier *rid;
	rid = &ri->ri.keyTransRecipientInfo.recipientIdentifier;
	if (rid->identifierType == NSSCMSRecipientID_SubjectKeyID) {
	    return PR_TRUE;
	}
    }
    return PR_FALSE;
}

/*
 * NOTE: fakeContent marks CMSMessage structure which is only used as a carrier
 * of pwfn_arg and arena pools. In an ideal world, NSSCMSMessage would not have
 * been exported, and we would have added an ordinary enum to handle this 
 * check. Unfortunatly wo don't have that luxury so we are overloading the
 * contentTypeTag field. NO code should every try to interpret this content tag
 * as a real OID tag, or use any fields other than pwfn_arg or poolp of this 
 * CMSMessage for that matter */
static const SECOidData fakeContent;
NSSCMSRecipientInfo *
nss_cmsrecipientinfo_create(NSSCMSMessage *cmsg, NSSCMSRecipientIDSelector type,
                            CERTCertificate *cert, SECKEYPublicKey *pubKey, 
                            SECItem *subjKeyID, void* pwfn_arg, SECItem* DERinput)
{
    NSSCMSRecipientInfo *ri;
    void *mark;
    SECOidTag certalgtag;
    SECStatus rv = SECSuccess;
    NSSCMSRecipientEncryptedKey *rek;
    NSSCMSOriginatorIdentifierOrKey *oiok;
    unsigned long version;
    SECItem *dummy;
    PLArenaPool *poolp;
    CERTSubjectPublicKeyInfo *spki, *freeSpki = NULL;
    NSSCMSRecipientIdentifier *rid;
    extern const SEC_ASN1Template NSSCMSRecipientInfoTemplate[];

    if (!cmsg) {
	/* a CMSMessage wasn't supplied, create a fake one to hold the pwfunc
	 * and a private arena pool */
	cmsg = NSS_CMSMessage_Create(NULL);
        cmsg->pwfn_arg = pwfn_arg;
	/* mark it as a special cms message */
	cmsg->contentInfo.contentTypeTag = &fakeContent;
    }

    poolp = cmsg->poolp;

    mark = PORT_ArenaMark(poolp);

    ri = (NSSCMSRecipientInfo *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSRecipientInfo));
    if (ri == NULL)
	goto loser;

    ri->cmsg = cmsg;

    if (DERinput) {
        /* decode everything from DER */
        SECItem newinput;
        SECStatus rv = SECITEM_CopyItem(poolp, &newinput, DERinput);
        if (SECSuccess != rv)
            goto loser;
        rv = SEC_QuickDERDecodeItem(poolp, ri, NSSCMSRecipientInfoTemplate, &newinput);
        if (SECSuccess != rv)
            goto loser;
    }

    switch (type) {
        case NSSCMSRecipientID_IssuerSN:
        {
            ri->cert = CERT_DupCertificate(cert);
            if (NULL == ri->cert)
                goto loser;
            spki = &(cert->subjectPublicKeyInfo);
            break;
        }
        
        case NSSCMSRecipientID_SubjectKeyID:
        {
            PORT_Assert(pubKey);
            spki = freeSpki = SECKEY_CreateSubjectPublicKeyInfo(pubKey);
            break;
        }

	case NSSCMSRecipientID_BrandNew:
	    goto done;
	    break;

        default:
            /* unkown type */
            goto loser;
            break;
    }

    certalgtag = SECOID_GetAlgorithmTag(&(spki->algorithm));

    rid = &ri->ri.keyTransRecipientInfo.recipientIdentifier;
    switch (certalgtag) {
    case SEC_OID_PKCS1_RSA_ENCRYPTION:
	ri->recipientInfoType = NSSCMSRecipientInfoID_KeyTrans;
	rid->identifierType = type;
	if (type == NSSCMSRecipientID_IssuerSN) {
	    rid->id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert);
	    if (rid->id.issuerAndSN == NULL) {
	      break;
	    }
	} else if (type == NSSCMSRecipientID_SubjectKeyID){
	    NSSCMSKeyTransRecipientInfoEx *riExtra;

	    rid->id.subjectKeyID = PORT_ArenaNew(poolp, SECItem);
	    if (rid->id.subjectKeyID == NULL) {
		rv = SECFailure;
		PORT_SetError(SEC_ERROR_NO_MEMORY);
		break;
	    } 
	    SECITEM_CopyItem(poolp, rid->id.subjectKeyID, subjKeyID);
	    if (rid->id.subjectKeyID->data == NULL) {
		rv = SECFailure;
		PORT_SetError(SEC_ERROR_NO_MEMORY);
		break;
	    }
	    riExtra = &ri->ri.keyTransRecipientInfoEx;
	    riExtra->version = 0;
	    riExtra->pubKey = SECKEY_CopyPublicKey(pubKey);
	    if (riExtra->pubKey == NULL) {
		rv = SECFailure;
		PORT_SetError(SEC_ERROR_NO_MEMORY);
		break;
	    }
	} else {
	    PORT_SetError(SEC_ERROR_INVALID_ARGS);
	    rv = SECFailure;
	}
	break;
    case SEC_OID_MISSI_KEA_DSS_OLD:
    case SEC_OID_MISSI_KEA_DSS:
    case SEC_OID_MISSI_KEA:
	PORT_Assert(type == NSSCMSRecipientID_IssuerSN);
	if (type != NSSCMSRecipientID_IssuerSN) {
	    rv = SECFailure;
	    break;
	}
	/* backward compatibility - this is not really a keytrans operation */
	ri->recipientInfoType = NSSCMSRecipientInfoID_KeyTrans;
	/* hardcoded issuerSN choice for now */
	ri->ri.keyTransRecipientInfo.recipientIdentifier.identifierType = NSSCMSRecipientID_IssuerSN;
	ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert);
	if (ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN == NULL) {
	    rv = SECFailure;
	    break;
	}
	break;
    case SEC_OID_X942_DIFFIE_HELMAN_KEY: /* dh-public-number */
	PORT_Assert(type == NSSCMSRecipientID_IssuerSN);
	if (type != NSSCMSRecipientID_IssuerSN) {
	    rv = SECFailure;
	    break;
	}
	/* a key agreement op */
	ri->recipientInfoType = NSSCMSRecipientInfoID_KeyAgree;

	if (ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN == NULL) {
	    rv = SECFailure;
	    break;
	}
	/* we do not support the case where multiple recipients 
	 * share the same KeyAgreeRecipientInfo and have multiple RecipientEncryptedKeys
	 * in this case, we would need to walk all the recipientInfos, take the
	 * ones that do KeyAgreement algorithms and join them, algorithm by algorithm
	 * Then, we'd generate ONE ukm and OriginatorIdentifierOrKey */

	/* only epheremal-static Diffie-Hellman is supported for now
	 * this is the only form of key agreement that provides potential anonymity
	 * of the sender, plus we do not have to include certs in the message */

	/* force single recipientEncryptedKey for now */
	if ((rek = NSS_CMSRecipientEncryptedKey_Create(poolp)) == NULL) {
	    rv = SECFailure;
	    break;
	}

	/* hardcoded IssuerSN choice for now */
	rek->recipientIdentifier.identifierType = NSSCMSKeyAgreeRecipientID_IssuerSN;
	if ((rek->recipientIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert)) == NULL) {
	    rv = SECFailure;
	    break;
	}

	oiok = &(ri->ri.keyAgreeRecipientInfo.originatorIdentifierOrKey);

	/* see RFC2630 12.3.1.1 */
	oiok->identifierType = NSSCMSOriginatorIDOrKey_OriginatorPublicKey;

	rv = NSS_CMSArray_Add(poolp, (void ***)&ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys,
				    (void *)rek);

	break;
    default:
	/* other algorithms not supported yet */
	/* NOTE that we do not support any KEK algorithm */
	PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
	rv = SECFailure;
	break;
    }

    if (rv == SECFailure)
	goto loser;

    /* set version */
    switch (ri->recipientInfoType) {
    case NSSCMSRecipientInfoID_KeyTrans:
	if (ri->ri.keyTransRecipientInfo.recipientIdentifier.identifierType == NSSCMSRecipientID_IssuerSN)
	    version = NSS_CMS_KEYTRANS_RECIPIENT_INFO_VERSION_ISSUERSN;
	else
	    version = NSS_CMS_KEYTRANS_RECIPIENT_INFO_VERSION_SUBJKEY;
	dummy = SEC_ASN1EncodeInteger(poolp, &(ri->ri.keyTransRecipientInfo.version), version);
	if (dummy == NULL)
	    goto loser;
	break;
    case NSSCMSRecipientInfoID_KeyAgree:
	dummy = SEC_ASN1EncodeInteger(poolp, &(ri->ri.keyAgreeRecipientInfo.version),
						NSS_CMS_KEYAGREE_RECIPIENT_INFO_VERSION);
	if (dummy == NULL)
	    goto loser;
	break;
    case NSSCMSRecipientInfoID_KEK:
	/* NOTE: this cannot happen as long as we do not support any KEK algorithm */
	dummy = SEC_ASN1EncodeInteger(poolp, &(ri->ri.kekRecipientInfo.version),
						NSS_CMS_KEK_RECIPIENT_INFO_VERSION);
	if (dummy == NULL)
	    goto loser;
	break;
    
    }

done:
    PORT_ArenaUnmark (poolp, mark);
    if (freeSpki)
      SECKEY_DestroySubjectPublicKeyInfo(freeSpki);
    return ri;

loser:
    if (ri && ri->cert) {
        CERT_DestroyCertificate(ri->cert);
    }
    if (freeSpki) {
      SECKEY_DestroySubjectPublicKeyInfo(freeSpki);
    }
    PORT_ArenaRelease (poolp, mark);
    if (cmsg->contentInfo.contentTypeTag == &fakeContent) {
	NSS_CMSMessage_Destroy(cmsg);
    }
    return NULL;
}

/*
 * NSS_CMSRecipientInfo_Create - create a recipientinfo
 *
 * we currently do not create KeyAgreement recipientinfos with multiple 
 * recipientEncryptedKeys the certificate is supposed to have been 
 * verified by the caller
 */
NSSCMSRecipientInfo *
NSS_CMSRecipientInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert)
{
    return nss_cmsrecipientinfo_create(cmsg, NSSCMSRecipientID_IssuerSN, cert, 
                                       NULL, NULL, NULL, NULL);
}

NSSCMSRecipientInfo *
NSS_CMSRecipientInfo_CreateNew(void* pwfn_arg)
{
    return nss_cmsrecipientinfo_create(NULL, NSSCMSRecipientID_BrandNew, NULL, 
                                       NULL, NULL, pwfn_arg, NULL);
}

NSSCMSRecipientInfo *
NSS_CMSRecipientInfo_CreateFromDER(SECItem* input, void* pwfn_arg)
{
    return nss_cmsrecipientinfo_create(NULL, NSSCMSRecipientID_BrandNew, NULL, 
                                       NULL, NULL, pwfn_arg, input);
}


NSSCMSRecipientInfo *
NSS_CMSRecipientInfo_CreateWithSubjKeyID(NSSCMSMessage   *cmsg, 
                                     SECItem         *subjKeyID,
                                     SECKEYPublicKey *pubKey)
{
    return nss_cmsrecipientinfo_create(cmsg, NSSCMSRecipientID_SubjectKeyID, 
                                       NULL, pubKey, subjKeyID, NULL, NULL);
}

NSSCMSRecipientInfo *
NSS_CMSRecipientInfo_CreateWithSubjKeyIDFromCert(NSSCMSMessage *cmsg,
                                             CERTCertificate *cert)
{
    SECKEYPublicKey *pubKey = NULL;
    SECItem subjKeyID = {siBuffer, NULL, 0};
    NSSCMSRecipientInfo *retVal = NULL;

    if (!cmsg || !cert) {
	return NULL;
    }
    pubKey = CERT_ExtractPublicKey(cert);
    if (!pubKey) {
	goto done;
    }
    if (CERT_FindSubjectKeyIDExtension(cert, &subjKeyID) != SECSuccess ||
        subjKeyID.data == NULL) {
	goto done;
    }
    retVal = NSS_CMSRecipientInfo_CreateWithSubjKeyID(cmsg, &subjKeyID, pubKey);
done:
    if (pubKey)
	SECKEY_DestroyPublicKey(pubKey);

    if (subjKeyID.data)
	SECITEM_FreeItem(&subjKeyID, PR_FALSE);

    return retVal;
}

void
NSS_CMSRecipientInfo_Destroy(NSSCMSRecipientInfo *ri)
{
    if (!ri) {
        return;
    }
    /* version was allocated on the pool, so no need to destroy it */
    /* issuerAndSN was allocated on the pool, so no need to destroy it */
    if (ri->cert != NULL)
	CERT_DestroyCertificate(ri->cert);

    if (nss_cmsrecipientinfo_usessubjectkeyid(ri)) {
	NSSCMSKeyTransRecipientInfoEx *extra;
	extra = &ri->ri.keyTransRecipientInfoEx;
	if (extra->pubKey)
	    SECKEY_DestroyPublicKey(extra->pubKey);
    }
    if (ri->cmsg && ri->cmsg->contentInfo.contentTypeTag == &fakeContent) {
	NSS_CMSMessage_Destroy(ri->cmsg);
    }

    /* we're done. */
}

int
NSS_CMSRecipientInfo_GetVersion(NSSCMSRecipientInfo *ri)
{
    unsigned long version;
    SECItem *versionitem = NULL;

    switch (ri->recipientInfoType) {
    case NSSCMSRecipientInfoID_KeyTrans:
	/* ignore subIndex */
	versionitem = &(ri->ri.keyTransRecipientInfo.version);
	break;
    case NSSCMSRecipientInfoID_KEK:
	/* ignore subIndex */
	versionitem = &(ri->ri.kekRecipientInfo.version);
	break;
    case NSSCMSRecipientInfoID_KeyAgree:
	versionitem = &(ri->ri.keyAgreeRecipientInfo.version);
	break;
    }

    PORT_Assert(versionitem);
    if (versionitem == NULL) 
	return 0;

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

SECItem *
NSS_CMSRecipientInfo_GetEncryptedKey(NSSCMSRecipientInfo *ri, int subIndex)
{
    SECItem *enckey = NULL;

    switch (ri->recipientInfoType) {
    case NSSCMSRecipientInfoID_KeyTrans:
	/* ignore subIndex */
	enckey = &(ri->ri.keyTransRecipientInfo.encKey);
	break;
    case NSSCMSRecipientInfoID_KEK:
	/* ignore subIndex */
	enckey = &(ri->ri.kekRecipientInfo.encKey);
	break;
    case NSSCMSRecipientInfoID_KeyAgree:
	enckey = &(ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[subIndex]->encKey);
	break;
    }
    return enckey;
}


SECOidTag
NSS_CMSRecipientInfo_GetKeyEncryptionAlgorithmTag(NSSCMSRecipientInfo *ri)
{
    SECOidTag encalgtag = SEC_OID_UNKNOWN; /* an invalid encryption alg */

    switch (ri->recipientInfoType) {
    case NSSCMSRecipientInfoID_KeyTrans:
	encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyTransRecipientInfo.keyEncAlg));
	break;
    case NSSCMSRecipientInfoID_KeyAgree:
	encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyAgreeRecipientInfo.keyEncAlg));
	break;
    case NSSCMSRecipientInfoID_KEK:
	encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.kekRecipientInfo.keyEncAlg));
	break;
    }
    return encalgtag;
}

SECStatus
NSS_CMSRecipientInfo_WrapBulkKey(NSSCMSRecipientInfo *ri, PK11SymKey *bulkkey, 
                                 SECOidTag bulkalgtag)
{
    CERTCertificate *cert;
    SECOidTag certalgtag;
    SECStatus rv = SECSuccess;
    SECItem *params = NULL;
    NSSCMSRecipientEncryptedKey *rek;
    NSSCMSOriginatorIdentifierOrKey *oiok;
    CERTSubjectPublicKeyInfo *spki, *freeSpki = NULL;
    PLArenaPool *poolp;
    NSSCMSKeyTransRecipientInfoEx *extra = NULL;
    PRBool usesSubjKeyID;

    poolp = ri->cmsg->poolp;
    cert = ri->cert;
    usesSubjKeyID = nss_cmsrecipientinfo_usessubjectkeyid(ri);
    if (cert) {
	spki = &cert->subjectPublicKeyInfo;
	certalgtag = SECOID_GetAlgorithmTag(&(spki->algorithm));
    } else if (usesSubjKeyID) {
	extra = &ri->ri.keyTransRecipientInfoEx;
	/* sanity check */
	PORT_Assert(extra->pubKey);
	if (!extra->pubKey) {
	    PORT_SetError(SEC_ERROR_INVALID_ARGS);
	    return SECFailure;
	}
	spki = freeSpki = SECKEY_CreateSubjectPublicKeyInfo(extra->pubKey);
	certalgtag = SECOID_GetAlgorithmTag(&spki->algorithm);
    } else {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }

    /* XXX set ri->recipientInfoType to the proper value here */
    /* or should we look if it's been set already ? */

    certalgtag = SECOID_GetAlgorithmTag(&spki->algorithm);
    switch (certalgtag) {
    case SEC_OID_PKCS1_RSA_ENCRYPTION:
	/* wrap the symkey */
	if (cert) {
	    rv = NSS_CMSUtil_EncryptSymKey_RSA(poolp, cert, bulkkey, 
	                         &ri->ri.keyTransRecipientInfo.encKey);
 	    if (rv != SECSuccess)
		break;
	} else if (usesSubjKeyID) {
	    PORT_Assert(extra != NULL);
	    rv = NSS_CMSUtil_EncryptSymKey_RSAPubKey(poolp, extra->pubKey,
	                         bulkkey, &ri->ri.keyTransRecipientInfo.encKey);
 	    if (rv != SECSuccess)
		break;
	}

	rv = SECOID_SetAlgorithmID(poolp, &(ri->ri.keyTransRecipientInfo.keyEncAlg), certalgtag, NULL);
	break;
    case SEC_OID_MISSI_KEA_DSS_OLD:
    case SEC_OID_MISSI_KEA_DSS:
    case SEC_OID_MISSI_KEA:
	rv = NSS_CMSUtil_EncryptSymKey_MISSI(poolp, cert, bulkkey,
					bulkalgtag,
					&ri->ri.keyTransRecipientInfo.encKey,
					&params, ri->cmsg->pwfn_arg);
	if (rv != SECSuccess)
	    break;

	/* here, we DO need to pass the params to the wrap function because, with
	 * RSA, there is no funny stuff going on with generation of IV vectors or so */
	rv = SECOID_SetAlgorithmID(poolp, &(ri->ri.keyTransRecipientInfo.keyEncAlg), certalgtag, params);
	break;
    case SEC_OID_X942_DIFFIE_HELMAN_KEY: /* dh-public-number */
	rek = ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[0];
	if (rek == NULL) {
	    rv = SECFailure;
	    break;
	}

	oiok = &(ri->ri.keyAgreeRecipientInfo.originatorIdentifierOrKey);
	PORT_Assert(oiok->identifierType == NSSCMSOriginatorIDOrKey_OriginatorPublicKey);

	/* see RFC2630 12.3.1.1 */
	if (SECOID_SetAlgorithmID(poolp, &oiok->id.originatorPublicKey.algorithmIdentifier,
				    SEC_OID_X942_DIFFIE_HELMAN_KEY, NULL) != SECSuccess) {
	    rv = SECFailure;
	    break;
	}

	/* this will generate a key pair, compute the shared secret, */
	/* derive a key and ukm for the keyEncAlg out of it, encrypt the bulk key with */
	/* the keyEncAlg, set encKey, keyEncAlg, publicKey etc. */
	rv = NSS_CMSUtil_EncryptSymKey_ESDH(poolp, cert, bulkkey,
					&rek->encKey,
					&ri->ri.keyAgreeRecipientInfo.ukm,
					&ri->ri.keyAgreeRecipientInfo.keyEncAlg,
					&oiok->id.originatorPublicKey.publicKey);

	break;
    default:
	/* other algorithms not supported yet */
	/* NOTE that we do not support any KEK algorithm */
	PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
	rv = SECFailure;
	break;
    }
    if (freeSpki)
	SECKEY_DestroySubjectPublicKeyInfo(freeSpki);

    return rv;
}

PK11SymKey *
NSS_CMSRecipientInfo_UnwrapBulkKey(NSSCMSRecipientInfo *ri, int subIndex, 
	CERTCertificate *cert, SECKEYPrivateKey *privkey, SECOidTag bulkalgtag)
{
    PK11SymKey *bulkkey = NULL;
    SECAlgorithmID *encalg;
    SECOidTag encalgtag;
    SECItem *enckey;
    int error;

    ri->cert = CERT_DupCertificate(cert);
        	/* mark the recipientInfo so we can find it later */

    switch (ri->recipientInfoType) {
    case NSSCMSRecipientInfoID_KeyTrans:
	encalg = &(ri->ri.keyTransRecipientInfo.keyEncAlg);
	encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyTransRecipientInfo.keyEncAlg));
	enckey = &(ri->ri.keyTransRecipientInfo.encKey); /* ignore subIndex */
	switch (encalgtag) {
	case SEC_OID_PKCS1_RSA_ENCRYPTION:
	    /* RSA encryption algorithm: */
	    /* get the symmetric (bulk) key by unwrapping it using our private key */
	    bulkkey = NSS_CMSUtil_DecryptSymKey_RSA(privkey, enckey, bulkalgtag);
	    break;
	case SEC_OID_NETSCAPE_SMIME_KEA:
	    /* FORTEZZA key exchange algorithm */
	    /* the supplemental data is in the parameters of encalg */
	    bulkkey = NSS_CMSUtil_DecryptSymKey_MISSI(privkey, enckey, encalg, bulkalgtag, ri->cmsg->pwfn_arg);
	    break;
	default:
	    error = SEC_ERROR_UNSUPPORTED_KEYALG;
	    goto loser;
	}
	break;
    case NSSCMSRecipientInfoID_KeyAgree:
	encalg = &(ri->ri.keyAgreeRecipientInfo.keyEncAlg);
	encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyAgreeRecipientInfo.keyEncAlg));
	enckey = &(ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[subIndex]->encKey);
	switch (encalgtag) {
	case SEC_OID_X942_DIFFIE_HELMAN_KEY:
	    /* Diffie-Helman key exchange */
	    /* XXX not yet implemented */
	    /* XXX problem: SEC_OID_X942_DIFFIE_HELMAN_KEY points to a PKCS3 mechanism! */
	    /* we support ephemeral-static DH only, so if the recipientinfo */
	    /* has originator stuff in it, we punt (or do we? shouldn't be that hard...) */
	    /* first, we derive the KEK (a symkey!) using a Derive operation, then we get the */
	    /* content encryption key using a Unwrap op */
	    /* the derive operation has to generate the key using the algorithm in RFC2631 */
	    error = SEC_ERROR_UNSUPPORTED_KEYALG;
	    break;
	default:
	    error = SEC_ERROR_UNSUPPORTED_KEYALG;
	    goto loser;
	}
	break;
    case NSSCMSRecipientInfoID_KEK:
	encalg = &(ri->ri.kekRecipientInfo.keyEncAlg);
	encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.kekRecipientInfo.keyEncAlg));
	enckey = &(ri->ri.kekRecipientInfo.encKey);
	/* not supported yet */
	error = SEC_ERROR_UNSUPPORTED_KEYALG;
	goto loser;
	break;
    }
    /* XXXX continue here */
    return bulkkey;

loser:
    return NULL;
}

SECStatus NSS_CMSRecipientInfo_GetCertAndKey(NSSCMSRecipientInfo *ri,
                                             CERTCertificate** retcert,
                                             SECKEYPrivateKey** retkey)
{
    CERTCertificate* cert = NULL;
    NSSCMSRecipient** recipients = NULL;
    NSSCMSRecipientInfo* recipientInfos[2];
    SECStatus rv = SECSuccess;
    SECKEYPrivateKey* key = NULL;

    if (!ri)
        return SECFailure;
    
    if (!retcert && !retkey) {
        /* nothing requested, nothing found, success */
        return SECSuccess;
    }

    if (retcert) {
        *retcert = NULL;
    }
    if (retkey) {
        *retkey = NULL;
    }

    if (ri->cert) {
        cert = CERT_DupCertificate(ri->cert);
        if (!cert) {
            rv = SECFailure;
        }
    }
    if (SECSuccess == rv && !cert) {
        /* we don't have the cert, we have to look for it */
        /* first build an NSS_CMSRecipient */
        recipientInfos[0] = ri;
        recipientInfos[1] = NULL;

        recipients = nss_cms_recipient_list_create(recipientInfos);
        if (recipients) {
            /* now look for the cert and key */
            if (0 == PK11_FindCertAndKeyByRecipientListNew(recipients,
                ri->cmsg->pwfn_arg)) {
                cert = CERT_DupCertificate(recipients[0]->cert);
                key = SECKEY_CopyPrivateKey(recipients[0]->privkey);
            } else {
                rv = SECFailure;
            }

            nss_cms_recipient_list_destroy(recipients);
        }
        else {
            rv = SECFailure;
        }            
    } else if (SECSuccess == rv && cert && retkey) {
        /* we have the cert, we just need the key now */
        key = PK11_FindPrivateKeyFromCert(cert->slot, cert, ri->cmsg->pwfn_arg);
    }
    if (retcert) {
        *retcert = cert;
    } else {
        if (cert) {
            CERT_DestroyCertificate(cert);
        }
    }
    if (retkey) {
        *retkey = key;
    } else {
        if (key) {
            SECKEY_DestroyPrivateKey(key);
        }
    }

    return rv;
}

SECStatus NSS_CMSRecipientInfo_Encode(PRArenaPool* poolp,
                                      const NSSCMSRecipientInfo *src,
                                      SECItem* returned)
{
    extern const SEC_ASN1Template NSSCMSRecipientInfoTemplate[];
    SECStatus rv = SECFailure;
    if (!src || !returned) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
    } else if (SEC_ASN1EncodeItem(poolp, returned, src,
        NSSCMSRecipientInfoTemplate))   {
        rv = SECSuccess;
    }
    return rv;
}
