#include <Security/SecCmsDecoder.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsDigestContext.h>
#include <Security/SecCmsMessage.h>
#include "cmslocal.h"
#include "SecAsn1Item.h"
#include "secoid.h"
#include <security_asn1/secasn1.h>
#include <security_asn1/secerr.h>
#include <security_asn1/secport.h>
#include <limits.h>
struct SecCmsDecoderStr {
SEC_ASN1DecoderContext * dcx;
SecCmsMessageRef cmsg;
SECOidTag type;
SecCmsContent content;
SecCmsDecoderRef childp7dcx;
Boolean saw_contents;
int error;
SecCmsContentCallback cb;
void * cb_arg;
};
static void nss_cms_decoder_update_filter (void *arg, const char *data, size_t len,
int depth, SEC_ASN1EncodingPart data_kind);
static OSStatus nss_cms_before_data(SecCmsDecoderRef p7dcx);
static OSStatus nss_cms_after_data(SecCmsDecoderRef p7dcx);
static OSStatus nss_cms_after_end(SecCmsDecoderRef p7dcx);
static void nss_cms_decoder_work_data(SecCmsDecoderRef p7dcx,
const unsigned char *data, size_t len, Boolean final);
extern const SecAsn1Template SecCmsMessageTemplate[];
static void
nss_cms_decoder_notify(void *arg, Boolean before, void *dest, int depth)
{
SecCmsDecoderRef p7dcx;
SecCmsContentInfoRef rootcinfo, cinfo;
Boolean after = !before;
p7dcx = (SecCmsDecoderRef)arg;
rootcinfo = &(p7dcx->cmsg->contentInfo);
#ifdef CMSDEBUG
fprintf(stderr, "%6.6s, dest = 0x%08x, depth = %d\n", before ? "before" : "after", dest, depth);
#endif
switch (p7dcx->type) {
case SEC_OID_UNKNOWN:
if (after && dest == &(rootcinfo->contentType)) {
p7dcx->type = SecCmsContentInfoGetContentTypeTag(rootcinfo);
p7dcx->content = rootcinfo->content;
}
break;
case SEC_OID_PKCS7_DATA:
if (before && dest == &(rootcinfo->content)) {
SEC_ASN1DecoderSetFilterProc(p7dcx->dcx,
nss_cms_decoder_update_filter,
p7dcx,
(Boolean)(p7dcx->cb != NULL));
break;
}
if (after && dest == &(rootcinfo->content.data)) {
SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
}
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:
if (before && dest == &(rootcinfo->content))
break;
if (p7dcx->content.pointer == NULL)
p7dcx->content = rootcinfo->content;
cinfo = SecCmsContentGetContentInfo(p7dcx->content.pointer, p7dcx->type);
if (before && dest == &(cinfo->contentType)) {
switch (p7dcx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
p7dcx->content.signedData->contentInfo.cmsg = p7dcx->cmsg;
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
p7dcx->content.digestedData->contentInfo.cmsg = p7dcx->cmsg;
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
p7dcx->content.envelopedData->contentInfo.cmsg = p7dcx->cmsg;
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
p7dcx->content.encryptedData->contentInfo.cmsg = p7dcx->cmsg;
break;
default:
PORT_Assert(0);
break;
}
}
if (before && dest == &(cinfo->rawContent)) {
SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, nss_cms_decoder_update_filter,
p7dcx, (Boolean)(p7dcx->cb != NULL));
if (nss_cms_before_data(p7dcx) != SECSuccess) {
SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
p7dcx->error = PORT_GetError();
}
}
if (after && dest == &(cinfo->rawContent)) {
if (nss_cms_after_data(p7dcx) != SECSuccess)
p7dcx->error = PORT_GetError();
SEC_ASN1DecoderClearFilterProc(p7dcx->dcx);
}
break;
#if 0
case SEC_OID_PKCS7_AUTHENTICATED_DATA:
#endif
default:
p7dcx->error = SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE;
break;
}
}
static OSStatus
nss_cms_before_data(SecCmsDecoderRef p7dcx)
{
OSStatus rv;
SECOidTag childtype;
PLArenaPool *poolp;
SecCmsDecoderRef childp7dcx;
SecCmsContentInfoRef cinfo;
const SecAsn1Template *template;
void *mark = NULL;
size_t size;
poolp = p7dcx->cmsg->poolp;
switch (p7dcx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
rv = SecCmsSignedDataDecodeBeforeData(p7dcx->content.signedData);
if (rv != SECSuccess)
return SECFailure;
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
rv = SecCmsDigestedDataDecodeBeforeData(p7dcx->content.digestedData);
if (rv != SECSuccess)
return SECFailure;
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
rv = SecCmsEnvelopedDataDecodeBeforeData(p7dcx->content.envelopedData);
if (rv != SECSuccess)
return SECFailure;
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
rv = SecCmsEncryptedDataDecodeBeforeData(p7dcx->content.encryptedData);
if (rv != SECSuccess)
return SECFailure;
break;
default:
return SECFailure;
}
cinfo = SecCmsContentGetContentInfo(p7dcx->content.pointer, p7dcx->type);
childtype = SecCmsContentInfoGetContentTypeTag(cinfo);
if (childtype == SEC_OID_PKCS7_DATA) {
cinfo->content.data = SECITEM_AllocItem(poolp, NULL, 0);
if (cinfo->content.data == NULL)
return SECFailure;
p7dcx->childp7dcx = NULL;
return SECSuccess;
}
if ((template = SecCmsUtilGetTemplateByTypeTag(childtype)) == NULL)
return SECFailure;
childp7dcx = (SecCmsDecoderRef)PORT_ZAlloc(sizeof(struct SecCmsDecoderStr));
if (childp7dcx == NULL)
return SECFailure;
mark = PORT_ArenaMark(poolp);
size = SecCmsUtilGetSizeByTypeTag(childtype);
childp7dcx->content.pointer = (void *)PORT_ArenaZAlloc(poolp, size);
if (childp7dcx->content.pointer == NULL)
goto loser;
childp7dcx->dcx = SEC_ASN1DecoderStart(poolp, childp7dcx->content.pointer, template, NULL, 0);
if (childp7dcx->dcx == NULL)
goto loser;
SEC_ASN1DecoderSetNotifyProc(childp7dcx->dcx, nss_cms_decoder_notify, childp7dcx);
p7dcx->childp7dcx = childp7dcx;
childp7dcx->type = childtype;
childp7dcx->cmsg = p7dcx->cmsg;
childp7dcx->cb = p7dcx->cb;
childp7dcx->cb_arg = p7dcx->cb_arg;
p7dcx->cb = (SecCmsContentCallback)SecCmsDecoderUpdate;
p7dcx->cb_arg = childp7dcx;
PORT_ArenaUnmark(poolp, mark);
return SECSuccess;
loser:
if (mark)
PORT_ArenaRelease(poolp, mark);
if (childp7dcx)
PORT_Free(childp7dcx);
p7dcx->childp7dcx = NULL;
return SECFailure;
}
static OSStatus
nss_cms_after_data(SecCmsDecoderRef p7dcx)
{
SecCmsDecoderRef childp7dcx;
OSStatus rv = SECFailure;
nss_cms_decoder_work_data(p7dcx, NULL, 0, PR_TRUE);
if (p7dcx->childp7dcx != NULL) {
childp7dcx = p7dcx->childp7dcx;
if (childp7dcx->dcx != NULL) {
if (SEC_ASN1DecoderFinish(childp7dcx->dcx) != SECSuccess) {
rv = SECFailure;
} else {
rv = nss_cms_after_end(childp7dcx);
}
if (rv != SECSuccess)
goto done;
}
PORT_Free(p7dcx->childp7dcx);
p7dcx->childp7dcx = NULL;
}
switch (p7dcx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
rv = SecCmsSignedDataDecodeAfterData(p7dcx->content.signedData);
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
rv = SecCmsEnvelopedDataDecodeAfterData(p7dcx->content.envelopedData);
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
rv = SecCmsDigestedDataDecodeAfterData(p7dcx->content.digestedData);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
rv = SecCmsEncryptedDataDecodeAfterData(p7dcx->content.encryptedData);
break;
case SEC_OID_PKCS7_DATA:
break;
default:
rv = SECFailure;
break;
}
done:
return rv;
}
static OSStatus
nss_cms_after_end(SecCmsDecoderRef p7dcx)
{
OSStatus rv;
PLArenaPool *poolp;
poolp = p7dcx->cmsg->poolp;
switch (p7dcx->type) {
case SEC_OID_PKCS7_SIGNED_DATA:
rv = SecCmsSignedDataDecodeAfterEnd(p7dcx->content.signedData);
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
rv = SecCmsEnvelopedDataDecodeAfterEnd(p7dcx->content.envelopedData);
break;
case SEC_OID_PKCS7_DIGESTED_DATA:
rv = SecCmsDigestedDataDecodeAfterEnd(p7dcx->content.digestedData);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
rv = SecCmsEncryptedDataDecodeAfterEnd(p7dcx->content.encryptedData);
break;
case SEC_OID_PKCS7_DATA:
rv = SECSuccess;
break;
default:
rv = SECFailure;
break;
}
return rv;
}
static void
nss_cms_decoder_work_data(SecCmsDecoderRef p7dcx,
const unsigned char *data, size_t len,
Boolean final)
{
SecCmsContentInfoRef cinfo;
unsigned char *buf = NULL;
unsigned char *dest;
size_t offset;
OSStatus rv;
SecAsn1Item * storage;
PORT_Assert ((data != NULL && len) || final);
PORT_Assert (len <= UINT_MAX);
if (!p7dcx->content.pointer) return;
cinfo = SecCmsContentGetContentInfo(p7dcx->content.pointer, p7dcx->type);
if (cinfo->ciphcx != NULL) {
unsigned int outlen = 0;
unsigned int buflen;
buflen = SecCmsCipherContextDecryptLength(cinfo->ciphcx, (unsigned int)len, final);
if (buflen == 0 && len == 0)
goto loser;
if (buflen != 0) {
buf = (unsigned char *)PORT_Alloc(buflen);
if (buf == NULL) {
p7dcx->error = SEC_ERROR_NO_MEMORY;
goto loser;
}
}
rv = SecCmsCipherContextDecrypt(cinfo->ciphcx, buf, &outlen, buflen,
data, (unsigned int)len, final);
if (rv != SECSuccess) {
p7dcx->error = PORT_GetError();
goto loser;
}
data = buf;
len = outlen;
}
if (len == 0)
goto done;
if (cinfo->digcx)
SecCmsDigestContextUpdate(cinfo->digcx, data, len);
if (p7dcx->cb != NULL) {
(*p7dcx->cb)(p7dcx->cb_arg, (const char *)data, len);
}
#if 1
else
#endif
if (SecCmsContentInfoGetContentTypeTag(cinfo) == SEC_OID_PKCS7_DATA) {
storage = cinfo->content.data;
offset = storage->Length;
if (len >= (size_t)(INT_MAX - storage->Length)) {
p7dcx->error = SEC_ERROR_NO_MEMORY;
goto loser;
}
if (storage->Length == 0) {
dest = (unsigned char *)PORT_ArenaAlloc(p7dcx->cmsg->poolp, len);
} else {
dest = (unsigned char *)PORT_ArenaGrow(p7dcx->cmsg->poolp,
storage->Data,
storage->Length,
storage->Length + len);
}
if (dest == NULL) {
p7dcx->error = SEC_ERROR_NO_MEMORY;
goto loser;
}
storage->Data = dest;
storage->Length += len;
PORT_Memcpy(storage->Data + offset, data, len);
}
done:
loser:
if (buf)
PORT_Free (buf);
}
static void
nss_cms_decoder_update_filter (void *arg, const char *data, size_t len,
int depth, SEC_ASN1EncodingPart data_kind)
{
SecCmsDecoderRef p7dcx;
PORT_Assert (len);
if (len == 0)
return;
p7dcx = (SecCmsDecoderRef)arg;
p7dcx->saw_contents = PR_TRUE;
if (data_kind == SEC_ASN1_Contents)
nss_cms_decoder_work_data(p7dcx, (const unsigned char *) data, len, PR_FALSE);
}
OSStatus
SecCmsDecoderCreate(SecCmsContentCallback cb, void *cb_arg,
PK11PasswordFunc pwfn, void *pwfn_arg,
SecCmsGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg,
SecCmsDecoderRef *outDecoder)
{
SecCmsDecoderRef p7dcx;
SecCmsMessageRef cmsg;
OSStatus result;
cmsg = SecCmsMessageCreate();
if (cmsg == NULL)
goto loser;
SecCmsMessageSetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg);
p7dcx = (SecCmsDecoderRef)PORT_ZAlloc(sizeof(struct SecCmsDecoderStr));
if (p7dcx == NULL) {
SecCmsMessageDestroy(cmsg);
goto loser;
}
p7dcx->dcx = SEC_ASN1DecoderStart(cmsg->poolp, cmsg, SecCmsMessageTemplate, NULL, 0);
if (p7dcx->dcx == NULL) {
PORT_Free (p7dcx);
SecCmsMessageDestroy(cmsg);
goto loser;
}
SEC_ASN1DecoderSetNotifyProc (p7dcx->dcx, nss_cms_decoder_notify, p7dcx);
p7dcx->cmsg = cmsg;
p7dcx->type = SEC_OID_UNKNOWN;
p7dcx->cb = cb;
p7dcx->cb_arg = cb_arg;
*outDecoder = p7dcx;
return errSecSuccess;
loser:
result = PORT_GetError();
return result;
}
OSStatus
SecCmsDecoderUpdate(SecCmsDecoderRef p7dcx, const void *buf, CFIndex len)
{
if (p7dcx->dcx != NULL && p7dcx->error == 0) {
if (SEC_ASN1DecoderUpdate (p7dcx->dcx, buf, len) != SECSuccess) {
p7dcx->error = PORT_GetError();
PORT_Assert (p7dcx->error);
if (p7dcx->error == 0)
p7dcx->error = -1;
}
}
if (p7dcx->error == 0)
return 0;
if (p7dcx->dcx != NULL) {
(void) SEC_ASN1DecoderFinish (p7dcx->dcx);
p7dcx->dcx = NULL;
}
PORT_SetError (p7dcx->error);
return p7dcx->error;
}
void
SecCmsDecoderDestroy(SecCmsDecoderRef p7dcx)
{
SecCmsMessageDestroy(p7dcx->cmsg);
if (p7dcx->dcx)
(void)SEC_ASN1DecoderFinish(p7dcx->dcx);
PORT_Free(p7dcx);
}
OSStatus
SecCmsDecoderFinish(SecCmsDecoderRef p7dcx, SecCmsMessageRef *outMessage)
{
SecCmsMessageRef cmsg;
OSStatus result;
cmsg = p7dcx->cmsg;
if (p7dcx->dcx == NULL || SEC_ASN1DecoderFinish(p7dcx->dcx) != SECSuccess ||
nss_cms_after_end(p7dcx) != SECSuccess)
{
SecCmsMessageDestroy(cmsg);
result = PORT_GetError();
goto loser;
}
*outMessage = cmsg;
result = errSecSuccess;
loser:
PORT_Free(p7dcx);
return result;
}
OSStatus
SecCmsMessageDecode(const SecAsn1Item *encodedMessage,
SecCmsContentCallback cb, void *cb_arg,
PK11PasswordFunc pwfn, void *pwfn_arg,
SecCmsGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg,
SecCmsMessageRef *outMessage)
{
OSStatus result;
SecCmsDecoderRef decoder = NULL;
result = SecCmsDecoderCreate(cb, cb_arg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg, &decoder);
if (result)
goto loser;
result = SecCmsDecoderUpdate(decoder, encodedMessage->Data, encodedMessage->Length);
if (result) {
SecCmsDecoderDestroy(decoder);
goto loser;
}
result = SecCmsDecoderFinish(decoder, outMessage);
loser:
return result;
}