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