security/nss/lib/smime/cmsencode.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial