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/
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.
12 * The Original Code is the Netscape security libraries.
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
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
38 #include <Security/SecCmsDecoder.h>
39 #include <Security/SecCmsContentInfo.h>
40 #include <Security/SecCmsDigestContext.h>
41 #include <Security/SecCmsMessage.h>
47 #include <security_asn1/secasn1.h>
48 #include <security_asn1/secerr.h>
50 struct SecCmsDecoderStr
{
51 SEC_ASN1DecoderContext
* dcx
; /* ASN.1 decoder context */
52 SecCmsMessageRef cmsg
; /* backpointer to the root message */
53 SECOidTag type
; /* type of message */
54 SecCmsContent content
; /* pointer to message */
55 SecCmsDecoderRef childp7dcx
; /* inner CMS decoder context */
58 SecCmsContentCallback cb
;
62 static void nss_cms_decoder_update_filter (void *arg
, const char *data
, size_t len
,
63 int depth
, SEC_ASN1EncodingPart data_kind
);
64 static OSStatus
nss_cms_before_data(SecCmsDecoderRef p7dcx
);
65 static OSStatus
nss_cms_after_data(SecCmsDecoderRef p7dcx
);
66 static OSStatus
nss_cms_after_end(SecCmsDecoderRef p7dcx
);
67 static void nss_cms_decoder_work_data(SecCmsDecoderRef p7dcx
,
68 const unsigned char *data
, size_t len
, Boolean final
);
70 extern const SecAsn1Template SecCmsMessageTemplate
[];
73 * nss_cms_decoder_notify -
74 * this is the driver of the decoding process. It gets called by the ASN.1
75 * decoder before and after an object is decoded.
76 * at various points in the decoding process, we intercept to set up and do
80 nss_cms_decoder_notify(void *arg
, Boolean before
, void *dest
, int depth
)
82 SecCmsDecoderRef p7dcx
;
83 SecCmsContentInfoRef rootcinfo
, cinfo
;
84 Boolean after
= !before
;
86 p7dcx
= (SecCmsDecoderRef
)arg
;
87 rootcinfo
= &(p7dcx
->cmsg
->contentInfo
);
89 /* XXX error handling: need to set p7dcx->error */
92 fprintf(stderr
, "%6.6s, dest = %p, depth = %d\n", before
? "before" : "after", dest
, depth
);
95 /* so what are we working on right now? */
96 switch (p7dcx
->type
) {
99 * right now, we are still decoding the OUTER (root) cinfo
100 * As soon as we know the inner content type, set up the info,
101 * but NO inner decoder or filter. The root decoder handles the first
102 * level children by itself - only for encapsulated contents (which
103 * are encoded as DER inside of an OCTET STRING) we need to set up a
106 if (after
&& dest
== &(rootcinfo
->contentType
)) {
107 p7dcx
->type
= SecCmsContentInfoGetContentTypeTag(rootcinfo
);
108 p7dcx
->content
= rootcinfo
->content
; /* is this ready already ? need to alloc? */
109 /* XXX yes we need to alloc -- continue here */
112 case SEC_OID_PKCS7_DATA
:
114 /* this can only happen if the outermost cinfo has DATA in it */
115 /* otherwise, we handle this type implicitely in the inner decoders */
117 if (before
&& dest
== &(rootcinfo
->content
)) {
118 /* fake it to cause the filter to put the data in the right place... */
119 /* we want the ASN.1 decoder to deliver the decoded bytes to us from now on */
120 SEC_ASN1DecoderSetFilterProc(p7dcx
->dcx
,
121 nss_cms_decoder_update_filter
,
123 (Boolean
)(p7dcx
->cb
!= NULL
));
127 if (after
&& dest
== &(rootcinfo
->content
.data
)) {
128 /* remove the filter */
129 SEC_ASN1DecoderClearFilterProc(p7dcx
->dcx
);
133 case SEC_OID_PKCS7_SIGNED_DATA
:
134 case SEC_OID_PKCS7_ENVELOPED_DATA
:
135 case SEC_OID_PKCS7_DIGESTED_DATA
:
136 case SEC_OID_PKCS7_ENCRYPTED_DATA
:
138 if (before
&& dest
== &(rootcinfo
->content
))
139 break; /* we're not there yet */
141 if (p7dcx
->content
.pointer
== NULL
)
142 p7dcx
->content
= rootcinfo
->content
;
144 /* get this data type's inner contentInfo */
145 cinfo
= SecCmsContentGetContentInfo(p7dcx
->content
.pointer
, p7dcx
->type
);
147 if (before
&& dest
== &(cinfo
->contentType
)) {
148 /* at this point, set up the &%$&$ back pointer */
149 /* we cannot do it later, because the content itself is optional! */
150 /* please give me C++ */
151 switch (p7dcx
->type
) {
152 case SEC_OID_PKCS7_SIGNED_DATA
:
153 p7dcx
->content
.signedData
->cmsg
= p7dcx
->cmsg
;
155 case SEC_OID_PKCS7_DIGESTED_DATA
:
156 p7dcx
->content
.digestedData
->cmsg
= p7dcx
->cmsg
;
158 case SEC_OID_PKCS7_ENVELOPED_DATA
:
159 p7dcx
->content
.envelopedData
->cmsg
= p7dcx
->cmsg
;
161 case SEC_OID_PKCS7_ENCRYPTED_DATA
:
162 p7dcx
->content
.encryptedData
->cmsg
= p7dcx
->cmsg
;
170 if (before
&& dest
== &(cinfo
->rawContent
)) {
171 /* we want the ASN.1 decoder to deliver the decoded bytes to us from now on */
172 SEC_ASN1DecoderSetFilterProc(p7dcx
->dcx
, nss_cms_decoder_update_filter
,
173 p7dcx
, (Boolean
)(p7dcx
->cb
!= NULL
));
176 /* we're right in front of the data */
177 if (nss_cms_before_data(p7dcx
) != SECSuccess
) {
178 SEC_ASN1DecoderClearFilterProc(p7dcx
->dcx
); /* stop all processing */
179 p7dcx
->error
= PORT_GetError();
180 PORT_SetError(0); // Clean the thread error since we've returned the error
183 if (after
&& dest
== &(cinfo
->rawContent
)) {
184 /* we're right after of the data */
185 if (nss_cms_after_data(p7dcx
) != SECSuccess
) {
186 p7dcx
->error
= PORT_GetError();
187 PORT_SetError(0); // Clean the thread error since we've returned the error
190 /* we don't need to see the contents anymore */
191 SEC_ASN1DecoderClearFilterProc(p7dcx
->dcx
);
196 case SEC_OID_PKCS7_AUTHENTICATED_DATA
:
199 /* unsupported or unknown message type - fail (more or less) gracefully */
200 p7dcx
->error
= SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE
;
206 * nss_cms_before_data - set up the current encoder to receive data
209 nss_cms_before_data(SecCmsDecoderRef p7dcx
)
214 SecCmsDecoderRef childp7dcx
;
215 SecCmsContentInfoRef cinfo
;
216 const SecAsn1Template
*template;
220 poolp
= p7dcx
->cmsg
->poolp
;
222 /* call _Decode_BeforeData handlers */
223 switch (p7dcx
->type
) {
224 case SEC_OID_PKCS7_SIGNED_DATA
:
225 /* we're decoding a signedData, so set up the digests */
226 rv
= SecCmsSignedDataDecodeBeforeData(p7dcx
->content
.signedData
);
227 if (rv
!= SECSuccess
)
230 case SEC_OID_PKCS7_DIGESTED_DATA
:
231 /* we're encoding a digestedData, so set up the digest */
232 rv
= SecCmsDigestedDataDecodeBeforeData(p7dcx
->content
.digestedData
);
233 if (rv
!= SECSuccess
)
236 case SEC_OID_PKCS7_ENVELOPED_DATA
:
237 rv
= SecCmsEnvelopedDataDecodeBeforeData(p7dcx
->content
.envelopedData
);
238 if (rv
!= SECSuccess
)
241 case SEC_OID_PKCS7_ENCRYPTED_DATA
:
242 rv
= SecCmsEncryptedDataDecodeBeforeData(p7dcx
->content
.encryptedData
);
243 if (rv
!= SECSuccess
)
250 /* ok, now we have a pointer to cinfo */
251 /* find out what kind of data is encapsulated */
253 cinfo
= SecCmsContentGetContentInfo(p7dcx
->content
.pointer
, p7dcx
->type
);
254 childtype
= SecCmsContentInfoGetContentTypeTag(cinfo
);
256 /* special case for SignedData: "unknown" child type maps to SEC_OID_OTHER */
257 if((childtype
== SEC_OID_UNKNOWN
) && (p7dcx
->type
== SEC_OID_PKCS7_SIGNED_DATA
)) {
258 childtype
= SEC_OID_OTHER
;
261 if ((childtype
== SEC_OID_PKCS7_DATA
) || (childtype
== SEC_OID_OTHER
)){
262 cinfo
->content
.data
= SECITEM_AllocItem(poolp
, NULL
, 0);
263 if (cinfo
->content
.data
== NULL
)
264 /* set memory error */
267 p7dcx
->childp7dcx
= NULL
;
271 /* set up inner decoder */
273 if ((template = SecCmsUtilGetTemplateByTypeTag(childtype
)) == NULL
)
276 childp7dcx
= (SecCmsDecoderRef
)PORT_ZAlloc(sizeof(struct SecCmsDecoderStr
));
277 if (childp7dcx
== NULL
)
280 mark
= PORT_ArenaMark(poolp
);
282 /* allocate space for the stuff we're creating */
283 size
= SecCmsUtilGetSizeByTypeTag(childtype
);
284 childp7dcx
->content
.pointer
= (void *)PORT_ArenaZAlloc(poolp
, size
);
285 if (childp7dcx
->content
.pointer
== NULL
)
288 /* Apple: link the new content to parent ContentInfo */
289 cinfo
->content
.pointer
= childp7dcx
->content
.pointer
;
291 /* start the child decoder */
292 childp7dcx
->dcx
= SEC_ASN1DecoderStart(poolp
, childp7dcx
->content
.pointer
, template, NULL
, 0);
293 if (childp7dcx
->dcx
== NULL
)
296 /* the new decoder needs to notify, too */
297 SEC_ASN1DecoderSetNotifyProc(childp7dcx
->dcx
, nss_cms_decoder_notify
, childp7dcx
);
299 /* tell the parent decoder that it needs to feed us the content data */
300 p7dcx
->childp7dcx
= childp7dcx
;
302 childp7dcx
->type
= childtype
; /* our type */
304 childp7dcx
->cmsg
= p7dcx
->cmsg
; /* backpointer to root message */
306 /* should the child decoder encounter real data, it needs to give it to the caller */
307 childp7dcx
->cb
= p7dcx
->cb
;
308 childp7dcx
->cb_arg
= p7dcx
->cb_arg
;
310 /* now set up the parent to hand decoded data to the next level decoder */
311 p7dcx
->cb
= (SecCmsContentCallback
)SecCmsDecoderUpdate
;
312 p7dcx
->cb_arg
= childp7dcx
;
314 PORT_ArenaUnmark(poolp
, mark
);
320 PORT_ArenaRelease(poolp
, mark
);
322 PORT_Free(childp7dcx
);
323 p7dcx
->childp7dcx
= NULL
;
328 nss_cms_after_data(SecCmsDecoderRef p7dcx
)
331 SecCmsDecoderRef childp7dcx
;
332 OSStatus rv
= SECFailure
;
334 poolp
= p7dcx
->cmsg
->poolp
;
336 /* Handle last block. This is necessary to flush out the last bytes
337 * of a possibly incomplete block */
338 nss_cms_decoder_work_data(p7dcx
, NULL
, 0, PR_TRUE
);
340 /* finish any "inner" decoders - there's no more data coming... */
341 if (p7dcx
->childp7dcx
!= NULL
) {
342 childp7dcx
= p7dcx
->childp7dcx
;
343 if (childp7dcx
->dcx
!= NULL
) {
344 if (SEC_ASN1DecoderFinish(childp7dcx
->dcx
) != SECSuccess
) {
345 /* do what? free content? */
348 rv
= nss_cms_after_end(childp7dcx
);
350 if (rv
!= SECSuccess
)
353 PORT_Free(p7dcx
->childp7dcx
);
354 p7dcx
->childp7dcx
= NULL
;
357 switch (p7dcx
->type
) {
358 case SEC_OID_PKCS7_SIGNED_DATA
:
359 /* this will finish the digests and verify */
360 rv
= SecCmsSignedDataDecodeAfterData(p7dcx
->content
.signedData
);
362 case SEC_OID_PKCS7_ENVELOPED_DATA
:
363 rv
= SecCmsEnvelopedDataDecodeAfterData(p7dcx
->content
.envelopedData
);
365 case SEC_OID_PKCS7_DIGESTED_DATA
:
366 rv
= SecCmsDigestedDataDecodeAfterData(p7dcx
->content
.digestedData
);
368 case SEC_OID_PKCS7_ENCRYPTED_DATA
:
369 rv
= SecCmsEncryptedDataDecodeAfterData(p7dcx
->content
.encryptedData
);
371 case SEC_OID_PKCS7_DATA
:
383 nss_cms_after_end(SecCmsDecoderRef p7dcx
)
387 switch (p7dcx
->type
) {
388 case SEC_OID_PKCS7_SIGNED_DATA
:
389 rv
= SecCmsSignedDataDecodeAfterEnd(p7dcx
->content
.signedData
);
391 case SEC_OID_PKCS7_ENVELOPED_DATA
:
392 rv
= SecCmsEnvelopedDataDecodeAfterEnd(p7dcx
->content
.envelopedData
);
394 case SEC_OID_PKCS7_DIGESTED_DATA
:
395 rv
= SecCmsDigestedDataDecodeAfterEnd(p7dcx
->content
.digestedData
);
397 case SEC_OID_PKCS7_ENCRYPTED_DATA
:
398 rv
= SecCmsEncryptedDataDecodeAfterEnd(p7dcx
->content
.encryptedData
);
400 case SEC_OID_PKCS7_DATA
:
404 rv
= SECFailure
; /* we should not have got that far... */
411 * nss_cms_decoder_work_data - handle decoded data bytes.
413 * This function either decrypts the data if needed, and/or calculates digests
414 * on it, then either stores it or passes it on to the next level decoder.
417 nss_cms_decoder_work_data(SecCmsDecoderRef p7dcx
,
418 const unsigned char *data
, size_t len
,
421 SecCmsContentInfoRef cinfo
;
422 unsigned char *buf
= NULL
;
426 CSSM_DATA_PTR storage
;
429 * We should really have data to process, or we should be trying
430 * to finish/flush the last block. (This is an overly paranoid
431 * check since all callers are in this file and simple inspection
432 * proves they do it right. But it could find a bug in future
433 * modifications/development, that is why it is here.)
435 PORT_Assert ((data
!= NULL
&& len
) || final
);
437 if (!p7dcx
->content
.pointer
) // might be ExContent??
440 cinfo
= SecCmsContentGetContentInfo(p7dcx
->content
.pointer
, p7dcx
->type
);
442 if (cinfo
->ciphcx
!= NULL
) {
446 * XXX If we get an error, we do not want to do the digest or callback,
447 * but we want to keep decoding. Or maybe we want to stop decoding
448 * altogether if there is a callback, because obviously we are not
449 * sending the data back and they want to know that.
452 CSSM_SIZE outlen
= 0; /* length of decrypted data */
453 CSSM_SIZE buflen
; /* length available for decrypted data */
455 /* find out about the length of decrypted data */
456 buflen
= SecCmsCipherContextDecryptLength(cinfo
->ciphcx
, len
, final
);
459 * it might happen that we did not provide enough data for a full
460 * block (decryption unit), and that there is no output available
463 /* no output available, AND no input? */
464 if (buflen
== 0 && len
== 0)
465 goto loser
; /* bail out */
468 * have inner decoder: pass the data on (means inner content type is NOT data)
469 * no inner decoder: we have DATA in here: either call callback or store
472 /* there will be some output - need to make room for it */
473 /* allocate buffer from the heap */
474 buf
= (unsigned char *)PORT_Alloc(buflen
);
476 p7dcx
->error
= SEC_ERROR_NO_MEMORY
;
482 * decrypt incoming data
483 * buf can still be NULL here (and buflen == 0) here if we don't expect
484 * any output (see above), but we still need to call SecCmsCipherContextDecrypt to
485 * keep track of incoming data
487 rv
= SecCmsCipherContextDecrypt(cinfo
->ciphcx
, buf
, &outlen
, buflen
,
489 if (rv
!= SECSuccess
) {
490 p7dcx
->error
= PORT_GetError();
491 PORT_SetError(0); // Clean the thread error since we've returned the error
495 PORT_Assert (final
|| outlen
== buflen
);
497 /* swap decrypted data in */
503 goto done
; /* nothing more to do */
506 * Update the running digests with plaintext bytes (if we need to).
509 SecCmsDigestContextUpdate(cinfo
->digcx
, data
, len
);
511 /* at this point, we have the plain decoded & decrypted data */
512 /* which is either more encoded DER which we need to hand to the child decoder */
513 /* or data we need to hand back to our caller */
515 /* pass the content back to our caller or */
516 /* feed our freshly decrypted and decoded data into child decoder */
517 if (p7dcx
->cb
!= NULL
) {
518 (*p7dcx
->cb
)(p7dcx
->cb_arg
, (const char *)data
, len
);
523 switch(SecCmsContentInfoGetContentTypeTag(cinfo
)) {
526 case SEC_OID_PKCS7_DATA
:
528 /* store it in "inner" data item as well */
529 /* find the DATA item in the encapsulated cinfo and store it there */
530 storage
= cinfo
->content
.data
;
532 offset
= storage
->Length
;
534 /* check for potential overflow */
535 if (len
>= (size_t)(INT_MAX
- storage
->Length
)) {
536 p7dcx
->error
= SEC_ERROR_NO_MEMORY
;
540 if (storage
->Length
== 0) {
541 dest
= (unsigned char *)PORT_ArenaAlloc(p7dcx
->cmsg
->poolp
, len
);
543 dest
= (unsigned char *)PORT_ArenaGrow(p7dcx
->cmsg
->poolp
,
546 storage
->Length
+ len
);
549 p7dcx
->error
= SEC_ERROR_NO_MEMORY
;
553 storage
->Data
= dest
;
554 storage
->Length
+= len
;
557 PORT_Memcpy(storage
->Data
+ offset
, data
, len
);
567 * nss_cms_decoder_update_filter - process ASN.1 data
569 * once we have set up a filter in nss_cms_decoder_notify(),
570 * all data processed by the ASN.1 decoder is also passed through here.
571 * we pass the content bytes (as opposed to length and tag bytes) on to
572 * nss_cms_decoder_work_data().
575 nss_cms_decoder_update_filter (void *arg
, const char *data
, size_t len
,
576 int depth
, SEC_ASN1EncodingPart data_kind
)
578 SecCmsDecoderRef p7dcx
;
580 PORT_Assert (len
); /* paranoia */
584 p7dcx
= (SecCmsDecoderRef
)arg
;
586 p7dcx
->saw_contents
= PR_TRUE
;
588 /* pass on the content bytes only */
589 if (data_kind
== SEC_ASN1_Contents
)
590 nss_cms_decoder_work_data(p7dcx
, (const unsigned char *) data
, len
, PR_FALSE
);
594 * SecCmsDecoderCreate - set up decoding of a BER-encoded CMS message
597 SecCmsDecoderCreate(SecArenaPoolRef pool
,
598 SecCmsContentCallback cb
, void *cb_arg
,
599 PK11PasswordFunc pwfn
, void *pwfn_arg
,
600 SecCmsGetDecryptKeyCallback decrypt_key_cb
, void *decrypt_key_cb_arg
,
601 SecCmsDecoderRef
*outDecoder
)
603 SecCmsDecoderRef p7dcx
;
604 SecCmsMessageRef cmsg
;
607 /* Clear the thread error to clean up dirty threads */
610 cmsg
= SecCmsMessageCreate(pool
);
614 SecCmsMessageSetEncodingParams(cmsg
, pwfn
, pwfn_arg
, decrypt_key_cb
, decrypt_key_cb_arg
,
617 p7dcx
= (SecCmsDecoderRef
)PORT_ZAlloc(sizeof(struct SecCmsDecoderStr
));
619 SecCmsMessageDestroy(cmsg
);
623 p7dcx
->dcx
= SEC_ASN1DecoderStart(cmsg
->poolp
, cmsg
, SecCmsMessageTemplate
, NULL
, 0);
624 if (p7dcx
->dcx
== NULL
) {
626 SecCmsMessageDestroy(cmsg
);
630 SEC_ASN1DecoderSetNotifyProc (p7dcx
->dcx
, nss_cms_decoder_notify
, p7dcx
);
633 p7dcx
->type
= SEC_OID_UNKNOWN
;
636 p7dcx
->cb_arg
= cb_arg
;
642 result
= PORT_GetError();
643 PORT_SetError(0); // Clean the thread error since we've returned the error
648 * SecCmsDecoderUpdate - feed DER-encoded data to decoder
651 SecCmsDecoderUpdate(SecCmsDecoderRef p7dcx
, const void *buf
, CFIndex len
)
657 if (p7dcx
->dcx
!= NULL
&& p7dcx
->error
== 0) { /* if error is set already, don't bother */
658 if (SEC_ASN1DecoderUpdate (p7dcx
->dcx
, buf
, len
) != SECSuccess
) {
659 p7dcx
->error
= PORT_GetError();
660 PORT_Assert (p7dcx
->error
);
661 if (p7dcx
->error
== 0)
666 if (p7dcx
->error
== 0)
669 /* there has been a problem, let's finish the decoder */
670 if (p7dcx
->dcx
!= NULL
) {
671 /* @@@ Change this to SEC_ASN1DecoderAbort()? */
672 (void) SEC_ASN1DecoderFinish (p7dcx
->dcx
);
675 PORT_SetError (0); // Clean the thread error since we've returned the error
681 * SecCmsDecoderDestroy - stop decoding in case of error
684 SecCmsDecoderDestroy(SecCmsDecoderRef p7dcx
)
686 /* SecCmsMessageDestroy frees inner decoders and digests. */
688 SecCmsMessageDestroy(p7dcx
->cmsg
);
691 (void)SEC_ASN1DecoderFinish(p7dcx
->dcx
);
693 /* Clear out references */
696 p7dcx
->childp7dcx
= NULL
;
701 * SecCmsDecoderFinish - mark the end of inner content and finish decoding
704 SecCmsDecoderFinish(SecCmsDecoderRef p7dcx
, SecCmsMessageRef
*outMessage
)
706 SecCmsMessageRef cmsg
;
711 if (p7dcx
->dcx
== NULL
|| SEC_ASN1DecoderFinish(p7dcx
->dcx
) != SECSuccess
||
712 nss_cms_after_end(p7dcx
) != SECSuccess
)
715 SecCmsMessageDestroy(cmsg
); /* needs to get rid of pool if it's ours */
717 result
= PORT_GetError();
725 /* Clear out references */
728 p7dcx
->childp7dcx
= NULL
;
730 PORT_SetError(0); // Clean the thread error since we've returned the error
735 SecCmsMessageDecode(const CSSM_DATA
*encodedMessage
,
736 SecCmsContentCallback cb
, void *cb_arg
,
737 PK11PasswordFunc pwfn
, void *pwfn_arg
,
738 SecCmsGetDecryptKeyCallback decrypt_key_cb
, void *decrypt_key_cb_arg
,
739 SecCmsMessageRef
*outMessage
)
742 SecCmsDecoderRef decoder
;
744 result
= SecCmsDecoderCreate(NULL
, cb
, cb_arg
, pwfn
, pwfn_arg
, decrypt_key_cb
, decrypt_key_cb_arg
, &decoder
);
747 result
= SecCmsDecoderUpdate(decoder
, encodedMessage
->Data
, encodedMessage
->Length
);
749 SecCmsDecoderDestroy(decoder
);
753 result
= SecCmsDecoderFinish(decoder
, outMessage
);