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