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