#include <Security/SecCmsSignedData.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsDigestContext.h>
#include <Security/SecCmsSignerInfo.h>
#include "cmslocal.h"
#include "cert.h"
#include "secitem.h"
#include "secoid.h"
#include <security_asn1/secasn1.h>
#include <security_asn1/secerr.h>
#include <Security/SecBase.h>
#define SIGDATA_DEBUG 0
#if SIGDATA_DEBUG
#define dprintf(args...) printf(args)
#else
#define dprintf(args...)
#endif
SecCmsSignedDataRef
SecCmsSignedDataCreate(SecCmsMessageRef cmsg)
{
void *mark;
SecCmsSignedDataRef sigd;
PLArenaPool *poolp;
poolp = cmsg->poolp;
mark = PORT_ArenaMark(poolp);
sigd = (SecCmsSignedDataRef)PORT_ArenaZAlloc (poolp, sizeof(SecCmsSignedData));
if (sigd == NULL)
goto loser;
sigd->cmsg = cmsg;
PORT_ArenaUnmark(poolp, mark);
return sigd;
loser:
PORT_ArenaRelease(poolp, mark);
return NULL;
}
void
SecCmsSignedDataDestroy(SecCmsSignedDataRef sigd)
{
SecCmsSignerInfoRef *signerinfos, si;
if (sigd == NULL)
return;
if (sigd->certs != NULL)
CFRelease(sigd->certs);
signerinfos = sigd->signerInfos;
if (signerinfos != NULL) {
while ((si = *signerinfos++) != NULL)
SecCmsSignerInfoDestroy(si);
}
SecCmsContentInfoDestroy(&(sigd->contentInfo));
}
OSStatus
SecCmsSignedDataEncodeBeforeStart(SecCmsSignedDataRef sigd)
{
SecCmsSignerInfoRef signerinfo;
SECOidTag digestalgtag;
CSSM_DATA_PTR dummy;
int version;
OSStatus rv;
Boolean haveDigests = PR_FALSE;
int n, i;
PLArenaPool *poolp;
poolp = sigd->cmsg->poolp;
if (sigd->digestAlgorithms != NULL && sigd->digests != NULL) {
for (i=0; sigd->digestAlgorithms[i] != NULL; i++) {
if (sigd->digests[i] == NULL)
break;
}
if (sigd->digestAlgorithms[i] == NULL)
haveDigests = PR_TRUE;
}
version = SEC_CMS_SIGNED_DATA_VERSION_BASIC;
if (SecCmsContentInfoGetContentTypeTag(&(sigd->contentInfo)) != SEC_OID_PKCS7_DATA)
version = SEC_CMS_SIGNED_DATA_VERSION_EXT;
for (i=0; i < SecCmsSignedDataSignerInfoCount(sigd); i++) {
signerinfo = SecCmsSignedDataGetSignerInfo(sigd, i);
if (SecCmsSignerInfoGetVersion(signerinfo) != SEC_CMS_SIGNER_INFO_VERSION_ISSUERSN)
version = SEC_CMS_SIGNED_DATA_VERSION_EXT;
digestalgtag = SecCmsSignerInfoGetDigestAlgTag(signerinfo);
n = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag);
if (n < 0 && haveDigests) {
goto loser;
} else if (n < 0) {
rv = SecCmsSignedDataAddDigest((SecArenaPoolRef)poolp, sigd, digestalgtag, NULL);
if (rv != SECSuccess)
goto loser;
} else {
}
}
dummy = SEC_ASN1EncodeInteger(poolp, &(sigd->version), (long)version);
if (dummy == NULL)
return SECFailure;
rv = SecCmsArraySortByDER((void **)sigd->digestAlgorithms,
SEC_ASN1_GET(SECOID_AlgorithmIDTemplate),
(void **)sigd->digests);
if (rv != SECSuccess)
return SECFailure;
return SECSuccess;
loser:
return SECFailure;
}
OSStatus
SecCmsSignedDataEncodeBeforeData(SecCmsSignedDataRef sigd)
{
if (sigd->digestAlgorithms != NULL) {
sigd->contentInfo.digcx = SecCmsDigestContextStartMultiple(sigd->digestAlgorithms);
if (sigd->contentInfo.digcx == NULL)
return SECFailure;
}
return SECSuccess;
}
OSStatus
SecCmsSignedDataEncodeAfterData(SecCmsSignedDataRef sigd)
{
SecCmsSignerInfoRef *signerinfos, signerinfo;
SecCmsContentInfoRef cinfo;
SECOidTag digestalgtag;
OSStatus ret = SECFailure;
OSStatus rv;
CSSM_DATA_PTR contentType;
int certcount;
int i, ci, n, rci, si;
PLArenaPool *poolp;
CFArrayRef certlist;
extern const SecAsn1Template SecCmsSignerInfoTemplate[];
poolp = sigd->cmsg->poolp;
cinfo = &(sigd->contentInfo);
if (cinfo->digcx) {
rv = SecCmsDigestContextFinishMultiple(cinfo->digcx, (SecArenaPoolRef)poolp, &(sigd->digests));
if (rv != SECSuccess)
goto loser;
cinfo->digcx = NULL;
}
signerinfos = sigd->signerInfos;
certcount = 0;
for (i=0; i < SecCmsSignedDataSignerInfoCount(sigd); i++) {
signerinfo = SecCmsSignedDataGetSignerInfo(sigd, i);
digestalgtag = SecCmsSignerInfoGetDigestAlgTag(signerinfo);
n = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag);
if (n < 0 || sigd->digests == NULL || sigd->digests[n] == NULL) {
PORT_SetError(SEC_ERROR_DIGEST_NOT_FOUND);
goto loser;
}
if ((contentType = SecCmsContentInfoGetContentTypeOID(cinfo)) == NULL)
goto loser;
rv = SecCmsSignerInfoSign(signerinfo, sigd->digests[n], contentType);
if (rv != SECSuccess)
goto loser;
certlist = SecCmsSignerInfoGetCertList(signerinfo);
if (certlist)
certcount += CFArrayGetCount(certlist);
}
rv = SecCmsArraySortByDER((void **)signerinfos, SecCmsSignerInfoTemplate, NULL);
if (rv != SECSuccess)
goto loser;
if (sigd->certs != NULL)
certcount += CFArrayGetCount(sigd->certs);
if (certcount == 0) {
sigd->rawCerts = NULL;
} else {
sigd->rawCerts = (CSSM_DATA_PTR *)PORT_ArenaAlloc(poolp, (certcount + 1) * sizeof(CSSM_DATA_PTR));
if (sigd->rawCerts == NULL)
return SECFailure;
rci = 0;
if (signerinfos != NULL) {
for (si = 0; signerinfos[si] != NULL; si++) {
signerinfo = signerinfos[si];
for (ci = 0; ci < CFArrayGetCount(signerinfo->certList); ci++) {
sigd->rawCerts[rci] = PORT_ArenaZAlloc(poolp, sizeof(CSSM_DATA));
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(signerinfo->certList, ci);
SecCertificateGetData(cert, sigd->rawCerts[rci++]);
}
}
}
if (sigd->certs != NULL) {
for (ci = 0; ci < CFArrayGetCount(sigd->certs); ci++) {
sigd->rawCerts[rci] = PORT_ArenaZAlloc(poolp, sizeof(CSSM_DATA));
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(sigd->certs, ci);
SecCertificateGetData(cert, sigd->rawCerts[rci++]);
}
}
sigd->rawCerts[rci] = NULL;
SecCmsArraySort((void **)sigd->rawCerts, SecCmsUtilDERCompare, NULL, NULL);
}
ret = SECSuccess;
loser:
return ret;
}
OSStatus
SecCmsSignedDataDecodeBeforeData(SecCmsSignedDataRef sigd)
{
if (sigd->digestAlgorithms != NULL && sigd->digests == NULL) {
sigd->contentInfo.digcx = SecCmsDigestContextStartMultiple(sigd->digestAlgorithms);
if (sigd->contentInfo.digcx == NULL)
return SECFailure;
}
return SECSuccess;
}
OSStatus
SecCmsSignedDataDecodeAfterData(SecCmsSignedDataRef sigd)
{
if (sigd->contentInfo.digcx) {
if (SecCmsDigestContextFinishMultiple(sigd->contentInfo.digcx, (SecArenaPoolRef)sigd->cmsg->poolp, &(sigd->digests)) != SECSuccess)
return SECFailure;
sigd->contentInfo.digcx = NULL;
}
return SECSuccess;
}
OSStatus
SecCmsSignedDataDecodeAfterEnd(SecCmsSignedDataRef sigd)
{
SecCmsSignerInfoRef *signerinfos;
int i;
signerinfos = sigd->signerInfos;
if (signerinfos) {
for (i = 0; signerinfos[i] != NULL; i++) {
signerinfos[i]->cmsg = sigd->cmsg;
signerinfos[i]->sigd = sigd;
}
}
return SECSuccess;
}
SecCmsSignerInfoRef *
SecCmsSignedDataGetSignerInfos(SecCmsSignedDataRef sigd)
{
return sigd->signerInfos;
}
int
SecCmsSignedDataSignerInfoCount(SecCmsSignedDataRef sigd)
{
return SecCmsArrayCount((void **)sigd->signerInfos);
}
SecCmsSignerInfoRef
SecCmsSignedDataGetSignerInfo(SecCmsSignedDataRef sigd, int i)
{
return sigd->signerInfos[i];
}
SECAlgorithmID **
SecCmsSignedDataGetDigestAlgs(SecCmsSignedDataRef sigd)
{
return sigd->digestAlgorithms;
}
SecCmsContentInfoRef
SecCmsSignedDataGetContentInfo(SecCmsSignedDataRef sigd)
{
return &(sigd->contentInfo);
}
CSSM_DATA_PTR *
SecCmsSignedDataGetCertificateList(SecCmsSignedDataRef sigd)
{
return sigd->rawCerts;
}
OSStatus
SecCmsSignedDataImportCerts(SecCmsSignedDataRef sigd, SecKeychainRef keychain,
SECCertUsage certusage, Boolean keepcerts)
{
int certcount;
OSStatus rv;
int i;
certcount = SecCmsArrayCount((void **)sigd->rawCerts);
rv = CERT_ImportCerts(keychain, certusage, certcount, sigd->rawCerts, NULL,
keepcerts, PR_FALSE, NULL);
if (sigd->signerInfos != NULL) {
for (i = 0; sigd->signerInfos[i] != NULL; i++)
(void)SecCmsSignerInfoGetSigningCertificate(sigd->signerInfos[i], keychain);
}
return rv;
}
OSStatus
SecCmsSignedDataVerifySignerInfo(SecCmsSignedDataRef sigd, int i,
SecKeychainRef keychainOrArray, CFTypeRef policies, SecTrustRef *trustRef)
{
SecCmsSignerInfoRef signerinfo;
SecCmsContentInfoRef cinfo;
SECOidData *algiddata;
CSSM_DATA_PTR contentType, digest;
OSStatus status, status2;
cinfo = &(sigd->contentInfo);
signerinfo = sigd->signerInfos[i];
algiddata = SecCmsSignerInfoGetDigestAlg(signerinfo);
digest = SecCmsSignedDataGetDigestByAlgTag(sigd, algiddata->offset);
if(digest == NULL) {
return errSecDataNotAvailable;
}
contentType = SecCmsContentInfoGetContentTypeOID(cinfo);
status = SecCmsSignerInfoVerify(signerinfo, digest, contentType);
status2 = SecCmsSignerInfoVerifyCertificate(signerinfo, keychainOrArray,
policies, trustRef);
dprintf("SecCmsSignedDataVerifySignerInfo: status %d status2 %d\n", (int) status, (int)status2);
if (status)
return status;
return status2;
}
OSStatus
SecCmsSignedDataVerifyCertsOnly(SecCmsSignedDataRef sigd,
SecKeychainRef keychainOrArray,
CFTypeRef policies)
{
SecCertificateRef cert;
OSStatus rv = SECSuccess;
int i;
int count;
if (!sigd || !keychainOrArray || !sigd->rawCerts) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
count = SecCmsArrayCount((void**)sigd->rawCerts);
for (i=0; i < count; i++) {
if (sigd->certs && CFArrayGetCount(sigd->certs) > i) {
cert = (SecCertificateRef)CFArrayGetValueAtIndex(sigd->certs, i);
CFRetain(cert);
} else {
cert = CERT_FindCertByDERCert(keychainOrArray, sigd->rawCerts[i]);
if (!cert) {
rv = SECFailure;
break;
}
}
rv |= CERT_VerifyCert(keychainOrArray, cert, sigd->rawCerts,
policies, CFAbsoluteTimeGetCurrent(), NULL);
CFRelease(cert);
}
return rv;
}
Boolean
SecCmsSignedDataHasDigests(SecCmsSignedDataRef sigd)
{
return (sigd->digests != NULL);
}
OSStatus
SecCmsSignedDataAddCertList(SecCmsSignedDataRef sigd, CFArrayRef certlist)
{
PORT_Assert(certlist != NULL);
if (certlist == NULL)
return SECFailure;
if (!sigd->certs)
sigd->certs = CFArrayCreateMutableCopy(NULL, 0, certlist);
else
{
CFRange certlistRange = { 0, CFArrayGetCount(certlist) };
CFArrayAppendArray(sigd->certs, certlist, certlistRange);
}
return SECSuccess;
}
OSStatus
SecCmsSignedDataAddCertChain(SecCmsSignedDataRef sigd, SecCertificateRef cert)
{
CFArrayRef certlist;
SECCertUsage usage;
OSStatus rv;
usage = certUsageEmailSigner;
certlist = CERT_CertChainFromCert(cert, usage, PR_FALSE);
if (certlist == NULL)
return SECFailure;
rv = SecCmsSignedDataAddCertList(sigd, certlist);
CFRelease(certlist);
return rv;
}
OSStatus
SecCmsSignedDataAddCertificate(SecCmsSignedDataRef sigd, SecCertificateRef cert)
{
PORT_Assert(cert != NULL);
if (cert == NULL)
return SECFailure;
if (!sigd->certs)
sigd->certs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(sigd->certs, cert);
return SECSuccess;
}
Boolean
SecCmsSignedDataContainsCertsOrCrls(SecCmsSignedDataRef sigd)
{
if (sigd->rawCerts != NULL && sigd->rawCerts[0] != NULL)
return PR_TRUE;
else if (sigd->rawCrls != NULL && sigd->rawCrls[0] != NULL)
return PR_TRUE;
else
return PR_FALSE;
}
OSStatus
SecCmsSignedDataAddSignerInfo(SecCmsSignedDataRef sigd,
SecCmsSignerInfoRef signerinfo)
{
void *mark;
OSStatus rv;
SECOidTag digestalgtag;
PLArenaPool *poolp;
poolp = sigd->cmsg->poolp;
mark = PORT_ArenaMark(poolp);
rv = SecCmsArrayAdd(poolp, (void ***)&(sigd->signerInfos), (void *)signerinfo);
if (rv != SECSuccess)
goto loser;
signerinfo->sigd = sigd;
digestalgtag = SecCmsSignerInfoGetDigestAlgTag(signerinfo);
rv = SecCmsSignedDataSetDigestValue(sigd, digestalgtag, NULL);
if (rv != SECSuccess)
goto loser;
PORT_ArenaUnmark(poolp, mark);
return SECSuccess;
loser:
PORT_ArenaRelease (poolp, mark);
return SECFailure;
}
CSSM_DATA_PTR
SecCmsSignedDataGetDigestByAlgTag(SecCmsSignedDataRef sigd, SECOidTag algtag)
{
int idx;
if(sigd->digests == NULL) {
return NULL;
}
idx = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, algtag);
return sigd->digests[idx];
}
OSStatus
SecCmsSignedDataSetDigests(SecCmsSignedDataRef sigd,
SECAlgorithmID **digestalgs,
CSSM_DATA_PTR *digests)
{
int cnt, i, idx;
if (sigd->digestAlgorithms == NULL) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
PORT_Assert(sigd->digests == NULL);
if (sigd->digests != NULL) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
}
cnt = SecCmsArrayCount((void **)sigd->digestAlgorithms);
sigd->digests = PORT_ArenaZAlloc(sigd->cmsg->poolp, (cnt + 1) * sizeof(CSSM_DATA_PTR));
if (sigd->digests == NULL) {
PORT_SetError(SEC_ERROR_NO_MEMORY);
return SECFailure;
}
for (i = 0; sigd->digestAlgorithms[i] != NULL; i++) {
idx = SecCmsAlgArrayGetIndexByAlgID(digestalgs, sigd->digestAlgorithms[i]);
if (idx < 0) {
PORT_SetError(SEC_ERROR_DIGEST_NOT_FOUND);
return SECFailure;
}
if ((sigd->digests[i] = SECITEM_AllocItem(sigd->cmsg->poolp, NULL, 0)) == NULL ||
SECITEM_CopyItem(sigd->cmsg->poolp, sigd->digests[i], digests[idx]) != SECSuccess)
{
PORT_SetError(SEC_ERROR_NO_MEMORY);
return SECFailure;
}
}
return SECSuccess;
}
OSStatus
SecCmsSignedDataSetDigestValue(SecCmsSignedDataRef sigd,
SECOidTag digestalgtag,
CSSM_DATA_PTR digestdata)
{
CSSM_DATA_PTR digest = NULL;
PLArenaPool *poolp;
void *mark;
int n, cnt;
poolp = sigd->cmsg->poolp;
mark = PORT_ArenaMark(poolp);
if (digestdata) {
digest = (CSSM_DATA_PTR) PORT_ArenaZAlloc(poolp,sizeof(CSSM_DATA));
if (SECITEM_CopyItem(poolp, digest, digestdata) != SECSuccess)
goto loser;
}
if (sigd->digests == NULL) {
cnt = SecCmsArrayCount((void **)sigd->digestAlgorithms);
sigd->digests = PORT_ArenaZAlloc(sigd->cmsg->poolp, (cnt + 1) * sizeof(CSSM_DATA_PTR));
if (sigd->digests == NULL) {
PORT_SetError(SEC_ERROR_NO_MEMORY);
return SECFailure;
}
}
n = -1;
if (sigd->digestAlgorithms != NULL)
n = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag);
if (n < 0) {
if (SecCmsSignedDataAddDigest((SecArenaPoolRef)poolp, sigd, digestalgtag, digest) != SECSuccess)
goto loser;
} else {
sigd->digests[n] = digest;
}
PORT_ArenaUnmark(poolp, mark);
return SECSuccess;
loser:
PORT_ArenaRelease(poolp, mark);
return SECFailure;
}
OSStatus
SecCmsSignedDataAddDigest(SecArenaPoolRef pool,
SecCmsSignedDataRef sigd,
SECOidTag digestalgtag,
CSSM_DATA_PTR digest)
{
PRArenaPool *poolp = (PRArenaPool *)pool;
SECAlgorithmID *digestalg;
void *mark;
mark = PORT_ArenaMark(poolp);
digestalg = PORT_ArenaZAlloc(poolp, sizeof(SECAlgorithmID));
if (digestalg == NULL)
goto loser;
if (SECOID_SetAlgorithmID (poolp, digestalg, digestalgtag, NULL) != SECSuccess)
goto loser;
if (SecCmsArrayAdd(poolp, (void ***)&(sigd->digestAlgorithms), (void *)digestalg) != SECSuccess ||
SecCmsArrayAdd(poolp, (void ***)&(sigd->digests), (void *)digest) != SECSuccess)
{
goto loser;
}
PORT_ArenaUnmark(poolp, mark);
return SECSuccess;
loser:
PORT_ArenaRelease(poolp, mark);
return SECFailure;
}
CSSM_DATA_PTR
SecCmsSignedDataGetDigestValue(SecCmsSignedDataRef sigd, SECOidTag digestalgtag)
{
int n;
if (sigd->digestAlgorithms == NULL)
return NULL;
n = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag);
return (n < 0) ? NULL : sigd->digests[n];
}
SecCmsSignedDataRef
SecCmsSignedDataCreateCertsOnly(SecCmsMessageRef cmsg, SecCertificateRef cert, Boolean include_chain)
{
SecCmsSignedDataRef sigd;
void *mark;
PLArenaPool *poolp;
OSStatus rv;
poolp = cmsg->poolp;
mark = PORT_ArenaMark(poolp);
sigd = SecCmsSignedDataCreate(cmsg);
if (sigd == NULL)
goto loser;
if (include_chain) {
rv = SecCmsSignedDataAddCertChain(sigd, cert);
} else {
rv = SecCmsSignedDataAddCertificate(sigd, cert);
}
if (rv != SECSuccess)
goto loser;
rv = SecCmsContentInfoSetContentData(cmsg, &(sigd->contentInfo), NULL, PR_TRUE);
if (rv != SECSuccess)
goto loser;
PORT_ArenaUnmark(poolp, mark);
return sigd;
loser:
if (sigd)
SecCmsSignedDataDestroy(sigd);
PORT_ArenaRelease(poolp, mark);
return NULL;
}
extern OSStatus SecCmsSignedDataRawCerts(SecCmsSignedDataRef sigd,
CSSM_DATA_PTR **rawCerts)
{
*rawCerts = sigd->rawCerts;
return noErr;
}