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 "SecAsn1Item.h"
47
48#include <security_asn1/secasn1.h>
49#include <security_asn1/secerr.h>
50#include <security_asn1/secport.h>
51
52#include <Security/SecBase.h>
53
54#include <limits.h>
55
56struct nss_cms_encoder_output {
57    SecCmsContentCallback outputfn;
58    void *outputarg;
59    CFMutableDataRef berData;
60};
61
62struct 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
73static OSStatus nss_cms_before_data(SecCmsEncoderRef p7ecx);
74static OSStatus nss_cms_after_data(SecCmsEncoderRef p7ecx);
75static void nss_cms_encoder_update(void *arg, const char *data, size_t len);
76static OSStatus nss_cms_encoder_work_data(SecCmsEncoderRef p7ecx, SecAsn1Item * dest,
77			     const unsigned char *data, size_t len,
78			     Boolean final, Boolean innermost);
79
80extern 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 */
87static void
88nss_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;
92
93#ifdef CMSDEBUG
94    size_t i;
95
96    fprintf(stderr, "kind = %d, depth = %d, len = %d\n", data_kind, depth, len);
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
108    if (output->berData != NULL) {
109	/* store DER data in output->dest */
110	CFDataAppendBytes(output->berData, (const UInt8 *)buf, len);
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 */
121static void
122nss_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;
129    SecAsn1Item * item;
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
138    fprintf(stderr, "%6.6s, dest = 0x%08x, depth = %d\n", before ? "before" : "after", dest, depth);
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:
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)) {
185	    if (childtype == SEC_OID_PKCS7_DATA && (item = cinfo->content.data) != NULL)
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 */
204static OSStatus
205nss_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 */
265	childp7ecx->output.outputfn = nss_cms_encoder_update;
266	childp7ecx->output.outputarg = p7ecx;
267	childp7ecx->output.berData = NULL;
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:
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:
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
344loser:
345    if (childp7ecx) {
346	if (childp7ecx->ecx)
347	    SEC_ASN1EncoderFinish(childp7ecx->ecx);
348	PORT_Free(childp7ecx);
349    }
350    return SECFailure;
351}
352
353static OSStatus
354nss_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:
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
387 *
388 */
389static OSStatus
390nss_cms_encoder_work_data(SecCmsEncoderRef p7ecx, SecAsn1Item * dest,
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);
408    PORT_Assert (len < UINT_MAX); /* overflow check for later cast */
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) {
419	unsigned int inlen;	/* length of data being encrypted */
420	unsigned int outlen;	/* length of encrypted data */
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;
427
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
470done:
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 */
488static void
489nss_cms_encoder_update(void *arg, const char *data, size_t len)
490{
491    /* XXX Error handling needs help.  Return what?  Do "Finish" on failure? */
492    SecCmsEncoderRef p7ecx = (SecCmsEncoderRef)arg;
493
494    (void)nss_cms_encoder_work_data (p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_FALSE);
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.
503 * "dest" - if non-NULL, pointer to SecAsn1Item that will hold the DER-encoded output
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 */
509OSStatus
510SecCmsEncoderCreate(SecCmsMessageRef cmsg,
511                    SecCmsContentCallback outputfn, void *outputarg,
512                    CFMutableDataRef outBer,
513                    PK11PasswordFunc pwfn, void *pwfn_arg,
514                    SecCmsGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg,
515                    SecCmsEncoderRef *outEncoder)
516{
517    SecCmsEncoderRef p7ecx;
518    OSStatus result;
519    SecCmsContentInfoRef cinfo;
520
521    SecCmsMessageSetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg);
522
523    p7ecx = (SecCmsEncoderRef)PORT_ZAlloc(sizeof(struct SecCmsEncoderStr));
524    if (p7ecx == NULL) {
525        result = errSecAllocate;
526        goto loser;
527    }
528
529    p7ecx->cmsg = cmsg;
530    p7ecx->output.outputfn = outputfn;
531    p7ecx->output.outputarg = outputarg;
532    p7ecx->output.berData = outBer;
533
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. */
553	result = errSecParam;
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;
591loser:
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 */
605OSStatus
606SecCmsEncoderUpdate(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);
624	if (childtype != SEC_OID_PKCS7_DATA)
625	    return errSecParam; /* @@@ Maybe come up with a better error? */
626	/* and we must not have preset data */
627	if (cinfo->content.data != NULL)
628	    return errSecParam; /* @@@ Maybe come up with a better error? */
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 */
643void
644SecCmsEncoderDestroy(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
677loser:
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 */
687OSStatus
688SecCmsEncoderFinish(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);
722    if (childtype == SEC_OID_PKCS7_DATA && cinfo->content.data == NULL) {
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
735loser:
736    SEC_ASN1EncoderFinish(p7ecx->ecx);
737    PORT_Free (p7ecx);
738    return result;
739}
740
741OSStatus
742SecCmsMessageEncode(SecCmsMessageRef cmsg, const SecAsn1Item *input,
743                    CFMutableDataRef outBer)
744{
745    SecCmsEncoderRef encoder = NULL;
746    OSStatus result;
747
748    if (!cmsg || !outBer) {
749        result = errSecParam;
750        goto loser;
751    }
752
753    result = SecCmsEncoderCreate(cmsg, 0, 0, outBer, 0, 0, 0, 0, &encoder);
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
766loser:
767    return result;
768}
769