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