#include <Security/SecCmsEncoder.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsDigestContext.h>
#include <Security/SecCmsMessage.h>
#include "cmslocal.h"
#include "secoid.h"
#include "secitem.h"
#include <security_asn1/secasn1.h>
#include <security_asn1/secerr.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
struct nss_cms_encoder_output {
SecCmsContentCallback outputfn;
void *outputarg;
PLArenaPool *destpoolp;
CSSM_DATA_PTR dest;
};
struct SecCmsEncoderStr {
SEC_ASN1EncoderContext * ecx;
Boolean ecxupdated;
SecCmsMessageRef cmsg;
SECOidTag type;
SecCmsContent content;
struct nss_cms_encoder_output output;
int error;
SecCmsEncoderRef childp7ecx;
};
static OSStatus nss_cms_before_data(SecCmsEncoderRef p7ecx);
static OSStatus nss_cms_after_data(SecCmsEncoderRef p7ecx);
static OSStatus nss_cms_encoder_update(SecCmsEncoderRef p7ecx, const char *data, size_t len);
static OSStatus nss_cms_encoder_work_data(SecCmsEncoderRef p7ecx, CSSM_DATA_PTR dest,
const unsigned char *data, size_t len,
Boolean final, Boolean innermost);
extern const SecAsn1Template SecCmsMessageTemplate[];
static void
nss_cms_encoder_out(void *arg, const char *buf, size_t len,
int depth, SEC_ASN1EncodingPart data_kind)
{
struct nss_cms_encoder_output *output = (struct nss_cms_encoder_output *)arg;
unsigned char *dest;
CSSM_SIZE offset;
#ifdef CMSDEBUG
int i;
fprintf(stderr, "kind = %d, depth = %d, len = %lu\n", data_kind, 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)
output->outputfn(output->outputarg, buf, len);
if (output->dest != NULL) {
offset = output->dest->Length;
if (offset == 0) {
dest = (unsigned char *)PORT_ArenaAlloc(output->destpoolp, len);
} else {
dest = (unsigned char *)PORT_ArenaGrow(output->destpoolp,
output->dest->Data,
output->dest->Length,
output->dest->Length + len);
}
if (dest == NULL)
return;
output->dest->Data = dest;
output->dest->Length += len;
PORT_Memcpy(output->dest->Data + offset, buf, len);
}
}
static void
nss_cms_encoder_notify(void *arg, Boolean before, void *dest, int depth)
{
SecCmsEncoderRef p7ecx;
SecCmsContentInfoRef rootcinfo, cinfo;
Boolean after = !before;
PLArenaPool *poolp;
SECOidTag childtype;
CSSM_DATA_PTR item;
p7ecx = (SecCmsEncoderRef)arg;
PORT_Assert(p7ecx != NULL);
rootcinfo = &(p7ecx->cmsg->contentInfo);
poolp = p7ecx->cmsg->poolp;
#ifdef CMSDEBUG
fprintf(stderr, "%6.6s, dest = %p, depth = %d\n", before ? "before" : "after", dest, depth);
#endif
switch (p7ecx->type) {
default:
case SEC_OID_UNKNOWN:
if (after && dest == &(rootcinfo->contentType)) {
p7ecx->type = SecCmsContentInfoGetContentTypeTag(rootcinfo);
p7ecx->content = rootcinfo->content;
}
break;
case SEC_OID_PKCS7_DATA:
case SEC_OID_OTHER:
if (before && dest == &(rootcinfo->rawContent)) {
if ((item = rootcinfo->content.data) != NULL)
(void)nss_cms_encoder_work_data(p7ecx, NULL, item->Data, item->Length, PR_TRUE, PR_TRUE);
else
SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx);
SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx);
}
break;
case SEC_OID_PKCS7_SIGNED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
cinfo = SecCmsContentGetContentInfo(p7ecx->content.pointer, p7ecx->type);
childtype = SecCmsContentInfoGetContentTypeTag(cinfo);
if (after && dest == &(cinfo->contentType)) {
if (nss_cms_before_data(p7ecx) != SECSuccess)
p7ecx->error = PORT_GetError();
}
if (before && dest == &(cinfo->rawContent)) {
if ( ((childtype == SEC_OID_PKCS7_DATA) || (childtype == SEC_OID_OTHER)) &&
((item = cinfo->content.data) != NULL))
(void)nss_cms_encoder_work_data(p7ecx, NULL, item->Data, item->Length, PR_TRUE, PR_TRUE);
else
SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx);
}
if (after && dest == &(cinfo->rawContent)) {
if (nss_cms_after_data(p7ecx) != SECSuccess)
p7ecx->error = PORT_GetError();
SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx);
}
break;
}
}
static OSStatus
nss_cms_before_data(SecCmsEncoderRef p7ecx)
{
OSStatus rv;
SECOidTag childtype;
SecCmsContentInfoRef cinfo;
PLArenaPool *poolp;
SecCmsEncoderRef childp7ecx;
const SecAsn1Template *template;
poolp = p7ecx->cmsg->poolp;
switch (p7ecx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
rv = SecCmsSignedDataEncodeBeforeData(p7ecx->content.signedData);
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
rv = SecCmsDigestedDataEncodeBeforeData(p7ecx->content.digestedData);
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
rv = SecCmsEnvelopedDataEncodeBeforeData(p7ecx->content.envelopedData);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
rv = SecCmsEncryptedDataEncodeBeforeData(p7ecx->content.encryptedData);
break;
default:
rv = SECFailure;
}
if (rv != SECSuccess)
return SECFailure;
cinfo = SecCmsContentGetContentInfo(p7ecx->content.pointer, p7ecx->type);
childtype = SecCmsContentInfoGetContentTypeTag(cinfo);
switch (childtype) {
case SEC_OID_PKCS7_SIGNED_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
case SEC_OID_PKCS7_DIGESTED_DATA:
#if 0
case SEC_OID_PKCS7_DATA:
#endif
childp7ecx = PORT_ZAlloc(sizeof(struct SecCmsEncoderStr));
if (childp7ecx == NULL)
return SECFailure;
childp7ecx->type = childtype;
childp7ecx->content = cinfo->content;
childp7ecx->output.outputfn = (SecCmsContentCallback)nss_cms_encoder_update;
childp7ecx->output.outputarg = p7ecx;
childp7ecx->output.destpoolp = NULL;
childp7ecx->output.dest = NULL;
childp7ecx->cmsg = p7ecx->cmsg;
template = SecCmsUtilGetTemplateByTypeTag(childtype);
if (template == NULL)
goto loser;
switch (childp7ecx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
rv = SecCmsSignedDataEncodeBeforeStart(cinfo->content.signedData);
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
rv = SecCmsEnvelopedDataEncodeBeforeStart(cinfo->content.envelopedData);
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
rv = SecCmsDigestedDataEncodeBeforeStart(cinfo->content.digestedData);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
rv = SecCmsEncryptedDataEncodeBeforeStart(cinfo->content.encryptedData);
break;
case SEC_OID_PKCS7_DATA:
case SEC_OID_OTHER:
rv = SECSuccess;
break;
default:
PORT_Assert(0);
break;
}
if (rv != SECSuccess)
goto loser;
childp7ecx->ecx = SEC_ASN1EncoderStart(cinfo->content.pointer, template,
nss_cms_encoder_out, &(childp7ecx->output));
if (childp7ecx->ecx == NULL)
goto loser;
childp7ecx->ecxupdated = PR_FALSE;
SEC_ASN1EncoderSetStreaming(childp7ecx->ecx);
SEC_ASN1EncoderSetNotifyProc(childp7ecx->ecx, nss_cms_encoder_notify, childp7ecx);
if (SEC_ASN1EncoderUpdate(childp7ecx->ecx, NULL, 0) != SECSuccess)
goto loser;
p7ecx->childp7ecx = childp7ecx;
break;
case SEC_OID_PKCS7_DATA:
case SEC_OID_OTHER:
p7ecx->childp7ecx = NULL;
break;
default:
p7ecx->error = SEC_ERROR_BAD_DER;
break;
}
return SECSuccess;
loser:
if (childp7ecx) {
if (childp7ecx->ecx)
SEC_ASN1EncoderFinish(childp7ecx->ecx);
PORT_Free(childp7ecx);
}
return SECFailure;
}
static OSStatus
nss_cms_after_data(SecCmsEncoderRef p7ecx)
{
OSStatus rv = SECFailure;
switch (p7ecx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
rv = SecCmsSignedDataEncodeAfterData(p7ecx->content.signedData);
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
rv = SecCmsEnvelopedDataEncodeAfterData(p7ecx->content.envelopedData);
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
rv = SecCmsDigestedDataEncodeAfterData(p7ecx->content.digestedData);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
rv = SecCmsEncryptedDataEncodeAfterData(p7ecx->content.encryptedData);
break;
case SEC_OID_PKCS7_DATA:
case SEC_OID_OTHER:
break;
default:
rv = SECFailure;
break;
}
return rv;
}
static OSStatus
nss_cms_encoder_work_data(SecCmsEncoderRef p7ecx, CSSM_DATA_PTR dest,
const unsigned char *data, size_t len,
Boolean final, Boolean innermost)
{
unsigned char *buf = NULL;
OSStatus rv;
SecCmsContentInfoRef cinfo;
rv = SECSuccess;
PORT_Assert ((data != NULL && len) || final);
cinfo = SecCmsContentGetContentInfo(p7ecx->content.pointer, p7ecx->type);
if (len && cinfo->digcx != NULL)
SecCmsDigestContextUpdate(cinfo->digcx, data, len);
if (cinfo->ciphcx != NULL) {
CSSM_SIZE inlen;
CSSM_SIZE outlen;
CSSM_SIZE buflen;
inlen = len;
buflen = SecCmsCipherContextEncryptLength(cinfo->ciphcx, inlen, final);
if (buflen == 0) {
rv = SecCmsCipherContextEncrypt(cinfo->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 = SecCmsCipherContextEncrypt(cinfo->ciphcx, buf, &outlen, buflen,
data, inlen, final);
data = buf;
len = outlen;
}
if (rv != SECSuccess)
return rv;
}
if (p7ecx->ecx != NULL && len && (!innermost || cinfo->rawContent != NULL))
rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, (const char *)data, len);
done:
if (cinfo->ciphcx != NULL) {
if (dest != NULL) {
dest->Data = buf;
dest->Length = len;
} else if (buf != NULL) {
PORT_Free (buf);
}
}
return rv;
}
static OSStatus
nss_cms_encoder_update(SecCmsEncoderRef p7ecx, const char *data, size_t len)
{
return nss_cms_encoder_work_data (p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_FALSE);
}
OSStatus
SecCmsEncoderCreate(SecCmsMessageRef cmsg,
SecCmsContentCallback outputfn, void *outputarg,
CSSM_DATA_PTR dest, SecArenaPoolRef destpool,
PK11PasswordFunc pwfn, void *pwfn_arg,
SecCmsGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg,
SECAlgorithmID **detached_digestalgs, CSSM_DATA_PTR *detached_digests,
SecCmsEncoderRef *outEncoder)
{
SecCmsEncoderRef p7ecx;
OSStatus result;
SecCmsContentInfoRef cinfo;
SecCmsMessageSetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg,
detached_digestalgs, detached_digests);
p7ecx = (SecCmsEncoderRef)PORT_ZAlloc(sizeof(struct SecCmsEncoderStr));
if (p7ecx == NULL) {
result = memFullErr;
goto loser;
}
p7ecx->cmsg = cmsg;
p7ecx->output.outputfn = outputfn;
p7ecx->output.outputarg = outputarg;
p7ecx->output.dest = dest;
p7ecx->output.destpoolp = (PLArenaPool *)destpool;
p7ecx->type = SEC_OID_UNKNOWN;
cinfo = SecCmsMessageGetContentInfo(cmsg);
switch (SecCmsContentInfoGetContentTypeTag(cinfo)) {
case SEC_OID_PKCS7_SIGNED_DATA:
result = SecCmsSignedDataEncodeBeforeStart(cinfo->content.signedData);
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
result = SecCmsEnvelopedDataEncodeBeforeStart(cinfo->content.envelopedData);
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
result = SecCmsDigestedDataEncodeBeforeStart(cinfo->content.digestedData);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
result = SecCmsEncryptedDataEncodeBeforeStart(cinfo->content.encryptedData);
break;
default:
result = paramErr;
break;
}
if (result)
goto loser;
p7ecx->ecx = SEC_ASN1EncoderStart(cmsg, SecCmsMessageTemplate,
nss_cms_encoder_out, &(p7ecx->output));
if (p7ecx->ecx == NULL) {
result = PORT_GetError();
PORT_Free (p7ecx);
goto loser;
}
p7ecx->ecxupdated = PR_FALSE;
SEC_ASN1EncoderSetStreaming(p7ecx->ecx);
SEC_ASN1EncoderSetNotifyProc(p7ecx->ecx, nss_cms_encoder_notify, p7ecx);
if (SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0) != SECSuccess) {
result = PORT_GetError();
PORT_Free (p7ecx);
goto loser;
}
*outEncoder = p7ecx;
loser:
return result;
}
OSStatus
SecCmsEncoderUpdate(SecCmsEncoderRef p7ecx, const void *data, CFIndex len)
{
OSStatus result;
SecCmsContentInfoRef cinfo;
SECOidTag childtype;
if (p7ecx->error)
return p7ecx->error;
if (p7ecx->childp7ecx) {
result = SecCmsEncoderUpdate(p7ecx->childp7ecx, data, len);
} else {
cinfo = SecCmsContentGetContentInfo(p7ecx->content.pointer, p7ecx->type);
childtype = SecCmsContentInfoGetContentTypeTag(cinfo);
if ((childtype != SEC_OID_PKCS7_DATA) && (childtype != SEC_OID_OTHER))
return paramErr;
if (cinfo->content.data != NULL)
return paramErr;
result = nss_cms_encoder_work_data(p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_TRUE);
if (result)
result = PORT_GetError();
}
return result;
}
void
SecCmsEncoderDestroy(SecCmsEncoderRef p7ecx)
{
if (p7ecx->childp7ecx)
SecCmsEncoderDestroy(p7ecx->childp7ecx);
if (nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)))
goto loser;
p7ecx->childp7ecx = NULL;
SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx);
SEC_ASN1EncoderClearStreaming(p7ecx->ecx);
SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0);
loser:
SEC_ASN1EncoderFinish(p7ecx->ecx);
PORT_Free (p7ecx);
}
OSStatus
SecCmsEncoderFinish(SecCmsEncoderRef p7ecx)
{
OSStatus result;
SecCmsContentInfoRef cinfo;
SECOidTag childtype;
if (p7ecx->childp7ecx) {
result = SecCmsEncoderFinish(p7ecx->childp7ecx);
if (result)
goto loser;
}
result = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL));
if (result) {
result = PORT_GetError();
goto loser;
}
p7ecx->childp7ecx = NULL;
cinfo = SecCmsContentGetContentInfo(p7ecx->content.pointer, p7ecx->type);
childtype = SecCmsContentInfoGetContentTypeTag(cinfo);
if ( ((childtype == SEC_OID_PKCS7_DATA) || (childtype == SEC_OID_OTHER)) &&
(cinfo->content.data == NULL)) {
SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx);
result = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0);
if (result)
result = PORT_GetError();
}
SEC_ASN1EncoderClearStreaming(p7ecx->ecx);
if (p7ecx->error && !result)
result = p7ecx->error;
loser:
SEC_ASN1EncoderFinish(p7ecx->ecx);
PORT_Free (p7ecx);
return result;
}
OSStatus
SecCmsMessageEncode(SecCmsMessageRef cmsg, const CSSM_DATA *input, SecArenaPoolRef arena,
CSSM_DATA_PTR outBer)
{
SecCmsEncoderRef encoder;
OSStatus result;
if (!cmsg || !outBer || !arena) {
result = paramErr;
goto loser;
}
result = SecCmsEncoderCreate(cmsg, 0, 0, outBer, arena, 0, 0, 0, 0, 0, 0, &encoder);
if (result)
goto loser;
if (input) {
result = SecCmsEncoderUpdate(encoder, input->Data, input->Length);
if (result) {
SecCmsEncoderDestroy(encoder);
goto loser;
}
}
result = SecCmsEncoderFinish(encoder);
loser:
return result;
}