1/*
2 * The contents of this file are subject to the Mozilla Public
3 * License Version 1.1 (the "License"); you may not use this file
4 * except in compliance with the License. You may obtain a copy of
5 * the License at http://www.mozilla.org/MPL/
6 *
7 * Software distributed under the License is distributed on an "AS
8 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9 * implied. See the License for the specific language governing
10 * rights and limitations under the License.
11 *
12 * The Original Code is the Netscape security libraries.
13 *
14 * The Initial Developer of the Original Code is Netscape
15 * Communications Corporation.  Portions created by Netscape are
16 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
17 * Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * Alternatively, the contents of this file may be used under the
22 * terms of the GNU General Public License Version 2 or later (the
23 * "GPL"), in which case the provisions of the GPL are applicable
24 * instead of those above.  If you wish to allow use of your
25 * version of this file only under the terms of the GPL and not to
26 * allow others to use your version of this file under the MPL,
27 * indicate your decision by deleting the provisions above and
28 * replace them with the notice and other provisions required by
29 * the GPL.  If you do not delete the provisions above, a recipient
30 * may use your version of this file under either the MPL or the
31 * GPL.
32 */
33
34/*
35 * CMS envelopedData methods.
36 */
37
38#include <Security/SecCmsEnvelopedData.h>
39
40#include <Security/SecCmsContentInfo.h>
41#include <Security/SecCmsRecipientInfo.h>
42
43#include "cmslocal.h"
44
45#include "secitem.h"
46#include "secoid.h"
47#include "cryptohi.h"
48
49#include <security_asn1/secasn1.h>
50#include <security_asn1/secerr.h>
51#include <Security/SecKeyPriv.h>
52
53/*
54 * SecCmsEnvelopedDataCreate - create an enveloped data message
55 */
56SecCmsEnvelopedDataRef
57SecCmsEnvelopedDataCreate(SecCmsMessageRef cmsg, SECOidTag algorithm, int keysize)
58{
59    void *mark;
60    SecCmsEnvelopedDataRef envd;
61    PLArenaPool *poolp;
62    OSStatus rv;
63
64    poolp = cmsg->poolp;
65
66    mark = PORT_ArenaMark(poolp);
67
68    envd = (SecCmsEnvelopedDataRef)PORT_ArenaZAlloc(poolp, sizeof(SecCmsEnvelopedData));
69    if (envd == NULL)
70	goto loser;
71
72    envd->cmsg = cmsg;
73
74    /* version is set in SecCmsEnvelopedDataEncodeBeforeStart() */
75
76    rv = SecCmsContentInfoSetContentEncAlg((SecArenaPoolRef)poolp, &(envd->contentInfo), algorithm, NULL, keysize);
77    if (rv != SECSuccess)
78	goto loser;
79
80    PORT_ArenaUnmark(poolp, mark);
81    return envd;
82
83loser:
84    PORT_ArenaRelease(poolp, mark);
85    return NULL;
86}
87
88/*
89 * SecCmsEnvelopedDataDestroy - destroy an enveloped data message
90 */
91void
92SecCmsEnvelopedDataDestroy(SecCmsEnvelopedDataRef edp)
93{
94    SecCmsRecipientInfoRef *recipientinfos;
95    SecCmsRecipientInfoRef ri;
96
97    if (edp == NULL)
98	return;
99
100    recipientinfos = edp->recipientInfos;
101    if (recipientinfos == NULL)
102	return;
103
104    while ((ri = *recipientinfos++) != NULL)
105	SecCmsRecipientInfoDestroy(ri);
106
107   SecCmsContentInfoDestroy(&(edp->contentInfo));
108
109}
110
111/*
112 * SecCmsEnvelopedDataGetContentInfo - return pointer to this envelopedData's contentinfo
113 */
114SecCmsContentInfoRef
115SecCmsEnvelopedDataGetContentInfo(SecCmsEnvelopedDataRef envd)
116{
117    return &(envd->contentInfo);
118}
119
120/*
121 * SecCmsEnvelopedDataAddRecipient - add a recipientinfo to the enveloped data msg
122 *
123 * rip must be created on the same pool as edp - this is not enforced, though.
124 */
125OSStatus
126SecCmsEnvelopedDataAddRecipient(SecCmsEnvelopedDataRef edp, SecCmsRecipientInfoRef rip)
127{
128    void *mark;
129    OSStatus rv;
130
131    /* XXX compare pools, if not same, copy rip into edp's pool */
132
133    PR_ASSERT(edp != NULL);
134    PR_ASSERT(rip != NULL);
135
136    mark = PORT_ArenaMark(edp->cmsg->poolp);
137
138    rv = SecCmsArrayAdd(edp->cmsg->poolp, (void ***)&(edp->recipientInfos), (void *)rip);
139    if (rv != SECSuccess) {
140	PORT_ArenaRelease(edp->cmsg->poolp, mark);
141	return SECFailure;
142    }
143
144    PORT_ArenaUnmark (edp->cmsg->poolp, mark);
145    return SECSuccess;
146}
147
148/*
149 * SecCmsEnvelopedDataEncodeBeforeStart - prepare this envelopedData for encoding
150 *
151 * at this point, we need
152 * - recipientinfos set up with recipient's certificates
153 * - a content encryption algorithm (if none, 3DES will be used)
154 *
155 * this function will generate a random content encryption key (aka bulk key),
156 * initialize the recipientinfos with certificate identification and wrap the bulk key
157 * using the proper algorithm for every certificiate.
158 * it will finally set the bulk algorithm and key so that the encode step can find it.
159 */
160OSStatus
161SecCmsEnvelopedDataEncodeBeforeStart(SecCmsEnvelopedDataRef envd)
162{
163    int version;
164    SecCmsRecipientInfoRef *recipientinfos;
165    SecCmsContentInfoRef cinfo;
166    SecSymmetricKeyRef bulkkey = NULL;
167    CSSM_ALGORITHMS algorithm;
168    SECOidTag bulkalgtag;
169    //CK_MECHANISM_TYPE type;
170    //PK11SlotInfo *slot;
171    OSStatus rv;
172    CSSM_DATA_PTR dummy;
173    PLArenaPool *poolp;
174    extern const SecAsn1Template SecCmsRecipientInfoTemplate[];
175    void *mark = NULL;
176    int i;
177
178    poolp = envd->cmsg->poolp;
179    cinfo = &(envd->contentInfo);
180
181    recipientinfos = envd->recipientInfos;
182    if (recipientinfos == NULL) {
183	PORT_SetError(SEC_ERROR_BAD_DATA);
184#if 0
185	PORT_SetErrorString("Cannot find recipientinfos to encode.");
186#endif
187	goto loser;
188    }
189
190    version = SEC_CMS_ENVELOPED_DATA_VERSION_REG;
191    if (envd->originatorInfo != NULL || envd->unprotectedAttr != NULL) {
192	version = SEC_CMS_ENVELOPED_DATA_VERSION_ADV;
193    } else {
194	for (i = 0; recipientinfos[i] != NULL; i++) {
195	    if (SecCmsRecipientInfoGetVersion(recipientinfos[i]) != 0) {
196		version = SEC_CMS_ENVELOPED_DATA_VERSION_ADV;
197		break;
198	    }
199	}
200    }
201    dummy = SEC_ASN1EncodeInteger(poolp, &(envd->version), version);
202    if (dummy == NULL)
203	goto loser;
204
205    /* now we need to have a proper content encryption algorithm
206     * on the SMIME level, we would figure one out by looking at SMIME capabilities
207     * we cannot do that on our level, so if none is set already, we'll just go
208     * with one of the mandatory algorithms (3DES) */
209    if ((bulkalgtag = SecCmsContentInfoGetContentEncAlgTag(cinfo)) == SEC_OID_UNKNOWN) {
210	rv = SecCmsContentInfoSetContentEncAlg((SecArenaPoolRef)poolp, cinfo, SEC_OID_DES_EDE3_CBC, NULL, 168);
211	if (rv != SECSuccess)
212	    goto loser;
213	bulkalgtag = SEC_OID_DES_EDE3_CBC;
214    }
215
216    algorithm = SECOID_FindyCssmAlgorithmByTag(bulkalgtag);
217    if (!algorithm)
218	goto loser;
219    rv = SecKeyGenerate(NULL,	/* keychainRef */
220		algorithm,
221		SecCmsContentInfoGetBulkKeySize(cinfo),
222		0,		/* contextHandle */
223		CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
224		CSSM_KEYATTR_EXTRACTABLE,
225		NULL,		/* initialAccess */
226		&bulkkey);
227    if (rv)
228	goto loser;
229
230    mark = PORT_ArenaMark(poolp);
231
232    /* Encrypt the bulk key with the public key of each recipient.  */
233    for (i = 0; recipientinfos[i] != NULL; i++) {
234	rv = SecCmsRecipientInfoWrapBulkKey(recipientinfos[i], bulkkey, bulkalgtag);
235	if (rv != SECSuccess)
236	    goto loser;	/* error has been set by SecCmsRecipientInfoEncryptBulkKey */
237	    		/* could be: alg not supported etc. */
238    }
239
240    /* the recipientinfos are all finished. now sort them by DER for SET OF encoding */
241    rv = SecCmsArraySortByDER((void **)envd->recipientInfos, SecCmsRecipientInfoTemplate, NULL);
242    if (rv != SECSuccess)
243	goto loser;	/* error has been set by SecCmsArraySortByDER */
244
245    /* store the bulk key in the contentInfo so that the encoder can find it */
246    SecCmsContentInfoSetBulkKey(cinfo, bulkkey);
247
248    PORT_ArenaUnmark(poolp, mark);
249
250    CFRelease(bulkkey);
251
252    return SECSuccess;
253
254loser:
255    if (mark != NULL)
256	PORT_ArenaRelease (poolp, mark);
257    if (bulkkey)
258	CFRelease(bulkkey);
259
260    return SECFailure;
261}
262
263/*
264 * SecCmsEnvelopedDataEncodeBeforeData - set up encryption
265 *
266 * it is essential that this is called before the contentEncAlg is encoded, because
267 * setting up the encryption may generate IVs and thus change it!
268 */
269OSStatus
270SecCmsEnvelopedDataEncodeBeforeData(SecCmsEnvelopedDataRef envd)
271{
272    SecCmsContentInfoRef cinfo;
273    SecSymmetricKeyRef bulkkey;
274    SECAlgorithmID *algid;
275
276    cinfo = &(envd->contentInfo);
277
278    /* find bulkkey and algorithm - must have been set by SecCmsEnvelopedDataEncodeBeforeStart */
279    bulkkey = SecCmsContentInfoGetBulkKey(cinfo);
280    if (bulkkey == NULL)
281	return SECFailure;
282    algid = SecCmsContentInfoGetContentEncAlg(cinfo);
283    if (algid == NULL)
284	return SECFailure;
285
286    /* this may modify algid (with IVs generated in a token).
287     * it is essential that algid is a pointer to the contentEncAlg data, not a
288     * pointer to a copy! */
289    cinfo->ciphcx = SecCmsCipherContextStartEncrypt(envd->cmsg->poolp, bulkkey, algid);
290    CFRelease(bulkkey);
291    if (cinfo->ciphcx == NULL)
292	return SECFailure;
293
294    return SECSuccess;
295}
296
297/*
298 * SecCmsEnvelopedDataEncodeAfterData - finalize this envelopedData for encoding
299 */
300OSStatus
301SecCmsEnvelopedDataEncodeAfterData(SecCmsEnvelopedDataRef envd)
302{
303    if (envd->contentInfo.ciphcx) {
304	SecCmsCipherContextDestroy(envd->contentInfo.ciphcx);
305	envd->contentInfo.ciphcx = NULL;
306    }
307
308    /* nothing else to do after data */
309    return SECSuccess;
310}
311
312/*
313 * SecCmsEnvelopedDataDecodeBeforeData - find our recipientinfo,
314 * derive bulk key & set up our contentinfo
315 */
316OSStatus
317SecCmsEnvelopedDataDecodeBeforeData(SecCmsEnvelopedDataRef envd)
318{
319    SecCmsRecipientInfoRef ri;
320    SecSymmetricKeyRef bulkkey = NULL;
321    SECOidTag bulkalgtag;
322    SECAlgorithmID *bulkalg;
323    OSStatus rv = SECFailure;
324    SecCmsContentInfoRef cinfo;
325    SecCmsRecipient **recipient_list = NULL;
326    SecCmsRecipient *recipient;
327    int rlIndex;
328
329    if (SecCmsArrayCount((void **)envd->recipientInfos) == 0) {
330	PORT_SetError(SEC_ERROR_BAD_DATA);
331#if 0
332	PORT_SetErrorString("No recipient data in envelope.");
333#endif
334	goto loser;
335    }
336
337    /* look if one of OUR cert's issuerSN is on the list of recipients, and if so,  */
338    /* get the cert and private key for it right away */
339    recipient_list = nss_cms_recipient_list_create(envd->recipientInfos);
340    if (recipient_list == NULL)
341	goto loser;
342
343    /* what about multiple recipientInfos that match?
344     * especially if, for some reason, we could not produce a bulk key with the first match?!
345     * we could loop & feed partial recipient_list to PK11_FindCertAndKeyByRecipientList...
346     * maybe later... */
347    rlIndex = nss_cms_FindCertAndKeyByRecipientList(recipient_list, envd->cmsg->pwfn_arg);
348
349    /* if that fails, then we're not an intended recipient and cannot decrypt */
350    if (rlIndex < 0) {
351	PORT_SetError(SEC_ERROR_NOT_A_RECIPIENT);
352#if 0
353	PORT_SetErrorString("Cannot decrypt data because proper key cannot be found.");
354#endif
355	goto loser;
356    }
357
358    recipient = recipient_list[rlIndex];
359    if (!recipient->cert || !recipient->privkey) {
360	/* XXX should set an error code ?!? */
361	goto loser;
362    }
363    /* get a pointer to "our" recipientinfo */
364    ri = envd->recipientInfos[recipient->riIndex];
365
366    cinfo = &(envd->contentInfo);
367    bulkalgtag = SecCmsContentInfoGetContentEncAlgTag(cinfo);
368    if (bulkalgtag == SEC_OID_UNKNOWN) {
369	PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
370    } else
371	bulkkey = SecCmsRecipientInfoUnwrapBulkKey(ri,recipient->subIndex,
372						    recipient->cert,
373						    recipient->privkey,
374						    bulkalgtag);
375    if (bulkkey == NULL) {
376	/* no success finding a bulk key */
377	rv = errSecDataNotAvailable;
378	goto loser;
379    }
380
381    SecCmsContentInfoSetBulkKey(cinfo, bulkkey);
382    // @@@ See 3401088 for details.  We need to CFRelease cinfo->bulkkey before recipient->privkey gets CFReleased. It's created with SecKeyCreateWithCSSMKey which is not safe currently.  If the private key's SecKeyRef from which we extracted the CSP gets CFRelease before the builkkey does we crash.  We should really fix SecKeyCreateWithCSSMKey which is a huge hack currently.  To work around this we add recipient->privkey to the cinfo so it gets when cinfo is destroyed.
383    CFRetain(recipient->privkey);
384    cinfo->privkey = recipient->privkey;
385
386    bulkalg = SecCmsContentInfoGetContentEncAlg(cinfo);
387
388    cinfo->ciphcx = SecCmsCipherContextStartDecrypt(bulkkey, bulkalg);
389    if (cinfo->ciphcx == NULL)
390	goto loser;		/* error has been set by SecCmsCipherContextStartDecrypt */
391
392#if 1
393    // @@@ Fix me
394#else
395    /*
396     * HACK ALERT!!
397     * For PKCS5 Encryption Algorithms, the bulkkey is actually a different
398     * structure.  Therefore, we need to set the bulkkey to the actual key
399     * prior to freeing it.
400     */
401    if (SEC_PKCS5IsAlgorithmPBEAlg(bulkalg)) {
402	SEC_PKCS5KeyAndPassword *keyPwd = (SEC_PKCS5KeyAndPassword *)bulkkey;
403	bulkkey = keyPwd->key;
404    }
405#endif
406
407    rv = SECSuccess;
408
409loser:
410    if (bulkkey)
411	CFRelease(bulkkey);
412    if (recipient_list != NULL)
413	nss_cms_recipient_list_destroy(recipient_list);
414    return rv;
415}
416
417/*
418 * SecCmsEnvelopedDataDecodeAfterData - finish decrypting this envelopedData's content
419 */
420OSStatus
421SecCmsEnvelopedDataDecodeAfterData(SecCmsEnvelopedDataRef envd)
422{
423    if (envd && envd->contentInfo.ciphcx) {
424	SecCmsCipherContextDestroy(envd->contentInfo.ciphcx);
425	envd->contentInfo.ciphcx = NULL;
426    }
427
428    return SECSuccess;
429}
430
431/*
432 * SecCmsEnvelopedDataDecodeAfterEnd - finish decoding this envelopedData
433 */
434OSStatus
435SecCmsEnvelopedDataDecodeAfterEnd(SecCmsEnvelopedDataRef envd)
436{
437    /* apply final touches */
438    return SECSuccess;
439}
440
441