michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * CMS envelopedData methods. michael@0: */ michael@0: michael@0: #include "cmslocal.h" michael@0: michael@0: #include "cert.h" michael@0: #include "key.h" michael@0: #include "secasn1.h" michael@0: #include "secitem.h" michael@0: #include "secoid.h" michael@0: #include "pk11func.h" michael@0: #include "secerr.h" michael@0: #include "secpkcs5.h" michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Create - create an enveloped data message michael@0: */ michael@0: NSSCMSEnvelopedData * michael@0: NSS_CMSEnvelopedData_Create(NSSCMSMessage *cmsg, SECOidTag algorithm, int keysize) michael@0: { michael@0: void *mark; michael@0: NSSCMSEnvelopedData *envd; michael@0: PLArenaPool *poolp; michael@0: SECStatus rv; michael@0: michael@0: poolp = cmsg->poolp; michael@0: michael@0: mark = PORT_ArenaMark(poolp); michael@0: michael@0: envd = (NSSCMSEnvelopedData *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSEnvelopedData)); michael@0: if (envd == NULL) michael@0: goto loser; michael@0: michael@0: envd->cmsg = cmsg; michael@0: michael@0: /* version is set in NSS_CMSEnvelopedData_Encode_BeforeStart() */ michael@0: michael@0: rv = NSS_CMSContentInfo_SetContentEncAlg(poolp, &(envd->contentInfo), algorithm, NULL, keysize); michael@0: if (rv != SECSuccess) michael@0: goto loser; michael@0: michael@0: PORT_ArenaUnmark(poolp, mark); michael@0: return envd; michael@0: michael@0: loser: michael@0: PORT_ArenaRelease(poolp, mark); michael@0: return NULL; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Destroy - destroy an enveloped data message michael@0: */ michael@0: void michael@0: NSS_CMSEnvelopedData_Destroy(NSSCMSEnvelopedData *edp) michael@0: { michael@0: NSSCMSRecipientInfo **recipientinfos; michael@0: NSSCMSRecipientInfo *ri; michael@0: michael@0: if (edp == NULL) michael@0: return; michael@0: michael@0: recipientinfos = edp->recipientInfos; michael@0: if (recipientinfos == NULL) michael@0: return; michael@0: michael@0: while ((ri = *recipientinfos++) != NULL) michael@0: NSS_CMSRecipientInfo_Destroy(ri); michael@0: michael@0: NSS_CMSContentInfo_Destroy(&(edp->contentInfo)); michael@0: michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_GetContentInfo - return pointer to this envelopedData's contentinfo michael@0: */ michael@0: NSSCMSContentInfo * michael@0: NSS_CMSEnvelopedData_GetContentInfo(NSSCMSEnvelopedData *envd) michael@0: { michael@0: return &(envd->contentInfo); michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_AddRecipient - add a recipientinfo to the enveloped data msg michael@0: * michael@0: * rip must be created on the same pool as edp - this is not enforced, though. michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_AddRecipient(NSSCMSEnvelopedData *edp, NSSCMSRecipientInfo *rip) michael@0: { michael@0: void *mark; michael@0: SECStatus rv; michael@0: michael@0: /* XXX compare pools, if not same, copy rip into edp's pool */ michael@0: michael@0: PR_ASSERT(edp != NULL); michael@0: PR_ASSERT(rip != NULL); michael@0: michael@0: mark = PORT_ArenaMark(edp->cmsg->poolp); michael@0: michael@0: rv = NSS_CMSArray_Add(edp->cmsg->poolp, (void ***)&(edp->recipientInfos), (void *)rip); michael@0: if (rv != SECSuccess) { michael@0: PORT_ArenaRelease(edp->cmsg->poolp, mark); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PORT_ArenaUnmark (edp->cmsg->poolp, mark); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Encode_BeforeStart - prepare this envelopedData for encoding michael@0: * michael@0: * at this point, we need michael@0: * - recipientinfos set up with recipient's certificates michael@0: * - a content encryption algorithm (if none, 3DES will be used) michael@0: * michael@0: * this function will generate a random content encryption key (aka bulk key), michael@0: * initialize the recipientinfos with certificate identification and wrap the bulk key michael@0: * using the proper algorithm for every certificiate. michael@0: * it will finally set the bulk algorithm and key so that the encode step can find it. michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_Encode_BeforeStart(NSSCMSEnvelopedData *envd) michael@0: { michael@0: int version; michael@0: NSSCMSRecipientInfo **recipientinfos; michael@0: NSSCMSContentInfo *cinfo; michael@0: PK11SymKey *bulkkey = NULL; michael@0: SECOidTag bulkalgtag; michael@0: CK_MECHANISM_TYPE type; michael@0: PK11SlotInfo *slot; michael@0: SECStatus rv; michael@0: SECItem *dummy; michael@0: PLArenaPool *poolp; michael@0: extern const SEC_ASN1Template NSSCMSRecipientInfoTemplate[]; michael@0: void *mark = NULL; michael@0: int i; michael@0: michael@0: poolp = envd->cmsg->poolp; michael@0: cinfo = &(envd->contentInfo); michael@0: michael@0: recipientinfos = envd->recipientInfos; michael@0: if (recipientinfos == NULL) { michael@0: PORT_SetError(SEC_ERROR_BAD_DATA); michael@0: #if 0 michael@0: PORT_SetErrorString("Cannot find recipientinfos to encode."); michael@0: #endif michael@0: goto loser; michael@0: } michael@0: michael@0: version = NSS_CMS_ENVELOPED_DATA_VERSION_REG; michael@0: if (envd->originatorInfo != NULL || envd->unprotectedAttr != NULL) { michael@0: version = NSS_CMS_ENVELOPED_DATA_VERSION_ADV; michael@0: } else { michael@0: for (i = 0; recipientinfos[i] != NULL; i++) { michael@0: if (NSS_CMSRecipientInfo_GetVersion(recipientinfos[i]) != 0) { michael@0: version = NSS_CMS_ENVELOPED_DATA_VERSION_ADV; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: dummy = SEC_ASN1EncodeInteger(poolp, &(envd->version), version); michael@0: if (dummy == NULL) michael@0: goto loser; michael@0: michael@0: /* now we need to have a proper content encryption algorithm michael@0: * on the SMIME level, we would figure one out by looking at SMIME capabilities michael@0: * we cannot do that on our level, so if none is set already, we'll just go michael@0: * with one of the mandatory algorithms (3DES) */ michael@0: if ((bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo)) == SEC_OID_UNKNOWN) { michael@0: rv = NSS_CMSContentInfo_SetContentEncAlg(poolp, cinfo, SEC_OID_DES_EDE3_CBC, NULL, 168); michael@0: if (rv != SECSuccess) michael@0: goto loser; michael@0: bulkalgtag = SEC_OID_DES_EDE3_CBC; michael@0: } michael@0: michael@0: /* generate a random bulk key suitable for content encryption alg */ michael@0: type = PK11_AlgtagToMechanism(bulkalgtag); michael@0: slot = PK11_GetBestSlot(type, envd->cmsg->pwfn_arg); michael@0: if (slot == NULL) michael@0: goto loser; /* error has been set by PK11_GetBestSlot */ michael@0: michael@0: /* this is expensive... */ michael@0: bulkkey = PK11_KeyGen(slot, type, NULL, NSS_CMSContentInfo_GetBulkKeySize(cinfo) / 8, envd->cmsg->pwfn_arg); michael@0: PK11_FreeSlot(slot); michael@0: if (bulkkey == NULL) michael@0: goto loser; /* error has been set by PK11_KeyGen */ michael@0: michael@0: mark = PORT_ArenaMark(poolp); michael@0: michael@0: /* Encrypt the bulk key with the public key of each recipient. */ michael@0: for (i = 0; recipientinfos[i] != NULL; i++) { michael@0: rv = NSS_CMSRecipientInfo_WrapBulkKey(recipientinfos[i], bulkkey, bulkalgtag); michael@0: if (rv != SECSuccess) michael@0: goto loser; /* error has been set by NSS_CMSRecipientInfo_EncryptBulkKey */ michael@0: /* could be: alg not supported etc. */ michael@0: } michael@0: michael@0: /* the recipientinfos are all finished. now sort them by DER for SET OF encoding */ michael@0: rv = NSS_CMSArray_SortByDER((void **)envd->recipientInfos, NSSCMSRecipientInfoTemplate, NULL); michael@0: if (rv != SECSuccess) michael@0: goto loser; /* error has been set by NSS_CMSArray_SortByDER */ michael@0: michael@0: /* store the bulk key in the contentInfo so that the encoder can find it */ michael@0: NSS_CMSContentInfo_SetBulkKey(cinfo, bulkkey); michael@0: michael@0: PORT_ArenaUnmark(poolp, mark); michael@0: michael@0: PK11_FreeSymKey(bulkkey); michael@0: michael@0: return SECSuccess; michael@0: michael@0: loser: michael@0: if (mark != NULL) michael@0: PORT_ArenaRelease (poolp, mark); michael@0: if (bulkkey) michael@0: PK11_FreeSymKey(bulkkey); michael@0: michael@0: return SECFailure; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Encode_BeforeData - set up encryption michael@0: * michael@0: * it is essential that this is called before the contentEncAlg is encoded, because michael@0: * setting up the encryption may generate IVs and thus change it! michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_Encode_BeforeData(NSSCMSEnvelopedData *envd) michael@0: { michael@0: NSSCMSContentInfo *cinfo; michael@0: PK11SymKey *bulkkey; michael@0: SECAlgorithmID *algid; michael@0: SECStatus rv; michael@0: michael@0: cinfo = &(envd->contentInfo); michael@0: michael@0: /* find bulkkey and algorithm - must have been set by NSS_CMSEnvelopedData_Encode_BeforeStart */ michael@0: bulkkey = NSS_CMSContentInfo_GetBulkKey(cinfo); michael@0: if (bulkkey == NULL) michael@0: return SECFailure; michael@0: algid = NSS_CMSContentInfo_GetContentEncAlg(cinfo); michael@0: if (algid == NULL) michael@0: return SECFailure; michael@0: michael@0: rv = NSS_CMSContentInfo_Private_Init(cinfo); michael@0: if (rv != SECSuccess) { michael@0: return SECFailure; michael@0: } michael@0: /* this may modify algid (with IVs generated in a token). michael@0: * it is essential that algid is a pointer to the contentEncAlg data, not a michael@0: * pointer to a copy! */ michael@0: cinfo->privateInfo->ciphcx = NSS_CMSCipherContext_StartEncrypt(envd->cmsg->poolp, bulkkey, algid); michael@0: PK11_FreeSymKey(bulkkey); michael@0: if (cinfo->privateInfo->ciphcx == NULL) michael@0: return SECFailure; michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Encode_AfterData - finalize this envelopedData for encoding michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_Encode_AfterData(NSSCMSEnvelopedData *envd) michael@0: { michael@0: if (envd->contentInfo.privateInfo && envd->contentInfo.privateInfo->ciphcx) { michael@0: NSS_CMSCipherContext_Destroy(envd->contentInfo.privateInfo->ciphcx); michael@0: envd->contentInfo.privateInfo->ciphcx = NULL; michael@0: } michael@0: michael@0: /* nothing else to do after data */ michael@0: return SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Decode_BeforeData - find our recipientinfo, michael@0: * derive bulk key & set up our contentinfo michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_Decode_BeforeData(NSSCMSEnvelopedData *envd) michael@0: { michael@0: NSSCMSRecipientInfo *ri; michael@0: PK11SymKey *bulkkey = NULL; michael@0: SECOidTag bulkalgtag; michael@0: SECAlgorithmID *bulkalg; michael@0: SECStatus rv = SECFailure; michael@0: NSSCMSContentInfo *cinfo; michael@0: NSSCMSRecipient **recipient_list = NULL; michael@0: NSSCMSRecipient *recipient; michael@0: int rlIndex; michael@0: michael@0: if (NSS_CMSArray_Count((void **)envd->recipientInfos) == 0) { michael@0: PORT_SetError(SEC_ERROR_BAD_DATA); michael@0: #if 0 michael@0: PORT_SetErrorString("No recipient data in envelope."); michael@0: #endif michael@0: goto loser; michael@0: } michael@0: michael@0: /* look if one of OUR cert's issuerSN is on the list of recipients, and if so, */ michael@0: /* get the cert and private key for it right away */ michael@0: recipient_list = nss_cms_recipient_list_create(envd->recipientInfos); michael@0: if (recipient_list == NULL) michael@0: goto loser; michael@0: michael@0: /* what about multiple recipientInfos that match? michael@0: * especially if, for some reason, we could not produce a bulk key with the first match?! michael@0: * we could loop & feed partial recipient_list to PK11_FindCertAndKeyByRecipientList... michael@0: * maybe later... */ michael@0: rlIndex = PK11_FindCertAndKeyByRecipientListNew(recipient_list, envd->cmsg->pwfn_arg); michael@0: michael@0: /* if that fails, then we're not an intended recipient and cannot decrypt */ michael@0: if (rlIndex < 0) { michael@0: PORT_SetError(SEC_ERROR_NOT_A_RECIPIENT); michael@0: #if 0 michael@0: PORT_SetErrorString("Cannot decrypt data because proper key cannot be found."); michael@0: #endif michael@0: goto loser; michael@0: } michael@0: michael@0: recipient = recipient_list[rlIndex]; michael@0: if (!recipient->cert || !recipient->privkey) { michael@0: /* XXX should set an error code ?!? */ michael@0: goto loser; michael@0: } michael@0: /* get a pointer to "our" recipientinfo */ michael@0: ri = envd->recipientInfos[recipient->riIndex]; michael@0: michael@0: cinfo = &(envd->contentInfo); michael@0: bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo); michael@0: if (bulkalgtag == SEC_OID_UNKNOWN) { michael@0: PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); michael@0: } else michael@0: bulkkey = michael@0: NSS_CMSRecipientInfo_UnwrapBulkKey(ri,recipient->subIndex, michael@0: recipient->cert, michael@0: recipient->privkey, michael@0: bulkalgtag); michael@0: if (bulkkey == NULL) { michael@0: /* no success finding a bulk key */ michael@0: goto loser; michael@0: } michael@0: michael@0: NSS_CMSContentInfo_SetBulkKey(cinfo, bulkkey); michael@0: michael@0: bulkalg = NSS_CMSContentInfo_GetContentEncAlg(cinfo); michael@0: michael@0: rv = NSS_CMSContentInfo_Private_Init(cinfo); michael@0: if (rv != SECSuccess) { michael@0: goto loser; michael@0: } michael@0: rv = SECFailure; michael@0: cinfo->privateInfo->ciphcx = NSS_CMSCipherContext_StartDecrypt(bulkkey, bulkalg); michael@0: if (cinfo->privateInfo->ciphcx == NULL) michael@0: goto loser; /* error has been set by NSS_CMSCipherContext_StartDecrypt */ michael@0: michael@0: michael@0: rv = SECSuccess; michael@0: michael@0: loser: michael@0: if (bulkkey) michael@0: PK11_FreeSymKey(bulkkey); michael@0: if (recipient_list != NULL) michael@0: nss_cms_recipient_list_destroy(recipient_list); michael@0: return rv; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Decode_AfterData - finish decrypting this envelopedData's content michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_Decode_AfterData(NSSCMSEnvelopedData *envd) michael@0: { michael@0: if (envd && envd->contentInfo.privateInfo && envd->contentInfo.privateInfo->ciphcx) { michael@0: NSS_CMSCipherContext_Destroy(envd->contentInfo.privateInfo->ciphcx); michael@0: envd->contentInfo.privateInfo->ciphcx = NULL; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSEnvelopedData_Decode_AfterEnd - finish decoding this envelopedData michael@0: */ michael@0: SECStatus michael@0: NSS_CMSEnvelopedData_Decode_AfterEnd(NSSCMSEnvelopedData *envd) michael@0: { michael@0: /* apply final touches */ michael@0: return SECSuccess; michael@0: } michael@0: