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