/* * 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; }