|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* |
|
6 * CMS encoding. |
|
7 */ |
|
8 |
|
9 #include "cmslocal.h" |
|
10 |
|
11 #include "cert.h" |
|
12 #include "key.h" |
|
13 #include "secasn1.h" |
|
14 #include "secoid.h" |
|
15 #include "secitem.h" |
|
16 #include "pk11func.h" |
|
17 #include "secerr.h" |
|
18 |
|
19 struct nss_cms_encoder_output { |
|
20 NSSCMSContentCallback outputfn; |
|
21 void *outputarg; |
|
22 PLArenaPool *destpoolp; |
|
23 SECItem *dest; |
|
24 }; |
|
25 |
|
26 struct NSSCMSEncoderContextStr { |
|
27 SEC_ASN1EncoderContext * ecx; /* ASN.1 encoder context */ |
|
28 PRBool ecxupdated; /* true if data was handed in */ |
|
29 NSSCMSMessage * cmsg; /* pointer to the root message */ |
|
30 SECOidTag type; /* type tag of the current content */ |
|
31 NSSCMSContent content; /* pointer to current content */ |
|
32 struct nss_cms_encoder_output output; /* output function */ |
|
33 int error; /* error code */ |
|
34 NSSCMSEncoderContext * childp7ecx; /* link to child encoder context */ |
|
35 }; |
|
36 |
|
37 static SECStatus nss_cms_before_data(NSSCMSEncoderContext *p7ecx); |
|
38 static SECStatus nss_cms_after_data(NSSCMSEncoderContext *p7ecx); |
|
39 static SECStatus nss_cms_encoder_update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len); |
|
40 static SECStatus nss_cms_encoder_work_data(NSSCMSEncoderContext *p7ecx, SECItem *dest, |
|
41 const unsigned char *data, unsigned long len, |
|
42 PRBool final, PRBool innermost); |
|
43 |
|
44 extern const SEC_ASN1Template NSSCMSMessageTemplate[]; |
|
45 |
|
46 /* |
|
47 * The little output function that the ASN.1 encoder calls to hand |
|
48 * us bytes which we in turn hand back to our caller (via the callback |
|
49 * they gave us). |
|
50 */ |
|
51 static void |
|
52 nss_cms_encoder_out(void *arg, const char *buf, unsigned long len, |
|
53 int depth, SEC_ASN1EncodingPart data_kind) |
|
54 { |
|
55 struct nss_cms_encoder_output *output = (struct nss_cms_encoder_output *)arg; |
|
56 unsigned char *dest; |
|
57 unsigned long offset; |
|
58 |
|
59 #ifdef CMSDEBUG |
|
60 int i; |
|
61 const char *data_name = "unknown"; |
|
62 |
|
63 switch (data_kind) { |
|
64 case SEC_ASN1_Identifier: |
|
65 data_name = "identifier"; |
|
66 break; |
|
67 case SEC_ASN1_Length: |
|
68 data_name = "length"; |
|
69 break; |
|
70 case SEC_ASN1_Contents: |
|
71 data_name = "contents"; |
|
72 break; |
|
73 case SEC_ASN1_EndOfContents: |
|
74 data_name = "end-of-contents"; |
|
75 break; |
|
76 } |
|
77 fprintf(stderr, "kind = %s, depth = %d, len = %d\n", data_name, depth, len); |
|
78 for (i=0; i < len; i++) { |
|
79 fprintf(stderr, " %02x%s", (unsigned int)buf[i] & 0xff, ((i % 16) == 15) ? "\n" : ""); |
|
80 } |
|
81 if ((i % 16) != 0) |
|
82 fprintf(stderr, "\n"); |
|
83 #endif |
|
84 |
|
85 if (output->outputfn != NULL) |
|
86 /* call output callback with DER data */ |
|
87 output->outputfn(output->outputarg, buf, len); |
|
88 |
|
89 if (output->dest != NULL) { |
|
90 /* store DER data in SECItem */ |
|
91 offset = output->dest->len; |
|
92 if (offset == 0) { |
|
93 dest = (unsigned char *)PORT_ArenaAlloc(output->destpoolp, len); |
|
94 } else { |
|
95 dest = (unsigned char *)PORT_ArenaGrow(output->destpoolp, |
|
96 output->dest->data, |
|
97 output->dest->len, |
|
98 output->dest->len + len); |
|
99 } |
|
100 if (dest == NULL) |
|
101 /* oops */ |
|
102 return; |
|
103 |
|
104 output->dest->data = dest; |
|
105 output->dest->len += len; |
|
106 |
|
107 /* copy it in */ |
|
108 PORT_Memcpy(output->dest->data + offset, buf, len); |
|
109 } |
|
110 } |
|
111 |
|
112 /* |
|
113 * nss_cms_encoder_notify - ASN.1 encoder callback |
|
114 * |
|
115 * this function is called by the ASN.1 encoder before and after the encoding of |
|
116 * every object. here, it is used to keep track of data structures, set up |
|
117 * encryption and/or digesting and possibly set up child encoders. |
|
118 */ |
|
119 static void |
|
120 nss_cms_encoder_notify(void *arg, PRBool before, void *dest, int depth) |
|
121 { |
|
122 NSSCMSEncoderContext *p7ecx; |
|
123 NSSCMSContentInfo *rootcinfo, *cinfo; |
|
124 PRBool after = !before; |
|
125 PLArenaPool *poolp; |
|
126 SECOidTag childtype; |
|
127 SECItem *item; |
|
128 |
|
129 p7ecx = (NSSCMSEncoderContext *)arg; |
|
130 PORT_Assert(p7ecx != NULL); |
|
131 |
|
132 rootcinfo = &(p7ecx->cmsg->contentInfo); |
|
133 poolp = p7ecx->cmsg->poolp; |
|
134 |
|
135 #ifdef CMSDEBUG |
|
136 fprintf(stderr, "%6.6s, dest = 0x%08x, depth = %d\n", before ? "before" : "after", dest, depth); |
|
137 #endif |
|
138 |
|
139 /* |
|
140 * Watch for the content field, at which point we want to instruct |
|
141 * the ASN.1 encoder to start taking bytes from the buffer. |
|
142 */ |
|
143 if (NSS_CMSType_IsData(p7ecx->type)) { |
|
144 cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
|
145 if (before && dest == &(cinfo->rawContent)) { |
|
146 /* just set up encoder to grab from user - no encryption or digesting */ |
|
147 if ((item = cinfo->content.data) != NULL) |
|
148 (void)nss_cms_encoder_work_data(p7ecx, NULL, item->data, item->len, PR_TRUE, PR_TRUE); |
|
149 else |
|
150 SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); |
|
151 SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); /* no need to get notified anymore */ |
|
152 } |
|
153 } else if (NSS_CMSType_IsWrapper(p7ecx->type)) { |
|
154 /* when we know what the content is, we encode happily until we reach the inner content */ |
|
155 cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
|
156 childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
|
157 |
|
158 if (after && dest == &(cinfo->contentType)) { |
|
159 /* we're right before encoding the data (if we have some or not) */ |
|
160 /* (for encrypted data, we're right before the contentEncAlg which may change */ |
|
161 /* in nss_cms_before_data because of IV calculation when setting up encryption) */ |
|
162 if (nss_cms_before_data(p7ecx) != SECSuccess) |
|
163 p7ecx->error = PORT_GetError(); |
|
164 } |
|
165 if (before && dest == &(cinfo->rawContent)) { |
|
166 if (p7ecx->childp7ecx == NULL) { |
|
167 if ((NSS_CMSType_IsData(childtype) && (item = cinfo->content.data) != NULL)) { |
|
168 /* we are the innermost non-data and we have data - feed it in */ |
|
169 (void)nss_cms_encoder_work_data(p7ecx, NULL, item->data, item->len, PR_TRUE, PR_TRUE); |
|
170 } else { |
|
171 /* else we'll have to get data from user */ |
|
172 SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); |
|
173 } |
|
174 } else { |
|
175 /* if we have a nested encoder, wait for its data */ |
|
176 SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); |
|
177 } |
|
178 } |
|
179 if (after && dest == &(cinfo->rawContent)) { |
|
180 if (nss_cms_after_data(p7ecx) != SECSuccess) |
|
181 p7ecx->error = PORT_GetError(); |
|
182 SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); /* no need to get notified anymore */ |
|
183 } |
|
184 } else { |
|
185 /* we're still in the root message */ |
|
186 if (after && dest == &(rootcinfo->contentType)) { |
|
187 /* got the content type OID now - so find out the type tag */ |
|
188 p7ecx->type = NSS_CMSContentInfo_GetContentTypeTag(rootcinfo); |
|
189 /* set up a pointer to our current content */ |
|
190 p7ecx->content = rootcinfo->content; |
|
191 } |
|
192 } |
|
193 } |
|
194 |
|
195 /* |
|
196 * nss_cms_before_data - setup the current encoder to receive data |
|
197 */ |
|
198 static SECStatus |
|
199 nss_cms_before_data(NSSCMSEncoderContext *p7ecx) |
|
200 { |
|
201 SECStatus rv; |
|
202 SECOidTag childtype; |
|
203 NSSCMSContentInfo *cinfo; |
|
204 PLArenaPool *poolp; |
|
205 NSSCMSEncoderContext *childp7ecx; |
|
206 const SEC_ASN1Template *template; |
|
207 |
|
208 poolp = p7ecx->cmsg->poolp; |
|
209 |
|
210 /* call _Encode_BeforeData handlers */ |
|
211 switch (p7ecx->type) { |
|
212 case SEC_OID_PKCS7_SIGNED_DATA: |
|
213 /* we're encoding a signedData, so set up the digests */ |
|
214 rv = NSS_CMSSignedData_Encode_BeforeData(p7ecx->content.signedData); |
|
215 break; |
|
216 case SEC_OID_PKCS7_DIGESTED_DATA: |
|
217 /* we're encoding a digestedData, so set up the digest */ |
|
218 rv = NSS_CMSDigestedData_Encode_BeforeData(p7ecx->content.digestedData); |
|
219 break; |
|
220 case SEC_OID_PKCS7_ENVELOPED_DATA: |
|
221 rv = NSS_CMSEnvelopedData_Encode_BeforeData(p7ecx->content.envelopedData); |
|
222 break; |
|
223 case SEC_OID_PKCS7_ENCRYPTED_DATA: |
|
224 rv = NSS_CMSEncryptedData_Encode_BeforeData(p7ecx->content.encryptedData); |
|
225 break; |
|
226 default: |
|
227 if (NSS_CMSType_IsWrapper(p7ecx->type)) { |
|
228 rv = NSS_CMSGenericWrapperData_Encode_BeforeData(p7ecx->type, p7ecx->content.genericData); |
|
229 } else { |
|
230 rv = SECFailure; |
|
231 } |
|
232 } |
|
233 if (rv != SECSuccess) |
|
234 return SECFailure; |
|
235 |
|
236 /* ok, now we have a pointer to cinfo */ |
|
237 /* find out what kind of data is encapsulated */ |
|
238 |
|
239 cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
|
240 childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
|
241 |
|
242 if (NSS_CMSType_IsWrapper(childtype)) { |
|
243 /* in these cases, we need to set up a child encoder! */ |
|
244 /* create new encoder context */ |
|
245 childp7ecx = PORT_ZAlloc(sizeof(NSSCMSEncoderContext)); |
|
246 if (childp7ecx == NULL) |
|
247 return SECFailure; |
|
248 |
|
249 /* the CHILD encoder needs to hand its encoded data to the CURRENT encoder |
|
250 * (which will encrypt and/or digest it) |
|
251 * this needs to route back into our update function |
|
252 * which finds the lowest encoding context & encrypts and computes digests */ |
|
253 childp7ecx->type = childtype; |
|
254 childp7ecx->content = cinfo->content; |
|
255 /* use the non-recursive update function here, of course */ |
|
256 childp7ecx->output.outputfn = (NSSCMSContentCallback)nss_cms_encoder_update; |
|
257 childp7ecx->output.outputarg = p7ecx; |
|
258 childp7ecx->output.destpoolp = NULL; |
|
259 childp7ecx->output.dest = NULL; |
|
260 childp7ecx->cmsg = p7ecx->cmsg; |
|
261 childp7ecx->ecxupdated = PR_FALSE; |
|
262 childp7ecx->childp7ecx = NULL; |
|
263 |
|
264 template = NSS_CMSUtil_GetTemplateByTypeTag(childtype); |
|
265 if (template == NULL) |
|
266 goto loser; /* cannot happen */ |
|
267 |
|
268 /* now initialize the data for encoding the first third */ |
|
269 switch (childp7ecx->type) { |
|
270 case SEC_OID_PKCS7_SIGNED_DATA: |
|
271 rv = NSS_CMSSignedData_Encode_BeforeStart(cinfo->content.signedData); |
|
272 break; |
|
273 case SEC_OID_PKCS7_ENVELOPED_DATA: |
|
274 rv = NSS_CMSEnvelopedData_Encode_BeforeStart(cinfo->content.envelopedData); |
|
275 break; |
|
276 case SEC_OID_PKCS7_DIGESTED_DATA: |
|
277 rv = NSS_CMSDigestedData_Encode_BeforeStart(cinfo->content.digestedData); |
|
278 break; |
|
279 case SEC_OID_PKCS7_ENCRYPTED_DATA: |
|
280 rv = NSS_CMSEncryptedData_Encode_BeforeStart(cinfo->content.encryptedData); |
|
281 break; |
|
282 default: |
|
283 rv = NSS_CMSGenericWrapperData_Encode_BeforeStart(childp7ecx->type, cinfo->content.genericData); |
|
284 break; |
|
285 } |
|
286 if (rv != SECSuccess) |
|
287 goto loser; |
|
288 |
|
289 /* |
|
290 * Initialize the BER encoder. |
|
291 */ |
|
292 childp7ecx->ecx = SEC_ASN1EncoderStart(cinfo->content.pointer, template, |
|
293 nss_cms_encoder_out, &(childp7ecx->output)); |
|
294 if (childp7ecx->ecx == NULL) |
|
295 goto loser; |
|
296 |
|
297 /* |
|
298 * Indicate that we are streaming. We will be streaming until we |
|
299 * get past the contents bytes. |
|
300 */ |
|
301 if (!cinfo->privateInfo || !cinfo->privateInfo->dontStream) |
|
302 SEC_ASN1EncoderSetStreaming(childp7ecx->ecx); |
|
303 |
|
304 /* |
|
305 * The notify function will watch for the contents field. |
|
306 */ |
|
307 p7ecx->childp7ecx = childp7ecx; |
|
308 SEC_ASN1EncoderSetNotifyProc(childp7ecx->ecx, nss_cms_encoder_notify, childp7ecx); |
|
309 |
|
310 /* please note that we are NOT calling SEC_ASN1EncoderUpdate here to kick off the */ |
|
311 /* encoding process - we'll do that from the update function instead */ |
|
312 /* otherwise we'd be encoding data from a call of the notify function of the */ |
|
313 /* parent encoder (which would not work) */ |
|
314 |
|
315 } else if (NSS_CMSType_IsData(childtype)) { |
|
316 p7ecx->childp7ecx = NULL; |
|
317 } else { |
|
318 /* we do not know this type */ |
|
319 p7ecx->error = SEC_ERROR_BAD_DER; |
|
320 } |
|
321 |
|
322 return SECSuccess; |
|
323 |
|
324 loser: |
|
325 if (childp7ecx) { |
|
326 if (childp7ecx->ecx) |
|
327 SEC_ASN1EncoderFinish(childp7ecx->ecx); |
|
328 PORT_Free(childp7ecx); |
|
329 p7ecx->childp7ecx = NULL; |
|
330 } |
|
331 return SECFailure; |
|
332 } |
|
333 |
|
334 static SECStatus |
|
335 nss_cms_after_data(NSSCMSEncoderContext *p7ecx) |
|
336 { |
|
337 SECStatus rv = SECFailure; |
|
338 |
|
339 switch (p7ecx->type) { |
|
340 case SEC_OID_PKCS7_SIGNED_DATA: |
|
341 /* this will finish the digests and sign */ |
|
342 rv = NSS_CMSSignedData_Encode_AfterData(p7ecx->content.signedData); |
|
343 break; |
|
344 case SEC_OID_PKCS7_ENVELOPED_DATA: |
|
345 rv = NSS_CMSEnvelopedData_Encode_AfterData(p7ecx->content.envelopedData); |
|
346 break; |
|
347 case SEC_OID_PKCS7_DIGESTED_DATA: |
|
348 rv = NSS_CMSDigestedData_Encode_AfterData(p7ecx->content.digestedData); |
|
349 break; |
|
350 case SEC_OID_PKCS7_ENCRYPTED_DATA: |
|
351 rv = NSS_CMSEncryptedData_Encode_AfterData(p7ecx->content.encryptedData); |
|
352 break; |
|
353 default: |
|
354 if (NSS_CMSType_IsWrapper(p7ecx->type)) { |
|
355 rv = NSS_CMSGenericWrapperData_Encode_AfterData(p7ecx->type, p7ecx->content.genericData); |
|
356 } else { |
|
357 rv = SECFailure; |
|
358 } |
|
359 break; |
|
360 } |
|
361 return rv; |
|
362 } |
|
363 |
|
364 /* |
|
365 * nss_cms_encoder_work_data - process incoming data |
|
366 * |
|
367 * (from the user or the next encoding layer) |
|
368 * Here, we need to digest and/or encrypt, then pass it on |
|
369 */ |
|
370 static SECStatus |
|
371 nss_cms_encoder_work_data(NSSCMSEncoderContext *p7ecx, SECItem *dest, |
|
372 const unsigned char *data, unsigned long len, |
|
373 PRBool final, PRBool innermost) |
|
374 { |
|
375 unsigned char *buf = NULL; |
|
376 SECStatus rv; |
|
377 NSSCMSContentInfo *cinfo; |
|
378 |
|
379 rv = SECSuccess; /* may as well be optimistic */ |
|
380 |
|
381 /* |
|
382 * We should really have data to process, or we should be trying |
|
383 * to finish/flush the last block. (This is an overly paranoid |
|
384 * check since all callers are in this file and simple inspection |
|
385 * proves they do it right. But it could find a bug in future |
|
386 * modifications/development, that is why it is here.) |
|
387 */ |
|
388 PORT_Assert ((data != NULL && len) || final); |
|
389 |
|
390 /* we got data (either from the caller, or from a lower level encoder) */ |
|
391 cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
|
392 if (!cinfo) { |
|
393 /* The original programmer didn't expect this to happen */ |
|
394 p7ecx->error = SEC_ERROR_LIBRARY_FAILURE; |
|
395 return SECFailure; |
|
396 } |
|
397 |
|
398 /* Update the running digest. */ |
|
399 if (len && cinfo->privateInfo && cinfo->privateInfo->digcx != NULL) |
|
400 NSS_CMSDigestContext_Update(cinfo->privateInfo->digcx, data, len); |
|
401 |
|
402 /* Encrypt this chunk. */ |
|
403 if (cinfo->privateInfo && cinfo->privateInfo->ciphcx != NULL) { |
|
404 unsigned int inlen; /* length of data being encrypted */ |
|
405 unsigned int outlen; /* length of encrypted data */ |
|
406 unsigned int buflen; /* length available for encrypted data */ |
|
407 |
|
408 inlen = len; |
|
409 buflen = NSS_CMSCipherContext_EncryptLength(cinfo->privateInfo->ciphcx, inlen, final); |
|
410 if (buflen == 0) { |
|
411 /* |
|
412 * No output is expected, but the input data may be buffered |
|
413 * so we still have to call Encrypt. |
|
414 */ |
|
415 rv = NSS_CMSCipherContext_Encrypt(cinfo->privateInfo->ciphcx, NULL, NULL, 0, |
|
416 data, inlen, final); |
|
417 if (final) { |
|
418 len = 0; |
|
419 goto done; |
|
420 } |
|
421 return rv; |
|
422 } |
|
423 |
|
424 if (dest != NULL) |
|
425 buf = (unsigned char*)PORT_ArenaAlloc(p7ecx->cmsg->poolp, buflen); |
|
426 else |
|
427 buf = (unsigned char*)PORT_Alloc(buflen); |
|
428 |
|
429 if (buf == NULL) { |
|
430 rv = SECFailure; |
|
431 } else { |
|
432 rv = NSS_CMSCipherContext_Encrypt(cinfo->privateInfo->ciphcx, buf, &outlen, buflen, |
|
433 data, inlen, final); |
|
434 data = buf; |
|
435 len = outlen; |
|
436 } |
|
437 if (rv != SECSuccess) |
|
438 /* encryption or malloc failed? */ |
|
439 return rv; |
|
440 } |
|
441 |
|
442 |
|
443 /* |
|
444 * at this point (data,len) has everything we'd like to give to the CURRENT encoder |
|
445 * (which will encode it, then hand it back to the user or the parent encoder) |
|
446 * We don't encode the data if we're innermost and we're told not to include the data |
|
447 */ |
|
448 if (p7ecx->ecx != NULL && len && (!innermost || cinfo->rawContent != cinfo->content.pointer)) |
|
449 rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, (const char *)data, len); |
|
450 |
|
451 done: |
|
452 |
|
453 if (cinfo->privateInfo && cinfo->privateInfo->ciphcx != NULL) { |
|
454 if (dest != NULL) { |
|
455 dest->data = buf; |
|
456 dest->len = len; |
|
457 } else if (buf != NULL) { |
|
458 PORT_Free (buf); |
|
459 } |
|
460 } |
|
461 return rv; |
|
462 } |
|
463 |
|
464 /* |
|
465 * nss_cms_encoder_update - deliver encoded data to the next higher level |
|
466 * |
|
467 * no recursion here because we REALLY want to end up at the next higher encoder! |
|
468 */ |
|
469 static SECStatus |
|
470 nss_cms_encoder_update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len) |
|
471 { |
|
472 /* XXX Error handling needs help. Return what? Do "Finish" on failure? */ |
|
473 return nss_cms_encoder_work_data (p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_FALSE); |
|
474 } |
|
475 |
|
476 /* |
|
477 * NSS_CMSEncoder_Start - set up encoding of a CMS message |
|
478 * |
|
479 * "cmsg" - message to encode |
|
480 * "outputfn", "outputarg" - callback function for delivery of DER-encoded output |
|
481 * will not be called if NULL. |
|
482 * "dest" - if non-NULL, pointer to SECItem that will hold the DER-encoded output |
|
483 * "destpoolp" - pool to allocate DER-encoded output in |
|
484 * "pwfn", pwfn_arg" - callback function for getting token password |
|
485 * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData |
|
486 * "detached_digestalgs", "detached_digests" - digests from detached content |
|
487 */ |
|
488 NSSCMSEncoderContext * |
|
489 NSS_CMSEncoder_Start(NSSCMSMessage *cmsg, |
|
490 NSSCMSContentCallback outputfn, void *outputarg, |
|
491 SECItem *dest, PLArenaPool *destpoolp, |
|
492 PK11PasswordFunc pwfn, void *pwfn_arg, |
|
493 NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg, |
|
494 SECAlgorithmID **detached_digestalgs, SECItem **detached_digests) |
|
495 { |
|
496 NSSCMSEncoderContext *p7ecx; |
|
497 SECStatus rv; |
|
498 NSSCMSContentInfo *cinfo; |
|
499 SECOidTag tag; |
|
500 |
|
501 NSS_CMSMessage_SetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg, |
|
502 detached_digestalgs, detached_digests); |
|
503 |
|
504 p7ecx = (NSSCMSEncoderContext *)PORT_ZAlloc(sizeof(NSSCMSEncoderContext)); |
|
505 if (p7ecx == NULL) { |
|
506 PORT_SetError(SEC_ERROR_NO_MEMORY); |
|
507 return NULL; |
|
508 } |
|
509 |
|
510 p7ecx->cmsg = cmsg; |
|
511 p7ecx->output.outputfn = outputfn; |
|
512 p7ecx->output.outputarg = outputarg; |
|
513 p7ecx->output.dest = dest; |
|
514 p7ecx->output.destpoolp = destpoolp; |
|
515 p7ecx->type = SEC_OID_UNKNOWN; |
|
516 |
|
517 cinfo = NSS_CMSMessage_GetContentInfo(cmsg); |
|
518 |
|
519 tag = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
|
520 switch (tag) { |
|
521 case SEC_OID_PKCS7_SIGNED_DATA: |
|
522 rv = NSS_CMSSignedData_Encode_BeforeStart(cinfo->content.signedData); |
|
523 break; |
|
524 case SEC_OID_PKCS7_ENVELOPED_DATA: |
|
525 rv = NSS_CMSEnvelopedData_Encode_BeforeStart(cinfo->content.envelopedData); |
|
526 break; |
|
527 case SEC_OID_PKCS7_DIGESTED_DATA: |
|
528 rv = NSS_CMSDigestedData_Encode_BeforeStart(cinfo->content.digestedData); |
|
529 break; |
|
530 case SEC_OID_PKCS7_ENCRYPTED_DATA: |
|
531 rv = NSS_CMSEncryptedData_Encode_BeforeStart(cinfo->content.encryptedData); |
|
532 break; |
|
533 default: |
|
534 if (NSS_CMSType_IsWrapper(tag)) { |
|
535 rv = NSS_CMSGenericWrapperData_Encode_BeforeStart(tag, |
|
536 p7ecx->content.genericData); |
|
537 } else { |
|
538 rv = SECFailure; |
|
539 } |
|
540 break; |
|
541 } |
|
542 if (rv != SECSuccess) { |
|
543 PORT_Free(p7ecx); |
|
544 return NULL; |
|
545 } |
|
546 |
|
547 /* Initialize the BER encoder. |
|
548 * Note that this will not encode anything until the first call to SEC_ASN1EncoderUpdate */ |
|
549 p7ecx->ecx = SEC_ASN1EncoderStart(cmsg, NSSCMSMessageTemplate, |
|
550 nss_cms_encoder_out, &(p7ecx->output)); |
|
551 if (p7ecx->ecx == NULL) { |
|
552 PORT_Free (p7ecx); |
|
553 return NULL; |
|
554 } |
|
555 p7ecx->ecxupdated = PR_FALSE; |
|
556 |
|
557 /* |
|
558 * Indicate that we are streaming. We will be streaming until we |
|
559 * get past the contents bytes. |
|
560 */ |
|
561 if (!cinfo->privateInfo || !cinfo->privateInfo->dontStream) |
|
562 SEC_ASN1EncoderSetStreaming(p7ecx->ecx); |
|
563 |
|
564 /* |
|
565 * The notify function will watch for the contents field. |
|
566 */ |
|
567 SEC_ASN1EncoderSetNotifyProc(p7ecx->ecx, nss_cms_encoder_notify, p7ecx); |
|
568 |
|
569 /* this will kick off the encoding process & encode everything up to the content bytes, |
|
570 * at which point the notify function sets streaming mode (and possibly creates |
|
571 * a child encoder). */ |
|
572 p7ecx->ecxupdated = PR_TRUE; |
|
573 if (SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0) != SECSuccess) { |
|
574 PORT_Free (p7ecx); |
|
575 return NULL; |
|
576 } |
|
577 |
|
578 return p7ecx; |
|
579 } |
|
580 |
|
581 /* |
|
582 * NSS_CMSEncoder_Update - take content data delivery from the user |
|
583 * |
|
584 * "p7ecx" - encoder context |
|
585 * "data" - content data |
|
586 * "len" - length of content data |
|
587 * |
|
588 * need to find the lowest level (and call SEC_ASN1EncoderUpdate on the way down), |
|
589 * then hand the data to the work_data fn |
|
590 */ |
|
591 SECStatus |
|
592 NSS_CMSEncoder_Update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len) |
|
593 { |
|
594 SECStatus rv; |
|
595 NSSCMSContentInfo *cinfo; |
|
596 SECOidTag childtype; |
|
597 |
|
598 if (p7ecx->error) |
|
599 return SECFailure; |
|
600 |
|
601 /* hand data to the innermost decoder */ |
|
602 if (p7ecx->childp7ecx) { |
|
603 /* tell the child to start encoding, up to its first data byte, if it |
|
604 * hasn't started yet */ |
|
605 if (!p7ecx->childp7ecx->ecxupdated) { |
|
606 p7ecx->childp7ecx->ecxupdated = PR_TRUE; |
|
607 if (SEC_ASN1EncoderUpdate(p7ecx->childp7ecx->ecx, NULL, 0) != SECSuccess) |
|
608 return SECFailure; |
|
609 } |
|
610 /* recursion here */ |
|
611 rv = NSS_CMSEncoder_Update(p7ecx->childp7ecx, data, len); |
|
612 } else { |
|
613 /* we are at innermost decoder */ |
|
614 /* find out about our inner content type - must be data */ |
|
615 cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
|
616 if (!cinfo) { |
|
617 /* The original programmer didn't expect this to happen */ |
|
618 p7ecx->error = SEC_ERROR_LIBRARY_FAILURE; |
|
619 return SECFailure; |
|
620 } |
|
621 |
|
622 childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
|
623 if (!NSS_CMSType_IsData(childtype)) |
|
624 return SECFailure; |
|
625 /* and we must not have preset data */ |
|
626 if (cinfo->content.data != NULL) |
|
627 return SECFailure; |
|
628 |
|
629 /* hand it the data so it can encode it (let DER trickle up the chain) */ |
|
630 rv = nss_cms_encoder_work_data(p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_TRUE); |
|
631 } |
|
632 return rv; |
|
633 } |
|
634 |
|
635 /* |
|
636 * NSS_CMSEncoder_Cancel - stop all encoding |
|
637 * |
|
638 * we need to walk down the chain of encoders and the finish them from the innermost out |
|
639 */ |
|
640 SECStatus |
|
641 NSS_CMSEncoder_Cancel(NSSCMSEncoderContext *p7ecx) |
|
642 { |
|
643 SECStatus rv = SECFailure; |
|
644 |
|
645 /* XXX do this right! */ |
|
646 |
|
647 /* |
|
648 * Finish any inner decoders before us so that all the encoded data is flushed |
|
649 * This basically finishes all the decoders from the innermost to the outermost. |
|
650 * Finishing an inner decoder may result in data being updated to the outer decoder |
|
651 * while we are already in NSS_CMSEncoder_Finish, but that's allright. |
|
652 */ |
|
653 if (p7ecx->childp7ecx) { |
|
654 rv = NSS_CMSEncoder_Cancel(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ |
|
655 /* remember rv for now */ |
|
656 } |
|
657 |
|
658 /* |
|
659 * On the way back up, there will be no more data (if we had an |
|
660 * inner encoder, it is done now!) |
|
661 * Flush out any remaining data and/or finish digests. |
|
662 */ |
|
663 rv = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)); |
|
664 if (rv != SECSuccess) |
|
665 goto loser; |
|
666 |
|
667 p7ecx->childp7ecx = NULL; |
|
668 |
|
669 /* kick the encoder back into working mode again. |
|
670 * We turn off streaming stuff (which will cause the encoder to continue |
|
671 * encoding happily, now that we have all the data (like digests) ready for it). |
|
672 */ |
|
673 SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); |
|
674 SEC_ASN1EncoderClearStreaming(p7ecx->ecx); |
|
675 |
|
676 /* now that TakeFromBuf is off, this will kick this encoder to finish encoding */ |
|
677 rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); |
|
678 |
|
679 loser: |
|
680 SEC_ASN1EncoderFinish(p7ecx->ecx); |
|
681 PORT_Free (p7ecx); |
|
682 return rv; |
|
683 } |
|
684 |
|
685 /* |
|
686 * NSS_CMSEncoder_Finish - signal the end of data |
|
687 * |
|
688 * we need to walk down the chain of encoders and the finish them from the innermost out |
|
689 */ |
|
690 SECStatus |
|
691 NSS_CMSEncoder_Finish(NSSCMSEncoderContext *p7ecx) |
|
692 { |
|
693 SECStatus rv = SECFailure; |
|
694 NSSCMSContentInfo *cinfo; |
|
695 |
|
696 /* |
|
697 * Finish any inner decoders before us so that all the encoded data is flushed |
|
698 * This basically finishes all the decoders from the innermost to the outermost. |
|
699 * Finishing an inner decoder may result in data being updated to the outer decoder |
|
700 * while we are already in NSS_CMSEncoder_Finish, but that's allright. |
|
701 */ |
|
702 if (p7ecx->childp7ecx) { |
|
703 /* tell the child to start encoding, up to its first data byte, if it |
|
704 * hasn't yet */ |
|
705 if (!p7ecx->childp7ecx->ecxupdated) { |
|
706 p7ecx->childp7ecx->ecxupdated = PR_TRUE; |
|
707 rv = SEC_ASN1EncoderUpdate(p7ecx->childp7ecx->ecx, NULL, 0); |
|
708 if (rv != SECSuccess) { |
|
709 NSS_CMSEncoder_Finish(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ |
|
710 goto loser; |
|
711 } |
|
712 } |
|
713 rv = NSS_CMSEncoder_Finish(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ |
|
714 if (rv != SECSuccess) |
|
715 goto loser; |
|
716 } |
|
717 |
|
718 /* |
|
719 * On the way back up, there will be no more data (if we had an |
|
720 * inner encoder, it is done now!) |
|
721 * Flush out any remaining data and/or finish digests. |
|
722 */ |
|
723 rv = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)); |
|
724 if (rv != SECSuccess) |
|
725 goto loser; |
|
726 |
|
727 p7ecx->childp7ecx = NULL; |
|
728 |
|
729 cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
|
730 if (!cinfo) { |
|
731 /* The original programmer didn't expect this to happen */ |
|
732 p7ecx->error = SEC_ERROR_LIBRARY_FAILURE; |
|
733 rv = SECFailure; |
|
734 goto loser; |
|
735 } |
|
736 SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); |
|
737 SEC_ASN1EncoderClearStreaming(p7ecx->ecx); |
|
738 /* now that TakeFromBuf is off, this will kick this encoder to finish encoding */ |
|
739 rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); |
|
740 |
|
741 if (p7ecx->error) |
|
742 rv = SECFailure; |
|
743 |
|
744 loser: |
|
745 SEC_ASN1EncoderFinish(p7ecx->ecx); |
|
746 PORT_Free (p7ecx); |
|
747 return rv; |
|
748 } |