X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_cms/lib/CMSEncoder.cpp?ds=inline diff --git a/Security/libsecurity_cms/lib/CMSEncoder.cpp b/Security/libsecurity_cms/lib/CMSEncoder.cpp new file mode 100644 index 00000000..12829f51 --- /dev/null +++ b/Security/libsecurity_cms/lib/CMSEncoder.cpp @@ -0,0 +1,1451 @@ +/* + * Copyright (c) 2006-2013 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * CMSEncoder.cpp - encode, sign, and/or encrypt CMS messages. + */ + +#include "CMSEncoder.h" +#include "CMSPrivate.h" +#include "CMSUtils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#pragma mark --- Private types and definitions --- + +/* + * Encoder state. + */ +typedef enum { + ES_Init, /* between CMSEncoderCreate and earlier of CMSEncoderUpdateContent + * and CMSEncodeGetCmsMessage */ + ES_Msg, /* created cmsMsg in CMSEncodeGetCmsMessage, but no encoder yet */ + ES_Updating, /* between first CMSEncoderUpdateContent and CMSEncoderCopyEncodedContent */ + ES_Final /* CMSEncoderCopyEncodedContent has been called */ +} CMSEncoderState; + +/* + * High-level operation: what are we doing? + */ +typedef enum { + EO_Sign, + EO_Encrypt, + EO_SignEncrypt +} CMSEncoderOp; + +/* + * Caller's CMSEncoderRef points to one of these. + */ +struct _CMSEncoder { + CFRuntimeBase base; + CMSEncoderState encState; + CMSEncoderOp op; + Boolean detachedContent; + CSSM_OID eContentType; + CFMutableArrayRef signers; + CFMutableArrayRef recipients; + CFMutableArrayRef otherCerts; + CMSSignedAttributes signedAttributes; + CFAbsoluteTime signingTime; + SecCmsMessageRef cmsMsg; + SecArenaPoolRef arena; /* the encoder's arena */ + SecCmsEncoderRef encoder; + CSSM_DATA encoderOut; /* output goes here... */ + bool customCoder; /* unless this is set by + * CMSEncoderSetEncoder */ + CMSCertificateChainMode chainMode; +}; + +static void cmsEncoderInit(CFTypeRef enc); +static void cmsEncoderFinalize(CFTypeRef enc); + +static CFRuntimeClass cmsEncoderRuntimeClass = +{ + 0, /* version */ + "CMSEncoder", + cmsEncoderInit, + NULL, /* copy */ + cmsEncoderFinalize, + NULL, /* equal - just use pointer equality */ + NULL, /* hash, ditto */ + NULL, /* copyFormattingDesc */ + NULL /* copyDebugDesc */ +}; + +void +CmsMessageSetTSACallback(CMSEncoderRef cmsEncoder, SecCmsTSACallback tsaCallback); + +#pragma mark --- Private routines --- + +/* + * Decode a CFStringRef representation of an integer + */ +static int cfStringToNumber( + CFStringRef inStr) +{ + int max = 32; + char buf[max]; + if (!inStr || !CFStringGetCString(inStr, buf, max-1, kCFStringEncodingASCII)) + return -1; + return atoi(buf); +} + +/* + * Encode an integer component of an OID, return resulting number of bytes; + * actual bytes are mallocd and returned in *encodeArray. + */ +static unsigned encodeNumber( + int num, + unsigned char **encodeArray) // mallocd and RETURNED +{ + unsigned char *result; + unsigned dex; + unsigned numDigits = 0; + unsigned scratch; + + /* trival case - 0 maps to 0 */ + if(num == 0) { + *encodeArray = (unsigned char *)malloc(1); + **encodeArray = 0; + return 1; + } + + /* first calculate the number of digits in num, base 128 */ + scratch = (unsigned)num; + while(scratch != 0) { + numDigits++; + scratch >>= 7; + } + + result = (unsigned char *)malloc(numDigits); + scratch = (unsigned)num; + for(dex=0; dex>= 7; + } + + /* all digits except the last one have m.s. bit set */ + for(dex=0; dex<(numDigits - 1); dex++) { + result[dex] |= 0x80; + } + + *encodeArray = result; + return numDigits; +} + +/* + * Given an OID in dotted-decimal string representation, convert to binary + * DER format. Returns a pointer in outOid which the caller must free(), + * as well as the length of the data in outLen. + * Function returns 0 if successful, non-zero otherwise. + */ +static int encodeOid( + const unsigned char *inStr, + unsigned char **outOid, + unsigned int *outLen) +{ + unsigned char **digits = NULL; /* array of char * from encodeNumber */ + unsigned *numDigits = NULL; /* array of unsigned from encodeNumber */ + CFIndex digit; + unsigned numDigitBytes; /* total #of output chars */ + unsigned char firstByte; + unsigned char *outP; + CFIndex numsToProcess; + CFStringRef oidStr = NULL; + CFArrayRef argvRef = NULL; + int num, result = 1; + CFIndex argc; + + /* parse input string into array of substrings */ + if (!inStr || !outOid || !outLen) goto cleanExit; + oidStr = CFStringCreateWithCString(NULL, (const char *)inStr, kCFStringEncodingASCII); + if (!oidStr) goto cleanExit; + argvRef = CFStringCreateArrayBySeparatingStrings(NULL, oidStr, CFSTR(".")); + if (!argvRef) goto cleanExit; + argc = CFArrayGetCount(argvRef); + if (argc < 3) goto cleanExit; + + /* first two numbers in OID munge together */ + num = cfStringToNumber((CFStringRef)CFArrayGetValueAtIndex(argvRef, 0)); + if (num < 0) goto cleanExit; + firstByte = (40 * num); + num = cfStringToNumber((CFStringRef)CFArrayGetValueAtIndex(argvRef, 1)); + if (num < 0) goto cleanExit; + firstByte += num; + numDigitBytes = 1; + + numsToProcess = argc - 2; + if(numsToProcess > 0) { + /* skip this loop in the unlikely event that input is only two numbers */ + digits = (unsigned char **) malloc(numsToProcess * sizeof(unsigned char *)); + numDigits = (unsigned *) malloc(numsToProcess * sizeof(unsigned)); + for(digit=0; digitData. + * + * Function returns 0 if successful, non-zero otherwise. + */ + +static int convertOid( + CFTypeRef inRef, + CSSM_OID *outOid) +{ + if (!inRef || !outOid) + return errSecParam; + + unsigned char *oidData = NULL; + unsigned int oidLen = 0; + + if (CFGetTypeID(inRef) == CFStringGetTypeID()) { + // CFStringRef: OID representation is a dotted-decimal string + CFStringRef inStr = (CFStringRef)inRef; + CFIndex max = CFStringGetLength(inStr) * 3; + char buf[max]; + if (!CFStringGetCString(inStr, buf, max-1, kCFStringEncodingASCII)) + return errSecParam; + + if(encodeOid((unsigned char *)buf, &oidData, &oidLen) != 0) + return errSecParam; + } + else if (CFGetTypeID(inRef) == CFDataGetTypeID()) { + // CFDataRef: OID representation is in binary DER format + CFDataRef inData = (CFDataRef)inRef; + oidLen = (unsigned int) CFDataGetLength(inData); + oidData = (unsigned char *) malloc(oidLen); + memcpy(oidData, CFDataGetBytePtr(inData), oidLen); + } + else { + // Not in a format we understand + return errSecParam; + } + outOid->Length = oidLen; + outOid->Data = (uint8 *)oidData; + return 0; +} + +static CFTypeID cmsEncoderTypeID = _kCFRuntimeNotATypeID; + +/* one time only class init, called via pthread_once() in CMSEncoderGetTypeID() */ +static void cmsEncoderClassInitialize(void) +{ + cmsEncoderTypeID = + _CFRuntimeRegisterClass((const CFRuntimeClass * const)&cmsEncoderRuntimeClass); +} + +/* init called out from _CFRuntimeCreateInstance() */ +static void cmsEncoderInit(CFTypeRef enc) +{ + char *start = ((char *)enc) + sizeof(CFRuntimeBase); + memset(start, 0, sizeof(struct _CMSEncoder) - sizeof(CFRuntimeBase)); +} + +/* + * Dispose of a CMSEncoder. Called out from CFRelease(). + */ +static void cmsEncoderFinalize( + CFTypeRef enc) +{ + CMSEncoderRef cmsEncoder = (CMSEncoderRef)enc; + if(cmsEncoder == NULL) { + return; + } + if(cmsEncoder->eContentType.Data != NULL) { + free(cmsEncoder->eContentType.Data); + } + CFRELEASE(cmsEncoder->signers); + CFRELEASE(cmsEncoder->recipients); + CFRELEASE(cmsEncoder->otherCerts); + if(cmsEncoder->cmsMsg != NULL) { + SecCmsMessageDestroy(cmsEncoder->cmsMsg); + } + if(cmsEncoder->arena != NULL) { + SecArenaPoolFree(cmsEncoder->arena, false); + } + if(cmsEncoder->encoder != NULL) { + /* + * Normally this gets freed in SecCmsEncoderFinish - this is + * an error case. + */ + SecCmsEncoderDestroy(cmsEncoder->encoder); + } +} + +static OSStatus cmsSetupEncoder( + CMSEncoderRef cmsEncoder) +{ + OSStatus ortn; + + ASSERT(cmsEncoder->arena == NULL); + ASSERT(cmsEncoder->encoder == NULL); + + ortn = SecArenaPoolCreate(1024, &cmsEncoder->arena); + if(ortn) { + return cmsRtnToOSStatus(ortn); + } + ortn = SecCmsEncoderCreate(cmsEncoder->cmsMsg, + NULL, NULL, // no callback + &cmsEncoder->encoderOut, // data goes here + cmsEncoder->arena, + NULL, NULL, // no password callback (right?) + NULL, NULL, // decrypt key callback + NULL, NULL, // detached digests + &cmsEncoder->encoder); + if(ortn) { + return cmsRtnToOSStatus(ortn); + } + return errSecSuccess; +} + +/* + * Set up a SecCmsMessageRef for a SignedData creation. + */ +static OSStatus cmsSetupForSignedData( + CMSEncoderRef cmsEncoder) +{ + ASSERT((cmsEncoder->signers != NULL) || (cmsEncoder->otherCerts != NULL)); + + SecCmsContentInfoRef contentInfo = NULL; + SecCmsSignedDataRef signedData = NULL; + OSStatus ortn; + + /* build chain of objects: message->signedData->data */ + if(cmsEncoder->cmsMsg != NULL) { + SecCmsMessageDestroy(cmsEncoder->cmsMsg); + } + cmsEncoder->cmsMsg = SecCmsMessageCreate(NULL); + if(cmsEncoder->cmsMsg == NULL) { + return errSecInternalComponent; + } + + signedData = SecCmsSignedDataCreate(cmsEncoder->cmsMsg); + if(signedData == NULL) { + return errSecInternalComponent; + } + contentInfo = SecCmsMessageGetContentInfo(cmsEncoder->cmsMsg); + ortn = SecCmsContentInfoSetContentSignedData(cmsEncoder->cmsMsg, contentInfo, + signedData); + if(ortn) { + return cmsRtnToOSStatus(ortn); + } + contentInfo = SecCmsSignedDataGetContentInfo(signedData); + if(cmsEncoder->eContentType.Data != NULL) { + /* Override the default eContentType of id-data */ + ortn = SecCmsContentInfoSetContentOther(cmsEncoder->cmsMsg, + contentInfo, + NULL, /* data - provided to encoder, not here */ + cmsEncoder->detachedContent, + &cmsEncoder->eContentType); + } + else { + ortn = SecCmsContentInfoSetContentData(cmsEncoder->cmsMsg, + contentInfo, + NULL, /* data - provided to encoder, not here */ + cmsEncoder->detachedContent); + } + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsContentInfoSetContent*", ortn); + return ortn; + } + + /* optional 'global' (per-SignedData) certs */ + if(cmsEncoder->otherCerts != NULL) { + ortn = SecCmsSignedDataAddCertList(signedData, cmsEncoder->otherCerts); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignedDataAddCertList", ortn); + return ortn; + } + } + + /* SignerInfos, one per signer */ + CFIndex numSigners = 0; + if(cmsEncoder->signers != NULL) { + /* this is optional...in case we're just creating a cert bundle */ + numSigners = CFArrayGetCount(cmsEncoder->signers); + } + CFIndex dex; + SecKeychainRef ourKc = NULL; + SecCertificateRef ourCert = NULL; + SecCmsCertChainMode chainMode = SecCmsCMCertChain; + + switch(cmsEncoder->chainMode) { + case kCMSCertificateNone: + chainMode = SecCmsCMNone; + break; + case kCMSCertificateSignerOnly: + chainMode = SecCmsCMCertOnly; + break; + case kCMSCertificateChainWithRoot: + chainMode = SecCmsCMCertChainWithRoot; + break; + default: + break; + } + for(dex=0; dexsigners, dex); + ortn = SecIdentityCopyCertificate(ourId, &ourCert); + if(ortn) { + CSSM_PERROR("SecIdentityCopyCertificate", ortn); + break; + } + ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)ourCert, &ourKc); + if(ortn) { + CSSM_PERROR("SecKeychainItemCopyKeychain", ortn); + break; + } + signerInfo = SecCmsSignerInfoCreate(cmsEncoder->cmsMsg, ourId, SEC_OID_SHA1); + if (signerInfo == NULL) { + ortn = errSecInternalComponent; + break; + } + + /* we want the cert chain included for this one */ + /* NOTE the usage parameter is currently unused by the SMIME lib */ + ortn = SecCmsSignerInfoIncludeCerts(signerInfo, chainMode, + certUsageEmailSigner); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignerInfoIncludeCerts", ortn); + break; + } + + /* other options */ + if(cmsEncoder->signedAttributes & kCMSAttrSmimeCapabilities) { + ortn = SecCmsSignerInfoAddSMIMECaps(signerInfo); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignerInfoAddSMIMEEncKeyPrefs", ortn); + break; + } + } + if(cmsEncoder->signedAttributes & kCMSAttrSmimeEncryptionKeyPrefs) { + ortn = SecCmsSignerInfoAddSMIMEEncKeyPrefs(signerInfo, ourCert, ourKc); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignerInfoAddSMIMEEncKeyPrefs", ortn); + break; + } + } + if(cmsEncoder->signedAttributes & kCMSAttrSmimeMSEncryptionKeyPrefs) { + ortn = SecCmsSignerInfoAddMSSMIMEEncKeyPrefs(signerInfo, ourCert, ourKc); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignerInfoAddMSSMIMEEncKeyPrefs", ortn); + break; + } + } + if(cmsEncoder->signedAttributes & kCMSAttrSigningTime) { + if (cmsEncoder->signingTime == 0) + cmsEncoder->signingTime = CFAbsoluteTimeGetCurrent(); + ortn = SecCmsSignerInfoAddSigningTime(signerInfo, cmsEncoder->signingTime); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignerInfoAddSigningTime", ortn); + break; + } + } + + ortn = SecCmsSignedDataAddSignerInfo(signedData, signerInfo); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsSignedDataAddSignerInfo", ortn); + break; + } + + CFRELEASE(ourKc); + CFRELEASE(ourCert); + ourKc = NULL; + ourCert = NULL; + } + if(ortn) { + CFRELEASE(ourKc); + CFRELEASE(ourCert); + } + return ortn; +} + +/* + * Set up a SecCmsMessageRef for a EnvelopedData creation. + */ +static OSStatus cmsSetupForEnvelopedData( + CMSEncoderRef cmsEncoder) +{ + ASSERT(cmsEncoder->op == EO_Encrypt); + ASSERT(cmsEncoder->recipients != NULL); + + SecCmsContentInfoRef contentInfo = NULL; + SecCmsEnvelopedDataRef envelopedData = NULL; + SECOidTag algorithmTag; + int keySize; + OSStatus ortn; + + /* + * Find encryption algorithm...unfortunately we need a NULL-terminated array + * of SecCertificateRefs for this. + */ + CFIndex numCerts = CFArrayGetCount(cmsEncoder->recipients); + CFIndex dex; + SecCertificateRef *certArray = (SecCertificateRef *)malloc( + (numCerts+1) * sizeof(SecCertificateRef)); + + for(dex=0; dexrecipients, dex); + } + certArray[numCerts] = NULL; + ortn = SecSMIMEFindBulkAlgForRecipients(certArray, &algorithmTag, &keySize); + free(certArray); + if(ortn) { + CSSM_PERROR("SecSMIMEFindBulkAlgForRecipients", ortn); + return ortn; + } + + /* build chain of objects: message->envelopedData->data */ + if(cmsEncoder->cmsMsg != NULL) { + SecCmsMessageDestroy(cmsEncoder->cmsMsg); + } + cmsEncoder->cmsMsg = SecCmsMessageCreate(NULL); + if(cmsEncoder->cmsMsg == NULL) { + return errSecInternalComponent; + } + envelopedData = SecCmsEnvelopedDataCreate(cmsEncoder->cmsMsg, + algorithmTag, keySize); + if(envelopedData == NULL) { + return errSecInternalComponent; + } + contentInfo = SecCmsMessageGetContentInfo(cmsEncoder->cmsMsg); + ortn = SecCmsContentInfoSetContentEnvelopedData(cmsEncoder->cmsMsg, + contentInfo, envelopedData); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsContentInfoSetContentEnvelopedData", ortn); + return ortn; + } + contentInfo = SecCmsEnvelopedDataGetContentInfo(envelopedData); + if(cmsEncoder->eContentType.Data != NULL) { + /* Override the default ContentType of id-data */ + ortn = SecCmsContentInfoSetContentOther(cmsEncoder->cmsMsg, + contentInfo, + NULL, /* data - provided to encoder, not here */ + FALSE, /* detachedContent */ + &cmsEncoder->eContentType); + } + else { + ortn = SecCmsContentInfoSetContentData(cmsEncoder->cmsMsg, + contentInfo, + NULL /* data - provided to encoder, not here */, + cmsEncoder->detachedContent); + } + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsContentInfoSetContentData*", ortn); + return ortn; + } + + /* + * create & attach recipient information, one for each recipient + */ + for(dex=0; dexrecipients, dex); + recipientInfo = SecCmsRecipientInfoCreate(cmsEncoder->cmsMsg, thisRecip); + ortn = SecCmsEnvelopedDataAddRecipient(envelopedData, recipientInfo); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsEnvelopedDataAddRecipient", ortn); + return ortn; + } + } + return errSecSuccess; +} + +/* + * Set up cmsMsg. Called from either the first call to CMSEncoderUpdateContent, or + * from CMSEncodeGetCmsMessage(). + */ +static OSStatus cmsSetupCmsMsg( + CMSEncoderRef cmsEncoder) +{ + ASSERT(cmsEncoder != NULL); + ASSERT(cmsEncoder->encState == ES_Init); + + /* figure out what high-level operation we're doing */ + if((cmsEncoder->signers != NULL) || (cmsEncoder->otherCerts != NULL)) { + if(cmsEncoder->recipients != NULL) { + cmsEncoder->op = EO_SignEncrypt; + } + else { + cmsEncoder->op = EO_Sign; + } + } + else if(cmsEncoder->recipients != NULL) { + cmsEncoder->op = EO_Encrypt; + } + else { + dprintf("CMSEncoderUpdateContent: nothing to do\n"); + return errSecParam; + } + + OSStatus ortn = errSecSuccess; + + switch(cmsEncoder->op) { + case EO_Sign: + case EO_SignEncrypt: + /* If we're signing & encrypting, do the signing first */ + ortn = cmsSetupForSignedData(cmsEncoder); + break; + case EO_Encrypt: + ortn = cmsSetupForEnvelopedData(cmsEncoder); + break; + } + cmsEncoder->encState = ES_Msg; + return ortn; +} + +/* + * ASN.1 template for decoding a ContentInfo. + */ +typedef struct { + CSSM_OID contentType; + CSSM_DATA content; +} SimpleContentInfo; + +static const SecAsn1Template cmsSimpleContentInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SimpleContentInfo) }, + { SEC_ASN1_OBJECT_ID, offsetof(SimpleContentInfo, contentType) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SimpleContentInfo, content), + kSecAsn1AnyTemplate }, + { 0, } +}; + +/* + * Obtain the content of a contentInfo, This basically strips off the contentType OID + * and returns its ASN_ANY content, allocated the provided coder's memory space. + */ +static OSStatus cmsContentInfoContent( + SecAsn1CoderRef asn1Coder, + const CSSM_DATA *contentInfo, + CSSM_DATA *content) /* RETURNED */ +{ + OSStatus ortn; + SimpleContentInfo decodedInfo; + + memset(&decodedInfo, 0, sizeof(decodedInfo)); + ortn = SecAsn1DecodeData(asn1Coder, contentInfo, + cmsSimpleContentInfoTemplate, &decodedInfo); + if(ortn) { + return ortn; + } + if(decodedInfo.content.Data == NULL) { + dprintf("***Error decoding contentInfo: no content\n"); + return errSecInternalComponent; + } + *content = decodedInfo.content; + return errSecSuccess; +} + +#pragma mark --- Start of Public API --- + +CFTypeID CMSEncoderGetTypeID(void) +{ + static pthread_once_t once = PTHREAD_ONCE_INIT; + + if(cmsEncoderTypeID == _kCFRuntimeNotATypeID) { + pthread_once(&once, &cmsEncoderClassInitialize); + } + return cmsEncoderTypeID; +} + +/* + * Create a CMSEncoder. Result must eventually be freed via CFRelease(). + */ +OSStatus CMSEncoderCreate( + CMSEncoderRef *cmsEncoderOut) /* RETURNED */ +{ + CMSEncoderRef cmsEncoder = NULL; + + uint32_t extra = sizeof(*cmsEncoder) - sizeof(cmsEncoder->base); + cmsEncoder = (CMSEncoderRef)_CFRuntimeCreateInstance(NULL, CMSEncoderGetTypeID(), + extra, NULL); + if(cmsEncoder == NULL) { + return errSecAllocate; + } + cmsEncoder->encState = ES_Init; + cmsEncoder->chainMode = kCMSCertificateChain; + *cmsEncoderOut = cmsEncoder; + return errSecSuccess; +} + +#pragma mark --- Getters & Setters --- + +/* + * Specify signers of the CMS message; implies that the message will be signed. + */ +OSStatus CMSEncoderAddSigners( + CMSEncoderRef cmsEncoder, + CFTypeRef signerOrArray) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + return cmsAppendToArray(signerOrArray, &cmsEncoder->signers, SecIdentityGetTypeID()); +} + +/* + * Obtain an array of signers as specified in CMSEncoderSetSigners(). + */ +OSStatus CMSEncoderCopySigners( + CMSEncoderRef cmsEncoder, + CFArrayRef *signers) +{ + if((cmsEncoder == NULL) || (signers == NULL)) { + return errSecParam; + } + if(cmsEncoder->signers != NULL) { + CFRetain(cmsEncoder->signers); + } + *signers = cmsEncoder->signers; + return errSecSuccess; +} + +/* + * Specify recipients of the message. Implies that the message will be encrypted. + */ +OSStatus CMSEncoderAddRecipients( + CMSEncoderRef cmsEncoder, + CFTypeRef recipientOrArray) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + return cmsAppendToArray(recipientOrArray, &cmsEncoder->recipients, + SecCertificateGetTypeID()); +} + +/* + * Obtain an array of recipients as specified in CMSEncoderSetRecipients(). + */ +OSStatus CMSEncoderCopyRecipients( + CMSEncoderRef cmsEncoder, + CFArrayRef *recipients) +{ + if((cmsEncoder == NULL) || (recipients == NULL)) { + return errSecParam; + } + if(cmsEncoder->recipients != NULL) { + CFRetain(cmsEncoder->recipients); + } + *recipients = cmsEncoder->recipients; + return errSecSuccess; +} + +/* + * Specify additional certs to include in a signed message. + */ +OSStatus CMSEncoderAddSupportingCerts( + CMSEncoderRef cmsEncoder, + CFTypeRef certOrArray) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + return cmsAppendToArray(certOrArray, &cmsEncoder->otherCerts, + SecCertificateGetTypeID()); +} + +/* + * Obtain the SecCertificates provided in CMSEncoderAddSupportingCerts(). + */ +OSStatus CMSEncoderCopySupportingCerts( + CMSEncoderRef cmsEncoder, + CFArrayRef *certs) /* RETURNED */ +{ + if((cmsEncoder == NULL) || (certs == NULL)) { + return errSecParam; + } + if(cmsEncoder->otherCerts != NULL) { + CFRetain(cmsEncoder->otherCerts); + } + *certs = cmsEncoder->otherCerts; + return errSecSuccess; +} + +OSStatus CMSEncoderSetHasDetachedContent( + CMSEncoderRef cmsEncoder, + Boolean detachedContent) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + cmsEncoder->detachedContent = detachedContent; + return errSecSuccess; +} + +OSStatus CMSEncoderGetHasDetachedContent( + CMSEncoderRef cmsEncoder, + Boolean *detachedContent) /* RETURNED */ +{ + if((cmsEncoder == NULL) || (detachedContent == NULL)) { + return errSecParam; + } + *detachedContent = cmsEncoder->detachedContent; + return errSecSuccess; +} + +/* + * Optionally specify an eContentType OID for the inner EncapsulatedData for + * a signed message. The default eContentType, used of this function is not + * called, is id-data. + */ +OSStatus CMSEncoderSetEncapsulatedContentType( + CMSEncoderRef cmsEncoder, + const CSSM_OID *eContentType) +{ + if((cmsEncoder == NULL) || (eContentType == NULL)) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + + CSSM_OID *ecOid = &cmsEncoder->eContentType; + if(ecOid->Data != NULL) { + free(ecOid->Data); + } + cmsCopyCmsData(eContentType, ecOid); + return errSecSuccess; +} + +OSStatus CMSEncoderSetEncapsulatedContentTypeOID( + CMSEncoderRef cmsEncoder, + CFTypeRef eContentTypeOID) +{ + // convert eContentTypeOID to a CSSM_OID + CSSM_OID contentType = { 0, NULL }; + if (!eContentTypeOID || convertOid(eContentTypeOID, &contentType) != 0) + return errSecParam; + OSStatus result = CMSEncoderSetEncapsulatedContentType(cmsEncoder, &contentType); + if (contentType.Data) + free(contentType.Data); + return result; +} + +/* + * Obtain the eContentType OID specified in CMSEncoderSetEncapsulatedContentType(). + */ +OSStatus CMSEncoderCopyEncapsulatedContentType( + CMSEncoderRef cmsEncoder, + CFDataRef *eContentType) +{ + if((cmsEncoder == NULL) || (eContentType == NULL)) { + return errSecParam; + } + + CSSM_OID *ecOid = &cmsEncoder->eContentType; + if(ecOid->Data == NULL) { + *eContentType = NULL; + } + else { + *eContentType = CFDataCreate(NULL, ecOid->Data, ecOid->Length); + } + return errSecSuccess; +} + +/* + * Optionally specify signed attributes. Only meaningful when creating a + * signed message. If this is called, it must be called before + * CMSEncoderUpdateContent(). + */ +OSStatus CMSEncoderAddSignedAttributes( + CMSEncoderRef cmsEncoder, + CMSSignedAttributes signedAttributes) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + cmsEncoder->signedAttributes = signedAttributes; + return errSecSuccess; +} + +/* + * Set the signing time for a CMSEncoder. + * This is only used if the kCMSAttrSigningTime attribute is included. + */ +OSStatus CMSEncoderSetSigningTime( + CMSEncoderRef cmsEncoder, + CFAbsoluteTime time) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + cmsEncoder->signingTime = time; + return errSecSuccess; +} + + +OSStatus CMSEncoderSetCertificateChainMode( + CMSEncoderRef cmsEncoder, + CMSCertificateChainMode chainMode) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + if(cmsEncoder->encState != ES_Init) { + return errSecParam; + } + switch(chainMode) { + case kCMSCertificateNone: + case kCMSCertificateSignerOnly: + case kCMSCertificateChain: + case kCMSCertificateChainWithRoot: + break; + default: + return errSecParam; + } + cmsEncoder->chainMode = chainMode; + return errSecSuccess; +} + +OSStatus CMSEncoderGetCertificateChainMode( + CMSEncoderRef cmsEncoder, + CMSCertificateChainMode *chainModeOut) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + *chainModeOut = cmsEncoder->chainMode; + return errSecSuccess; +} + +void +CmsMessageSetTSACallback(CMSEncoderRef cmsEncoder, SecCmsTSACallback tsaCallback) +{ + if (cmsEncoder->cmsMsg) + SecCmsMessageSetTSACallback(cmsEncoder->cmsMsg, tsaCallback); +} + +void +CmsMessageSetTSAContext(CMSEncoderRef cmsEncoder, CFTypeRef tsaContext) +{ + if (cmsEncoder->cmsMsg) + SecCmsMessageSetTSAContext(cmsEncoder->cmsMsg, tsaContext); +} + +#pragma mark --- Action --- + +/* + * Feed content bytes into the encoder. + * Can be called multiple times. + * No 'setter' routines can be called after this function has been called. + */ +OSStatus CMSEncoderUpdateContent( + CMSEncoderRef cmsEncoder, + const void *content, + size_t contentLen) +{ + if(cmsEncoder == NULL) { + return errSecParam; + } + + OSStatus ortn = errSecSuccess; + switch(cmsEncoder->encState) { + case ES_Init: + /* + * First time thru: do the CmsMsg setup. + */ + ortn = cmsSetupCmsMsg(cmsEncoder); + if(ortn) { + return ortn; + } + /* fall thru to set up the encoder */ + + case ES_Msg: + /* We have a cmsMsg but no encoder; create one */ + ASSERT(cmsEncoder->cmsMsg != NULL); + ASSERT(cmsEncoder->encoder == NULL); + ortn = cmsSetupEncoder(cmsEncoder); + if(ortn) { + return ortn; + } + /* only legal calls now are update and finalize */ + cmsEncoder->encState = ES_Updating; + break; + + case ES_Updating: + ASSERT(cmsEncoder->encoder != NULL); + break; + + case ES_Final: + /* Too late for another update */ + return errSecParam; + + default: + return errSecInternalComponent; + } + + /* FIXME - CFIndex same size as size_t on 64bit? */ + ortn = SecCmsEncoderUpdate(cmsEncoder->encoder, content, (CFIndex)contentLen); + if(ortn) { + ortn = cmsRtnToOSStatus(ortn); + CSSM_PERROR("SecCmsEncoderUpdate", ortn); + } + return ortn; +} + +/* + * Finish encoding the message and obtain the encoded result. + * Caller must CFRelease the result. + */ +OSStatus CMSEncoderCopyEncodedContent( + CMSEncoderRef cmsEncoder, + CFDataRef *encodedContent) +{ + if((cmsEncoder == NULL) || (encodedContent == NULL)) { + return errSecParam; + } + + OSStatus ortn; + + switch(cmsEncoder->encState) { + case ES_Updating: + /* normal termination */ + break; + case ES_Final: + /* already been called */ + return errSecParam; + case ES_Msg: + case ES_Init: + /* + * The only time these are legal is when we're doing a SignedData + * with certificates only (no signers, no content). + */ + if((cmsEncoder->signers != NULL) || + (cmsEncoder->recipients != NULL) || + (cmsEncoder->otherCerts == NULL)) { + return errSecParam; + } + + /* Set up for certs only */ + ortn = cmsSetupForSignedData(cmsEncoder); + if(ortn) { + return ortn; + } + /* and an encoder */ + ortn = cmsSetupEncoder(cmsEncoder); + if(ortn) { + return ortn; + } + break; + } + + + ASSERT(cmsEncoder->encoder != NULL); + ortn = SecCmsEncoderFinish(cmsEncoder->encoder); + /* regardless of the outcome, the encoder itself has been freed */ + cmsEncoder->encoder = NULL; + if(ortn) { + return cmsRtnToOSStatus(ortn); + } + cmsEncoder->encState = ES_Final; + + if((cmsEncoder->encoderOut.Data == NULL) && !cmsEncoder->customCoder) { + /* not sure how this could happen... */ + dprintf("Successful encode, but no data\n"); + return errSecInternalComponent; + } + if(cmsEncoder->customCoder) { + /* we're done */ + *encodedContent = NULL; + return errSecSuccess; + } + + /* in two out of three cases, we're done */ + switch(cmsEncoder->op) { + case EO_Sign: + case EO_Encrypt: + *encodedContent = CFDataCreate(NULL, (const UInt8 *)cmsEncoder->encoderOut.Data, + cmsEncoder->encoderOut.Length); + return errSecSuccess; + case EO_SignEncrypt: + /* proceed, more work to do */ + break; + } + + /* + * Signing & encrypting. + * Due to bugs in the libsecurity_smime encoder, it can't encode nested + * ContentInfos in one shot. So we do another pass, specifying the SignedData + * inside of the ContentInfo we just created as the data to encrypt. + */ + SecAsn1CoderRef asn1Coder = NULL; + CSSM_DATA signedData = {0, NULL}; + + ortn = SecAsn1CoderCreate(&asn1Coder); + if(ortn) { + return ortn; + } + ortn = cmsContentInfoContent(asn1Coder, &cmsEncoder->encoderOut, &signedData); + if(ortn) { + goto errOut; + } + + /* now just encrypt that, one-shot */ + ortn = CMSEncode(NULL, /* no signers this time */ + cmsEncoder->recipients, + &CSSMOID_PKCS7_SignedData, /* fake out encoder so it doesn't try to actually + * encode the signedData - this asserts the + * SEC_OID_OTHER OID tag in the EnvelopedData's + * ContentInfo */ + FALSE, /* detachedContent */ + kCMSAttrNone, /* signedAttributes - none this time */ + signedData.Data, signedData.Length, + encodedContent); + +errOut: + if(asn1Coder) { + SecAsn1CoderRelease(asn1Coder); + } + return ortn; +} + +#pragma mark --- High-level API --- + +/* + * High-level, one-shot encoder function. + */ +OSStatus CMSEncode( + CFTypeRef signers, + CFTypeRef recipients, + const CSSM_OID *eContentType, + Boolean detachedContent, + CMSSignedAttributes signedAttributes, + const void *content, + size_t contentLen, + CFDataRef *encodedContent) /* RETURNED */ +{ + if((signers == NULL) && (recipients == NULL)) { + return errSecParam; + } + if(encodedContent == NULL) { + return errSecParam; + } + + CMSEncoderRef cmsEncoder; + OSStatus ortn; + + /* set up the encoder */ + ortn = CMSEncoderCreate(&cmsEncoder); + if(ortn) { + return ortn; + } + + /* subsequent errors to errOut: */ + if(signers) { + ortn = CMSEncoderAddSigners(cmsEncoder, signers); + if(ortn) { + goto errOut; + } + } + if(recipients) { + ortn = CMSEncoderAddRecipients(cmsEncoder, recipients); + if(ortn) { + goto errOut; + } + } + if(eContentType) { + ortn = CMSEncoderSetEncapsulatedContentType(cmsEncoder, eContentType); + if(ortn) { + goto errOut; + } + } + if(detachedContent) { + ortn = CMSEncoderSetHasDetachedContent(cmsEncoder, detachedContent); + if(ortn) { + goto errOut; + } + } + if(signedAttributes) { + ortn = CMSEncoderAddSignedAttributes(cmsEncoder, signedAttributes); + if(ortn) { + goto errOut; + } + } + /* GO */ + ortn = CMSEncoderUpdateContent(cmsEncoder, content, contentLen); + if(ortn) { + goto errOut; + } + ortn = CMSEncoderCopyEncodedContent(cmsEncoder, encodedContent); + +errOut: + CFRelease(cmsEncoder); + return ortn; +} + +OSStatus CMSEncodeContent( + CFTypeRef signers, + CFTypeRef recipients, + CFTypeRef eContentTypeOID, + Boolean detachedContent, + CMSSignedAttributes signedAttributes, + const void *content, + size_t contentLen, + CFDataRef *encodedContentOut) /* RETURNED */ +{ + // convert eContentTypeOID to a CSSM_OID + CSSM_OID contentType = { 0, NULL }; + if (eContentTypeOID && convertOid(eContentTypeOID, &contentType) != 0) + return errSecParam; + const CSSM_OID *contentTypePtr = (eContentTypeOID) ? &contentType : NULL; + OSStatus result = CMSEncode(signers, recipients, contentTypePtr, + detachedContent, signedAttributes, + content, contentLen, encodedContentOut); + if (contentType.Data) + free(contentType.Data); + return result; +} + +#pragma mark --- SPI routines declared in CMSPrivate.h --- + +/* + * Obtain the SecCmsMessageRef associated with a CMSEncoderRef. + * If we don't have a SecCmsMessageRef yet, we create one now. + * This is the only place where we go to state ES_Msg. + */ +OSStatus CMSEncoderGetCmsMessage( + CMSEncoderRef cmsEncoder, + SecCmsMessageRef *cmsMessage) /* RETURNED */ +{ + if((cmsEncoder == NULL) || (cmsMessage == NULL)) { + return errSecParam; + } + if(cmsEncoder->cmsMsg != NULL) { + ASSERT(cmsEncoder->encState != ES_Init); + *cmsMessage = cmsEncoder->cmsMsg; + return errSecSuccess; + } + + OSStatus ortn = cmsSetupCmsMsg(cmsEncoder); + if(ortn) { + return ortn; + } + *cmsMessage = cmsEncoder->cmsMsg; + + /* Don't set up encoder yet; caller might do that via CMSEncoderSetEncoder */ + cmsEncoder->encState = ES_Msg; + return errSecSuccess; +} + +/* + * Optionally specify a SecCmsEncoderRef to use with a CMSEncoderRef. + * If this is called, it must be called before the first call to + * CMSEncoderUpdateContent(). The CMSEncoderRef takes ownership of the + * incoming SecCmsEncoderRef. + */ +OSStatus CMSEncoderSetEncoder( + CMSEncoderRef cmsEncoder, + SecCmsEncoderRef encoder) +{ + if((cmsEncoder == NULL) || (encoder == NULL)) { + return errSecParam; + } + + OSStatus ortn; + + switch(cmsEncoder->encState) { + case ES_Init: + /* No message, no encoder */ + ASSERT(cmsEncoder->cmsMsg == NULL); + ASSERT(cmsEncoder->encoder == NULL); + ortn = cmsSetupCmsMsg(cmsEncoder); + if(ortn) { + return ortn; + } + /* drop thru to set encoder */ + case ES_Msg: + /* cmsMsg but no encoder */ + ASSERT(cmsEncoder->cmsMsg != NULL); + ASSERT(cmsEncoder->encoder == NULL); + cmsEncoder->encoder = encoder; + cmsEncoder->encState = ES_Updating; + cmsEncoder->customCoder = true; /* we won't see data */ + return errSecSuccess; + default: + /* no can do, too late */ + return errSecParam; + } +} + +/* + * Obtain the SecCmsEncoderRef associated with a CMSEncoderRef. + * Returns a NULL SecCmsEncoderRef if neither CMSEncoderSetEncoder nor + * CMSEncoderUpdateContent() has been called. + * The CMSEncoderRef retains ownership of the SecCmsEncoderRef. + */ +OSStatus CMSEncoderGetEncoder( + CMSEncoderRef cmsEncoder, + SecCmsEncoderRef *encoder) /* RETURNED */ +{ + if((cmsEncoder == NULL) || (encoder == NULL)) { + return errSecParam; + } + + /* any state, whether we have an encoder or not is OK */ + *encoder = cmsEncoder->encoder; + return errSecSuccess; +} + +#include + +/* + * Obtain the timestamp of signer 'signerIndex' of a CMS message, if + * present. This timestamp is an authenticated timestamp provided by + * a timestamping authority. + * + * Returns errSecParam if the CMS message was not signed or if signerIndex + * is greater than the number of signers of the message minus one. + * + * This cannot be called until after CMSEncoderCopyEncodedContent() is called. + */ +OSStatus CMSEncoderCopySignerTimestamp( + CMSEncoderRef cmsEncoder, + size_t signerIndex, /* usually 0 */ + CFAbsoluteTime *timestamp) /* RETURNED */ +{ + return CMSEncoderCopySignerTimestampWithPolicy( + cmsEncoder, + NULL, + signerIndex, + timestamp); +} + +OSStatus CMSEncoderCopySignerTimestampWithPolicy( + CMSEncoderRef cmsEncoder, + CFTypeRef timeStampPolicy, + size_t signerIndex, /* usually 0 */ + CFAbsoluteTime *timestamp) /* RETURNED */ +{ + OSStatus status = errSecParam; + SecCmsMessageRef cmsg; + SecCmsSignedDataRef signedData = NULL; + int numContentInfos = 0; + + require(cmsEncoder && timestamp, xit); + require_noerr(CMSEncoderGetCmsMessage(cmsEncoder, &cmsg), xit); + numContentInfos = SecCmsMessageContentLevelCount(cmsg); + for (int dex = 0; !signedData && dex < numContentInfos; dex++) + { + SecCmsContentInfoRef ci = SecCmsMessageContentLevel(cmsg, dex); + SECOidTag tag = SecCmsContentInfoGetContentTypeTag(ci); + if (tag == SEC_OID_PKCS7_SIGNED_DATA) + if ((signedData = SecCmsSignedDataRef(SecCmsContentInfoGetContent(ci)))) + if (SecCmsSignerInfoRef signerInfo = SecCmsSignedDataGetSignerInfo(signedData, (int)signerIndex)) + { + status = SecCmsSignerInfoGetTimestampTimeWithPolicy(signerInfo, timeStampPolicy, timestamp); + break; + } + } + +xit: + return status; +}