| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * CMS encoding. |
| */ |
| |
| #include "cmslocal.h" |
| |
| #include "cert.h" |
| #include "keyhi.h" |
| #include "secasn1.h" |
| #include "secoid.h" |
| #include "secitem.h" |
| #include "pk11func.h" |
| #include "secerr.h" |
| |
| struct nss_cms_encoder_output { |
| NSSCMSContentCallback outputfn; |
| void *outputarg; |
| PLArenaPool *destpoolp; |
| SECItem *dest; |
| }; |
| |
| struct NSSCMSEncoderContextStr { |
| SEC_ASN1EncoderContext *ecx; /* ASN.1 encoder context */ |
| PRBool ecxupdated; /* true if data was handed in */ |
| NSSCMSMessage *cmsg; /* pointer to the root message */ |
| SECOidTag type; /* type tag of the current content */ |
| NSSCMSContent content; /* pointer to current content */ |
| struct nss_cms_encoder_output output; /* output function */ |
| int error; /* error code */ |
| NSSCMSEncoderContext *childp7ecx; /* link to child encoder context */ |
| }; |
| |
| static SECStatus nss_cms_before_data(NSSCMSEncoderContext *p7ecx); |
| static SECStatus nss_cms_after_data(NSSCMSEncoderContext *p7ecx); |
| static SECStatus nss_cms_encoder_update(NSSCMSEncoderContext *p7ecx, |
| const char *data, unsigned long len); |
| static SECStatus nss_cms_encoder_work_data(NSSCMSEncoderContext *p7ecx, SECItem *dest, |
| const unsigned char *data, unsigned long len, |
| PRBool final, PRBool innermost); |
| |
| extern const SEC_ASN1Template NSSCMSMessageTemplate[]; |
| |
| /* |
| * The little output function that the ASN.1 encoder calls to hand |
| * us bytes which we in turn hand back to our caller (via the callback |
| * they gave us). |
| */ |
| static void |
| nss_cms_encoder_out(void *arg, const char *buf, unsigned long len, |
| int depth, SEC_ASN1EncodingPart data_kind) |
| { |
| struct nss_cms_encoder_output *output = (struct nss_cms_encoder_output *)arg; |
| unsigned char *dest; |
| unsigned long offset; |
| |
| #ifdef CMSDEBUG |
| int i; |
| const char *data_name = "unknown"; |
| |
| switch (data_kind) { |
| case SEC_ASN1_Identifier: |
| data_name = "identifier"; |
| break; |
| case SEC_ASN1_Length: |
| data_name = "length"; |
| break; |
| case SEC_ASN1_Contents: |
| data_name = "contents"; |
| break; |
| case SEC_ASN1_EndOfContents: |
| data_name = "end-of-contents"; |
| break; |
| } |
| fprintf(stderr, "kind = %s, depth = %d, len = %d\n", data_name, depth, len); |
| for (i = 0; i < len; i++) { |
| fprintf(stderr, " %02x%s", (unsigned int)buf[i] & 0xff, ((i % 16) == 15) ? "\n" : ""); |
| } |
| if ((i % 16) != 0) |
| fprintf(stderr, "\n"); |
| #endif |
| |
| if (output->outputfn != NULL) |
| /* call output callback with DER data */ |
| output->outputfn(output->outputarg, buf, len); |
| |
| if (output->dest != NULL) { |
| /* store DER data in SECItem */ |
| offset = output->dest->len; |
| if (offset == 0) { |
| dest = (unsigned char *)PORT_ArenaAlloc(output->destpoolp, len); |
| } else { |
| dest = (unsigned char *)PORT_ArenaGrow(output->destpoolp, |
| output->dest->data, |
| output->dest->len, |
| output->dest->len + len); |
| } |
| if (dest == NULL) |
| /* oops */ |
| return; |
| |
| output->dest->data = dest; |
| output->dest->len += len; |
| |
| /* copy it in */ |
| if (len) { |
| PORT_Memcpy(output->dest->data + offset, buf, len); |
| } |
| } |
| } |
| |
| /* |
| * nss_cms_encoder_notify - ASN.1 encoder callback |
| * |
| * this function is called by the ASN.1 encoder before and after the encoding of |
| * every object. here, it is used to keep track of data structures, set up |
| * encryption and/or digesting and possibly set up child encoders. |
| */ |
| static void |
| nss_cms_encoder_notify(void *arg, PRBool before, void *dest, int depth) |
| { |
| NSSCMSEncoderContext *p7ecx; |
| NSSCMSContentInfo *rootcinfo, *cinfo; |
| PRBool after = !before; |
| SECOidTag childtype; |
| SECItem *item; |
| |
| p7ecx = (NSSCMSEncoderContext *)arg; |
| PORT_Assert(p7ecx != NULL); |
| |
| rootcinfo = &(p7ecx->cmsg->contentInfo); |
| |
| #ifdef CMSDEBUG |
| fprintf(stderr, "%6.6s, dest = 0x%08x, depth = %d\n", before ? "before" : "after", |
| dest, depth); |
| #endif |
| |
| /* |
| * Watch for the content field, at which point we want to instruct |
| * the ASN.1 encoder to start taking bytes from the buffer. |
| */ |
| if (NSS_CMSType_IsData(p7ecx->type)) { |
| cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
| if (before && dest == &(cinfo->rawContent)) { |
| /* just set up encoder to grab from user - no encryption or digesting */ |
| if ((item = cinfo->content.data) != NULL) |
| (void)nss_cms_encoder_work_data(p7ecx, NULL, item->data, |
| item->len, PR_TRUE, PR_TRUE); |
| else |
| SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); |
| SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); /* no need to get notified anymore */ |
| } |
| } else if (NSS_CMSType_IsWrapper(p7ecx->type)) { |
| /* when we know what the content is, we encode happily until we reach the inner content */ |
| cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
| childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
| |
| if (after && dest == &(cinfo->contentType)) { |
| /* we're right before encoding the data (if we have some or not) */ |
| /* (for encrypted data, we're right before the contentEncAlg which may change */ |
| /* in nss_cms_before_data because of IV calculation when setting up encryption) */ |
| if (nss_cms_before_data(p7ecx) != SECSuccess) |
| p7ecx->error = PORT_GetError(); |
| } |
| if (before && dest == &(cinfo->rawContent)) { |
| if (p7ecx->childp7ecx == NULL) { |
| if ((NSS_CMSType_IsData(childtype) && (item = cinfo->content.data) != NULL)) { |
| /* we are the innermost non-data and we have data - feed it in */ |
| (void)nss_cms_encoder_work_data(p7ecx, NULL, item->data, |
| item->len, PR_TRUE, PR_TRUE); |
| } else { |
| /* else we'll have to get data from user */ |
| SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); |
| } |
| } else { |
| /* if we have a nested encoder, wait for its data */ |
| SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); |
| } |
| } |
| if (after && dest == &(cinfo->rawContent)) { |
| if (nss_cms_after_data(p7ecx) != SECSuccess) |
| p7ecx->error = PORT_GetError(); |
| SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); /* no need to get notified anymore */ |
| } |
| } else { |
| /* we're still in the root message */ |
| if (after && dest == &(rootcinfo->contentType)) { |
| /* got the content type OID now - so find out the type tag */ |
| p7ecx->type = NSS_CMSContentInfo_GetContentTypeTag(rootcinfo); |
| /* set up a pointer to our current content */ |
| p7ecx->content = rootcinfo->content; |
| } |
| } |
| } |
| |
| /* |
| * nss_cms_before_data - setup the current encoder to receive data |
| */ |
| static SECStatus |
| nss_cms_before_data(NSSCMSEncoderContext *p7ecx) |
| { |
| SECStatus rv; |
| SECOidTag childtype; |
| NSSCMSContentInfo *cinfo; |
| NSSCMSEncoderContext *childp7ecx; |
| const SEC_ASN1Template *template; |
| |
| /* call _Encode_BeforeData handlers */ |
| switch (p7ecx->type) { |
| case SEC_OID_PKCS7_SIGNED_DATA: |
| /* we're encoding a signedData, so set up the digests */ |
| rv = NSS_CMSSignedData_Encode_BeforeData(p7ecx->content.signedData); |
| break; |
| case SEC_OID_PKCS7_DIGESTED_DATA: |
| /* we're encoding a digestedData, so set up the digest */ |
| rv = NSS_CMSDigestedData_Encode_BeforeData(p7ecx->content.digestedData); |
| break; |
| case SEC_OID_PKCS7_ENVELOPED_DATA: |
| rv = NSS_CMSEnvelopedData_Encode_BeforeData(p7ecx->content.envelopedData); |
| break; |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: |
| rv = NSS_CMSEncryptedData_Encode_BeforeData(p7ecx->content.encryptedData); |
| break; |
| default: |
| if (NSS_CMSType_IsWrapper(p7ecx->type)) { |
| rv = NSS_CMSGenericWrapperData_Encode_BeforeData(p7ecx->type, |
| p7ecx->content.genericData); |
| } else { |
| rv = SECFailure; |
| } |
| } |
| if (rv != SECSuccess) |
| return SECFailure; |
| |
| /* ok, now we have a pointer to cinfo */ |
| /* find out what kind of data is encapsulated */ |
| |
| cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
| childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
| |
| if (NSS_CMSType_IsWrapper(childtype)) { |
| /* in these cases, we need to set up a child encoder! */ |
| /* create new encoder context */ |
| childp7ecx = PORT_ZAlloc(sizeof(NSSCMSEncoderContext)); |
| if (childp7ecx == NULL) |
| return SECFailure; |
| |
| /* the CHILD encoder needs to hand its encoded data to the CURRENT encoder |
| * (which will encrypt and/or digest it) |
| * this needs to route back into our update function |
| * which finds the lowest encoding context & encrypts and computes digests */ |
| childp7ecx->type = childtype; |
| childp7ecx->content = cinfo->content; |
| /* use the non-recursive update function here, of course */ |
| childp7ecx->output.outputfn = (NSSCMSContentCallback)nss_cms_encoder_update; |
| childp7ecx->output.outputarg = p7ecx; |
| childp7ecx->output.destpoolp = NULL; |
| childp7ecx->output.dest = NULL; |
| childp7ecx->cmsg = p7ecx->cmsg; |
| childp7ecx->ecxupdated = PR_FALSE; |
| childp7ecx->childp7ecx = NULL; |
| |
| template = NSS_CMSUtil_GetTemplateByTypeTag(childtype); |
| if (template == NULL) |
| goto loser; /* cannot happen */ |
| |
| /* now initialize the data for encoding the first third */ |
| switch (childp7ecx->type) { |
| case SEC_OID_PKCS7_SIGNED_DATA: |
| rv = NSS_CMSSignedData_Encode_BeforeStart(cinfo->content.signedData); |
| break; |
| case SEC_OID_PKCS7_ENVELOPED_DATA: |
| rv = NSS_CMSEnvelopedData_Encode_BeforeStart(cinfo->content.envelopedData); |
| break; |
| case SEC_OID_PKCS7_DIGESTED_DATA: |
| rv = NSS_CMSDigestedData_Encode_BeforeStart(cinfo->content.digestedData); |
| break; |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: |
| rv = NSS_CMSEncryptedData_Encode_BeforeStart(cinfo->content.encryptedData); |
| break; |
| default: |
| rv = NSS_CMSGenericWrapperData_Encode_BeforeStart(childp7ecx->type, |
| cinfo->content.genericData); |
| break; |
| } |
| if (rv != SECSuccess) |
| goto loser; |
| |
| /* |
| * Initialize the BER encoder. |
| */ |
| childp7ecx->ecx = SEC_ASN1EncoderStart(cinfo->content.pointer, template, |
| nss_cms_encoder_out, &(childp7ecx->output)); |
| if (childp7ecx->ecx == NULL) |
| goto loser; |
| |
| /* |
| * Indicate that we are streaming. We will be streaming until we |
| * get past the contents bytes. |
| */ |
| if (!cinfo->privateInfo || !cinfo->privateInfo->dontStream) |
| SEC_ASN1EncoderSetStreaming(childp7ecx->ecx); |
| |
| /* |
| * The notify function will watch for the contents field. |
| */ |
| p7ecx->childp7ecx = childp7ecx; |
| SEC_ASN1EncoderSetNotifyProc(childp7ecx->ecx, nss_cms_encoder_notify, |
| childp7ecx); |
| |
| /* please note that we are NOT calling SEC_ASN1EncoderUpdate here to kick off the */ |
| /* encoding process - we'll do that from the update function instead */ |
| /* otherwise we'd be encoding data from a call of the notify function of the */ |
| /* parent encoder (which would not work) */ |
| |
| } else if (NSS_CMSType_IsData(childtype)) { |
| p7ecx->childp7ecx = NULL; |
| } else { |
| /* we do not know this type */ |
| p7ecx->error = SEC_ERROR_BAD_DER; |
| } |
| |
| return SECSuccess; |
| |
| loser: |
| if (childp7ecx) { |
| if (childp7ecx->ecx) |
| SEC_ASN1EncoderFinish(childp7ecx->ecx); |
| PORT_Free(childp7ecx); |
| p7ecx->childp7ecx = NULL; |
| } |
| return SECFailure; |
| } |
| |
| static SECStatus |
| nss_cms_after_data(NSSCMSEncoderContext *p7ecx) |
| { |
| SECStatus rv = SECFailure; |
| |
| switch (p7ecx->type) { |
| case SEC_OID_PKCS7_SIGNED_DATA: |
| /* this will finish the digests and sign */ |
| rv = NSS_CMSSignedData_Encode_AfterData(p7ecx->content.signedData); |
| break; |
| case SEC_OID_PKCS7_ENVELOPED_DATA: |
| rv = NSS_CMSEnvelopedData_Encode_AfterData(p7ecx->content.envelopedData); |
| break; |
| case SEC_OID_PKCS7_DIGESTED_DATA: |
| rv = NSS_CMSDigestedData_Encode_AfterData(p7ecx->content.digestedData); |
| break; |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: |
| rv = NSS_CMSEncryptedData_Encode_AfterData(p7ecx->content.encryptedData); |
| break; |
| default: |
| if (NSS_CMSType_IsWrapper(p7ecx->type)) { |
| rv = NSS_CMSGenericWrapperData_Encode_AfterData(p7ecx->type, |
| p7ecx->content.genericData); |
| } else { |
| rv = SECFailure; |
| } |
| break; |
| } |
| return rv; |
| } |
| |
| /* |
| * nss_cms_encoder_work_data - process incoming data |
| * |
| * (from the user or the next encoding layer) |
| * Here, we need to digest and/or encrypt, then pass it on |
| */ |
| static SECStatus |
| nss_cms_encoder_work_data(NSSCMSEncoderContext *p7ecx, SECItem *dest, |
| const unsigned char *data, unsigned long len, |
| PRBool final, PRBool innermost) |
| { |
| unsigned char *buf = NULL; |
| SECStatus rv; |
| NSSCMSContentInfo *cinfo; |
| |
| rv = SECSuccess; /* may as well be optimistic */ |
| |
| /* |
| * We should really have data to process, or we should be trying |
| * to finish/flush the last block. (This is an overly paranoid |
| * check since all callers are in this file and simple inspection |
| * proves they do it right. But it could find a bug in future |
| * modifications/development, that is why it is here.) |
| */ |
| PORT_Assert((data != NULL && len) || final); |
| |
| /* we got data (either from the caller, or from a lower level encoder) */ |
| cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
| if (!cinfo) { |
| /* The original programmer didn't expect this to happen */ |
| p7ecx->error = SEC_ERROR_LIBRARY_FAILURE; |
| return SECFailure; |
| } |
| |
| /* Update the running digest. */ |
| if (len && cinfo->privateInfo && cinfo->privateInfo->digcx != NULL) |
| NSS_CMSDigestContext_Update(cinfo->privateInfo->digcx, data, len); |
| |
| /* Encrypt this chunk. */ |
| if (cinfo->privateInfo && cinfo->privateInfo->ciphcx != NULL) { |
| unsigned int inlen; /* length of data being encrypted */ |
| unsigned int outlen; /* length of encrypted data */ |
| unsigned int buflen; /* length available for encrypted data */ |
| |
| inlen = len; |
| buflen = NSS_CMSCipherContext_EncryptLength(cinfo->privateInfo->ciphcx, |
| inlen, final); |
| if (buflen == 0) { |
| /* |
| * No output is expected, but the input data may be buffered |
| * so we still have to call Encrypt. |
| */ |
| rv = NSS_CMSCipherContext_Encrypt(cinfo->privateInfo->ciphcx, NULL, NULL, 0, |
| data, inlen, final); |
| if (final) { |
| len = 0; |
| goto done; |
| } |
| return rv; |
| } |
| |
| if (dest != NULL) |
| buf = (unsigned char *)PORT_ArenaAlloc(p7ecx->cmsg->poolp, buflen); |
| else |
| buf = (unsigned char *)PORT_Alloc(buflen); |
| |
| if (buf == NULL) { |
| rv = SECFailure; |
| } else { |
| rv = NSS_CMSCipherContext_Encrypt(cinfo->privateInfo->ciphcx, buf, |
| &outlen, buflen, |
| data, inlen, final); |
| data = buf; |
| len = outlen; |
| } |
| if (rv != SECSuccess) |
| /* encryption or malloc failed? */ |
| return rv; |
| } |
| |
| /* |
| * at this point (data,len) has everything we'd like to give to the CURRENT encoder |
| * (which will encode it, then hand it back to the user or the parent encoder) |
| * We don't encode the data if we're innermost and we're told not to include the data |
| */ |
| if (p7ecx->ecx != NULL && len && |
| (!innermost || cinfo->rawContent != cinfo->content.pointer)) |
| rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, (const char *)data, len); |
| |
| done: |
| |
| if (cinfo->privateInfo && cinfo->privateInfo->ciphcx != NULL) { |
| if (dest != NULL) { |
| dest->data = buf; |
| dest->len = len; |
| } else if (buf != NULL) { |
| PORT_Free(buf); |
| } |
| } |
| return rv; |
| } |
| |
| /* |
| * nss_cms_encoder_update - deliver encoded data to the next higher level |
| * |
| * no recursion here because we REALLY want to end up at the next higher encoder! |
| */ |
| static SECStatus |
| nss_cms_encoder_update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len) |
| { |
| /* XXX Error handling needs help. Return what? Do "Finish" on failure? */ |
| return nss_cms_encoder_work_data(p7ecx, NULL, (const unsigned char *)data, |
| len, PR_FALSE, PR_FALSE); |
| } |
| |
| /* |
| * NSS_CMSEncoder_Start - set up encoding of a CMS message |
| * |
| * "cmsg" - message to encode |
| * "outputfn", "outputarg" - callback function for delivery of DER-encoded output |
| * will not be called if NULL. |
| * "dest" - if non-NULL, pointer to SECItem that will hold the DER-encoded output |
| * "destpoolp" - pool to allocate DER-encoded output in |
| * "pwfn", pwfn_arg" - callback function for getting token password |
| * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData |
| * "detached_digestalgs", "detached_digests" - digests from detached content |
| */ |
| NSSCMSEncoderContext * |
| NSS_CMSEncoder_Start(NSSCMSMessage *cmsg, |
| NSSCMSContentCallback outputfn, void *outputarg, |
| SECItem *dest, PLArenaPool *destpoolp, |
| PK11PasswordFunc pwfn, void *pwfn_arg, |
| NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg, |
| SECAlgorithmID **detached_digestalgs, SECItem **detached_digests) |
| { |
| NSSCMSEncoderContext *p7ecx; |
| SECStatus rv; |
| NSSCMSContentInfo *cinfo; |
| SECOidTag tag; |
| |
| NSS_CMSMessage_SetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg, |
| detached_digestalgs, detached_digests); |
| |
| p7ecx = (NSSCMSEncoderContext *)PORT_ZAlloc(sizeof(NSSCMSEncoderContext)); |
| if (p7ecx == NULL) { |
| PORT_SetError(SEC_ERROR_NO_MEMORY); |
| return NULL; |
| } |
| |
| p7ecx->cmsg = cmsg; |
| p7ecx->output.outputfn = outputfn; |
| p7ecx->output.outputarg = outputarg; |
| p7ecx->output.dest = dest; |
| p7ecx->output.destpoolp = destpoolp; |
| p7ecx->type = SEC_OID_UNKNOWN; |
| |
| cinfo = NSS_CMSMessage_GetContentInfo(cmsg); |
| |
| tag = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
| switch (tag) { |
| case SEC_OID_PKCS7_SIGNED_DATA: |
| rv = NSS_CMSSignedData_Encode_BeforeStart(cinfo->content.signedData); |
| break; |
| case SEC_OID_PKCS7_ENVELOPED_DATA: |
| rv = NSS_CMSEnvelopedData_Encode_BeforeStart(cinfo->content.envelopedData); |
| break; |
| case SEC_OID_PKCS7_DIGESTED_DATA: |
| rv = NSS_CMSDigestedData_Encode_BeforeStart(cinfo->content.digestedData); |
| break; |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: |
| rv = NSS_CMSEncryptedData_Encode_BeforeStart(cinfo->content.encryptedData); |
| break; |
| default: |
| if (NSS_CMSType_IsWrapper(tag)) { |
| rv = NSS_CMSGenericWrapperData_Encode_BeforeStart(tag, |
| p7ecx->content.genericData); |
| } else { |
| rv = SECFailure; |
| } |
| break; |
| } |
| if (rv != SECSuccess) { |
| PORT_Free(p7ecx); |
| return NULL; |
| } |
| |
| /* Initialize the BER encoder. |
| * Note that this will not encode anything until the first call to SEC_ASN1EncoderUpdate */ |
| p7ecx->ecx = SEC_ASN1EncoderStart(cmsg, NSSCMSMessageTemplate, |
| nss_cms_encoder_out, &(p7ecx->output)); |
| if (p7ecx->ecx == NULL) { |
| PORT_Free(p7ecx); |
| return NULL; |
| } |
| p7ecx->ecxupdated = PR_FALSE; |
| |
| /* |
| * Indicate that we are streaming. We will be streaming until we |
| * get past the contents bytes. |
| */ |
| if (!cinfo->privateInfo || !cinfo->privateInfo->dontStream) |
| SEC_ASN1EncoderSetStreaming(p7ecx->ecx); |
| |
| /* |
| * The notify function will watch for the contents field. |
| */ |
| SEC_ASN1EncoderSetNotifyProc(p7ecx->ecx, nss_cms_encoder_notify, p7ecx); |
| |
| /* this will kick off the encoding process & encode everything up to the content bytes, |
| * at which point the notify function sets streaming mode (and possibly creates |
| * a child encoder). */ |
| p7ecx->ecxupdated = PR_TRUE; |
| if (SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0) != SECSuccess) { |
| PORT_Free(p7ecx); |
| return NULL; |
| } |
| |
| return p7ecx; |
| } |
| |
| /* |
| * NSS_CMSEncoder_Update - take content data delivery from the user |
| * |
| * "p7ecx" - encoder context |
| * "data" - content data |
| * "len" - length of content data |
| * |
| * need to find the lowest level (and call SEC_ASN1EncoderUpdate on the way down), |
| * then hand the data to the work_data fn |
| */ |
| SECStatus |
| NSS_CMSEncoder_Update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len) |
| { |
| SECStatus rv; |
| NSSCMSContentInfo *cinfo; |
| SECOidTag childtype; |
| |
| if (p7ecx->error) |
| return SECFailure; |
| |
| /* hand data to the innermost decoder */ |
| if (p7ecx->childp7ecx) { |
| /* tell the child to start encoding, up to its first data byte, if it |
| * hasn't started yet */ |
| if (!p7ecx->childp7ecx->ecxupdated) { |
| p7ecx->childp7ecx->ecxupdated = PR_TRUE; |
| if (SEC_ASN1EncoderUpdate(p7ecx->childp7ecx->ecx, NULL, 0) != SECSuccess) |
| return SECFailure; |
| } |
| /* recursion here */ |
| rv = NSS_CMSEncoder_Update(p7ecx->childp7ecx, data, len); |
| } else { |
| /* we are at innermost decoder */ |
| /* find out about our inner content type - must be data */ |
| cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
| if (!cinfo) { |
| /* The original programmer didn't expect this to happen */ |
| p7ecx->error = SEC_ERROR_LIBRARY_FAILURE; |
| return SECFailure; |
| } |
| |
| childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); |
| if (!NSS_CMSType_IsData(childtype)) |
| return SECFailure; |
| /* and we must not have preset data */ |
| if (cinfo->content.data != NULL) |
| return SECFailure; |
| |
| /* hand it the data so it can encode it (let DER trickle up the chain) */ |
| rv = nss_cms_encoder_work_data(p7ecx, NULL, (const unsigned char *)data, |
| len, PR_FALSE, PR_TRUE); |
| } |
| return rv; |
| } |
| |
| /* |
| * NSS_CMSEncoder_Cancel - stop all encoding |
| * |
| * we need to walk down the chain of encoders and the finish them from the innermost out |
| */ |
| SECStatus |
| NSS_CMSEncoder_Cancel(NSSCMSEncoderContext *p7ecx) |
| { |
| SECStatus rv = SECFailure; |
| |
| /* XXX do this right! */ |
| |
| /* |
| * Finish any inner decoders before us so that all the encoded data is flushed |
| * This basically finishes all the decoders from the innermost to the outermost. |
| * Finishing an inner decoder may result in data being updated to the outer decoder |
| * while we are already in NSS_CMSEncoder_Finish, but that's allright. |
| */ |
| if (p7ecx->childp7ecx) { |
| rv = NSS_CMSEncoder_Cancel(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ |
| /* remember rv for now */ |
| #ifdef CMSDEBUG |
| if (rv != SECSuccess) { |
| fprintf(stderr, "Fail to cancel inner encoder\n"); |
| } |
| #endif |
| } |
| |
| /* |
| * On the way back up, there will be no more data (if we had an |
| * inner encoder, it is done now!) |
| * Flush out any remaining data and/or finish digests. |
| */ |
| rv = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)); |
| if (rv != SECSuccess) |
| goto loser; |
| |
| p7ecx->childp7ecx = NULL; |
| |
| /* kick the encoder back into working mode again. |
| * We turn off streaming stuff (which will cause the encoder to continue |
| * encoding happily, now that we have all the data (like digests) ready for it). |
| */ |
| SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); |
| SEC_ASN1EncoderClearStreaming(p7ecx->ecx); |
| |
| /* now that TakeFromBuf is off, this will kick this encoder to finish encoding */ |
| rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); |
| |
| loser: |
| SEC_ASN1EncoderFinish(p7ecx->ecx); |
| PORT_Free(p7ecx); |
| return rv; |
| } |
| |
| /* |
| * NSS_CMSEncoder_Finish - signal the end of data |
| * |
| * we need to walk down the chain of encoders and the finish them from the innermost out |
| */ |
| SECStatus |
| NSS_CMSEncoder_Finish(NSSCMSEncoderContext *p7ecx) |
| { |
| SECStatus rv = SECFailure; |
| NSSCMSContentInfo *cinfo; |
| |
| /* |
| * Finish any inner decoders before us so that all the encoded data is flushed |
| * This basically finishes all the decoders from the innermost to the outermost. |
| * Finishing an inner decoder may result in data being updated to the outer decoder |
| * while we are already in NSS_CMSEncoder_Finish, but that's allright. |
| */ |
| if (p7ecx->childp7ecx) { |
| /* tell the child to start encoding, up to its first data byte, if it |
| * hasn't yet */ |
| if (!p7ecx->childp7ecx->ecxupdated) { |
| p7ecx->childp7ecx->ecxupdated = PR_TRUE; |
| rv = SEC_ASN1EncoderUpdate(p7ecx->childp7ecx->ecx, NULL, 0); |
| if (rv != SECSuccess) { |
| NSS_CMSEncoder_Finish(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ |
| goto loser; |
| } |
| } |
| rv = NSS_CMSEncoder_Finish(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ |
| if (rv != SECSuccess) |
| goto loser; |
| } |
| |
| /* |
| * On the way back up, there will be no more data (if we had an |
| * inner encoder, it is done now!) |
| * Flush out any remaining data and/or finish digests. |
| */ |
| rv = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)); |
| if (rv != SECSuccess) |
| goto loser; |
| |
| p7ecx->childp7ecx = NULL; |
| |
| cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); |
| if (!cinfo) { |
| /* The original programmer didn't expect this to happen */ |
| p7ecx->error = SEC_ERROR_LIBRARY_FAILURE; |
| rv = SECFailure; |
| goto loser; |
| } |
| SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); |
| SEC_ASN1EncoderClearStreaming(p7ecx->ecx); |
| /* now that TakeFromBuf is off, this will kick this encoder to finish encoding */ |
| rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); |
| |
| if (p7ecx->error) |
| rv = SECFailure; |
| |
| loser: |
| SEC_ASN1EncoderFinish(p7ecx->ecx); |
| PORT_Free(p7ecx); |
| return rv; |
| } |