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