--- /dev/null
+/*
+ * cmstool.cpp - manipluate CMS messages, intended to be an alternate for the
+ * currently useless cms command in /usr/bin/security
+ */
+
+#include <Security/Security.h>
+#include <security_cdsa_utils/cuFileIo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <utilLib/common.h>
+#include <security_cdsa_utils/cuFileIo.h>
+#include <security_cdsa_utils/cuPrintCert.h>
+#include <clAppUtils/identPicker.h>
+#include <clAppUtils/sslAppUtils.h>
+#include <security_cdsa_utils/cuOidParser.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <Security/SecCmsEncoder.h>
+#include <Security/SecCmsDecoder.h>
+#include <Security/SecCmsEncryptedData.h>
+#include <Security/SecCmsEnvelopedData.h>
+#include <Security/SecCmsMessage.h>
+#include <Security/SecCmsRecipientInfo.h>
+#include <Security/SecCmsSignedData.h>
+#include <Security/SecCmsSignerInfo.h>
+#include <Security/SecCmsContentInfo.h>
+#include <Security/SecCmsDigestContext.h>
+#include <Security/SecTrustPriv.h>
+#include <Security/SecKeychainItemPriv.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecSMIME.h>
+#include <Security/oidsattr.h>
+#include <Security/SecAsn1Coder.h>
+#include <Security/secasn1t.h>
+#include <Security/SecAsn1Templates.h>
+
+static void usage(char **argv)
+{
+ printf("Usage: %s cmd [option ...]\n", argv[0]);
+ printf("cmd values:\n");
+ printf(" sign -- create signedData\n");
+ printf(" envel -- create envelopedData\n");
+ printf(" signEnv -- create nested EnvelopedData(signedData(data))\n");
+ printf(" parse -- parse a CMS message file\n");
+ printf("Options:\n");
+ printf(" -i infile\n");
+ printf(" -o outfile\n");
+ printf(" -k keychain -- Keychain to search for certs\n");
+ printf(" -p -- Use identity picker\n");
+ printf(" -r recipient -- specify recipient of enveloped data\n");
+ printf(" -c -- parse signer cert\n");
+ printf(" -v sign|encr -- verify message is signed/encrypted\n");
+ printf(" -e eContentType -- a(uthData)|r(keyData)\n");
+ printf(" -d detached -- infile contains detached content (sign only)\n");
+ printf(" -D detachedContent -- detached content (parse only)\n");
+ printf(" -q -- quiet\n");
+ exit(1);
+}
+
+/* high level op */
+typedef enum {
+ CTO_Sign,
+ CTO_Envelop,
+ CTO_SignEnvelop,
+ CTO_Parse
+} CT_Op;
+
+/* to verify */
+typedef enum {
+ CTV_None,
+ CTV_Sign,
+ CTV_Envelop,
+ CTV_SignEnvelop
+} CT_Vfy;
+
+/* additional OIDS to specify as eContentType */
+#define OID_PKINIT 0x2B, 6, 1, 5, 2, 3
+#define OID_PKINIT_LEN 6
+
+static const uint8 OID_PKINIT_AUTH_DATA[] = {OID_PKINIT, 1};
+static const uint8 OID_PKINIT_DH_KEY_DATA[] = {OID_PKINIT, 2};
+static const uint8 OID_PKINIT_RKEY_DATA[] = {OID_PKINIT, 3};
+static const uint8 OID_PKINIT_KP_CLIENTAUTH[] = {OID_PKINIT, 3};
+static const uint8 OID_PKINIT_KPKDC[] = {OID_PKINIT, 5};
+
+static const CSSM_OID CSSMOID_PKINIT_AUTH_DATA =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_AUTH_DATA};
+static const CSSM_OID CSSMOID_PKINIT_DH_KEY_DATA =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_DH_KEY_DATA};
+static const CSSM_OID CSSMOID_PKINIT_RKEY_DATA =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_RKEY_DATA};
+static const CSSM_OID CSSMOID_PKINIT_KP_CLIENTAUTH =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_KP_CLIENTAUTH};
+static const CSSM_OID CSSMOID_PKINIT_KPKDC =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_KPKDC};
+
+typedef struct {
+ CSSM_OID contentType;
+ CSSM_DATA content;
+} SimpleContentInfo;
+
+const SecAsn1Template SimpleContentInfoTemplate[] = {
+ { 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 a mallocd copy of the ASN_ANY content.
+ */
+static OSStatus ContentInfoContent(
+ const unsigned char *contentInfo,
+ unsigned contentInfoLen,
+ unsigned char **content, /* mallocd and RETURNED */
+ unsigned *contentLen) /* RETURNED */
+{
+ SecAsn1CoderRef coder = NULL;
+ OSStatus ortn;
+ SimpleContentInfo decodedInfo;
+
+ ortn = SecAsn1CoderCreate(&coder);
+ if(ortn) {
+ return ortn;
+ }
+ memset(&decodedInfo, 0, sizeof(decodedInfo));
+ ortn = SecAsn1Decode(coder, contentInfo, contentInfoLen,
+ SimpleContentInfoTemplate, &decodedInfo);
+ if(ortn) {
+ goto errOut;
+ }
+ if(decodedInfo.content.Data == NULL) {
+ printf("***Error decoding contentInfo: no content\n");
+ ortn = internalComponentErr;
+ goto errOut;
+ }
+ *content = (unsigned char *)malloc(decodedInfo.content.Length);
+ memmove(*content, decodedInfo.content.Data, decodedInfo.content.Length);
+ *contentLen = decodedInfo.content.Length;
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ortn;
+}
+
+/*
+ * Find a cert in specified keychain or keychain list matching specified
+ * email address. We happen to knopw that the email address is stored with the
+ * kSecKeyAlias attribute.
+ */
+static OSStatus findCert(
+ const char *emailAddress,
+ CFTypeRef kcArArray, // kc, array, or even NULL
+ SecCertificateRef *cert)
+{
+ OSStatus ortn;
+ SecKeychainSearchRef srch;
+ SecKeychainAttributeList attrList;
+ SecKeychainAttribute attr;
+
+ attr.tag = kSecKeyAlias;
+ attr.length = strlen(emailAddress);
+ attr.data = (void *)emailAddress;
+ attrList.count = 1;
+ attrList.attr = &attr;
+
+ ortn = SecKeychainSearchCreateFromAttributes(kcArArray,
+ kSecCertificateItemClass,
+ &attrList,
+ &srch);
+ if(ortn) {
+ cssmPerror("SecKeychainSearchCreateFromAttributes", ortn);
+ return ortn;
+ }
+
+ ortn = SecKeychainSearchCopyNext(srch, (SecKeychainItemRef *)cert);
+ if(ortn) {
+ printf("***No certs founmd matching recipient %s. Aborting.\n",
+ emailAddress);
+ return ortn;
+ }
+ CFRelease(srch);
+ return noErr;
+}
+
+static void evalSecTrust(
+ SecTrustRef secTrust,
+ bool quiet)
+{
+ OSStatus ortn;
+ SecTrustResultType secTrustResult;
+
+ ortn = SecTrustEvaluate(secTrust, &secTrustResult);
+ if(ortn) {
+ /* should never happen */
+ cssmPerror("SecTrustEvaluate", ortn);
+ return;
+ }
+ switch(secTrustResult) {
+ case kSecTrustResultUnspecified:
+ /* cert chain valid, no special UserTrust assignments */
+ case kSecTrustResultProceed:
+ /* cert chain valid AND user explicitly trusts this */
+ if(!quiet) {
+ fprintf(stderr, "Successful\n");
+ }
+ return;
+ case kSecTrustResultDeny:
+ case kSecTrustResultConfirm:
+ /*
+ * Cert chain may well have verified OK, but user has flagged
+ * one of these certs as untrustable.
+ */
+ printf("Not trusted per user-specified Trust level\n");
+ return;
+ default:
+ {
+ /* get low-level TP error */
+ OSStatus tpStatus;
+ ortn = SecTrustGetCssmResultCode(secTrust, &tpStatus);
+ if(ortn) {
+ cssmPerror("SecTrustGetCssmResultCode", ortn);
+ return;
+ }
+ switch(tpStatus) {
+ case CSSMERR_TP_INVALID_ANCHOR_CERT:
+ fprintf(stderr, "Untrusted root\n");
+ return;
+ case CSSMERR_TP_NOT_TRUSTED:
+ /* no root, not even in implicit SSL roots */
+ fprintf(stderr, "No root cert found\n");
+ return;
+ case CSSMERR_TP_CERT_EXPIRED:
+ fprintf(stderr, "Expired cert\n");
+ return;
+ case CSSMERR_TP_CERT_NOT_VALID_YET:
+ fprintf(stderr, "Cert not valid yet\n");
+ break;
+ default:
+ printf("Other cert failure: ");
+ cssmPerror("", tpStatus);
+ return;
+ }
+ }
+ } /* SecTrustEvaluate error */
+
+}
+static OSStatus parseSignedData(
+ SecCmsSignedDataRef signedData,
+ SecArenaPoolRef arena, /* used for detached content only */
+ const unsigned char *detachedData,
+ unsigned detachedDataLen,
+ CT_Vfy vfyOp,
+ bool quiet,
+ bool parseSignerCert)
+{
+ Boolean b;
+ b = SecCmsSignedDataHasDigests(signedData);
+ if(!quiet) {
+ printf(" has digests : %s\n", b ? "true" : "false");
+ }
+
+ SecTrustRef secTrust = NULL;
+ OSStatus ortn;
+ SecPolicyRef policy = NULL;
+ SecPolicySearchRef policySearch = NULL;
+
+ ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
+ &CSSMOID_APPLE_X509_BASIC,
+ NULL,
+ &policySearch);
+ if(ortn) {
+ cssmPerror("SecPolicySearchCreate", ortn);
+ return ortn;
+ }
+ ortn = SecPolicySearchCopyNext(policySearch, &policy);
+ if(ortn) {
+ cssmPerror("SecPolicySearchCopyNext", ortn);
+ return ortn;
+ }
+
+ int numSigners = SecCmsSignedDataSignerInfoCount(signedData);
+ if(!quiet) {
+ printf(" num signers : %d\n", numSigners);
+ }
+ for(int dex=0; dex<numSigners; dex++) {
+ if(!quiet) {
+ fprintf(stderr, " signer %d :\n", dex);
+ fprintf(stderr, " vfy status : ");
+ }
+ Boolean b = SecCmsSignedDataHasDigests(signedData);
+ if(b) {
+ if(detachedData != NULL) {
+ fprintf(stderr, "<provided detachedContent, but msg has digests> ");
+ /* FIXME - does this make sense? Error? */
+ }
+ }
+ else if(detachedData != NULL) {
+ /* digest the detached content */
+ SECAlgorithmID **digestAlgorithms = SecCmsSignedDataGetDigestAlgs(signedData);
+ SecCmsDigestContextRef digcx = SecCmsDigestContextStartMultiple(digestAlgorithms);
+ CSSM_DATA **digests = NULL;
+
+ SecCmsDigestContextUpdate(digcx, detachedData, detachedDataLen);
+ ortn = SecCmsDigestContextFinishMultiple(digcx, arena, &digests);
+ if(ortn) {
+ fprintf(stderr, "SecCmsDigestContextFinishMultiple() returned %d\n", (int)ortn);
+ }
+ else {
+ SecCmsSignedDataSetDigests(signedData, digestAlgorithms, digests);
+ }
+ }
+ else {
+ fprintf(stderr, "<Msg has no digest: need detachedContent> ");
+ }
+ ortn = SecCmsSignedDataVerifySignerInfo(signedData, dex, NULL,
+ policy, &secTrust);
+ if(ortn) {
+ fprintf(stderr, "vfSignerInfo() returned %d\n", (int)ortn);
+ fprintf(stderr, " vfy status : ");
+ }
+ if(secTrust == NULL) {
+ fprintf(stderr, "***NO SecTrust available!\n");
+ }
+ else {
+ evalSecTrust(secTrust, quiet);
+ }
+
+ SecCmsSignerInfoRef signerInfo = SecCmsSignedDataGetSignerInfo(signedData, dex);
+ CFStringRef emailAddrs = SecCmsSignerInfoGetSignerCommonName(signerInfo);
+ char emailStr[1000];
+ if(!quiet) {
+ fprintf(stderr, " signer : ");
+ }
+ if(emailAddrs == NULL) {
+ fprintf(stderr, "<<SecCmsSignerInfoGetSignerCommonName returned NULL)>>\n");
+ }
+ else {
+ if(!CFStringGetCString(emailAddrs, emailStr, 1000, kCFStringEncodingASCII)) {
+ fprintf(stderr, "*** Error converting email address to C string\n");
+ }
+ else if(!quiet) {
+
+ fprintf(stderr, "%s\n", emailStr);
+ }
+ }
+ if(parseSignerCert) {
+ SecCertificateRef signer;
+ signer = SecCmsSignerInfoGetSigningCertificate(signerInfo, NULL);
+ if(signer) {
+ CSSM_DATA certData;
+ ortn = SecCertificateGetData(signer, &certData);
+ if(ortn) {
+ fprintf(stderr, "***Error getting signing cert data***\n");
+ cssmPerror("SecCertificateGetData", ortn);
+ }
+ else {
+ printf("========== Signer Cert==========\n\n");
+ printCert(certData.Data, certData.Length, CSSM_FALSE);
+ printf("========== End Signer Cert==========\n\n");
+ }
+ }
+ else {
+ fprintf(stderr, "***Error getting signing cert ***\n");
+ }
+ }
+ }
+ return ortn;
+}
+
+static OSStatus doParse(
+ const unsigned char *data,
+ unsigned dataLen,
+ const unsigned char *detachedData,
+ unsigned detachedDataLen,
+ CT_Vfy vfyOp,
+ bool parseSignerCert,
+ bool quiet,
+ unsigned char **outData, // mallocd and RETURNED
+ unsigned *outDataLen) // RETURNED
+{
+ if((data == NULL) || (dataLen == 0)) {
+ fprintf(stderr, "***Parse requires input file. Aborting.\n");
+ return paramErr;
+ }
+
+ SecArenaPoolRef arena = NULL;
+ SecArenaPoolCreate(1024, &arena);
+ SecCmsMessageRef cmsMsg = NULL;
+ SecCmsDecoderRef decoder;
+ OSStatus ortn;
+ OSStatus ourRtn = noErr;
+ bool foundOneSigned = false;
+ bool foundOneEnveloped = false;
+
+ ortn = SecCmsDecoderCreate(arena, NULL, NULL, NULL, NULL, NULL, NULL, &decoder);
+ if(ortn) {
+ cssmPerror("SecCmsDecoderCreate", ortn);
+ return ortn;
+ }
+ ortn = SecCmsDecoderUpdate(decoder, data, dataLen);
+ if(ortn) {
+ cssmPerror("SecCmsDecoderUpdate", ortn);
+ return ortn;
+ }
+ ortn = SecCmsDecoderFinish(decoder, &cmsMsg);
+ if(ortn) {
+ cssmPerror("SecCmsDecoderFinish", ortn);
+ return ortn;
+ }
+
+ Boolean b = SecCmsMessageIsSigned(cmsMsg);
+ switch(vfyOp) {
+ case CTV_None:
+ break;
+ case CTV_Sign:
+ if(!b) {
+ fprintf(stderr, "***Expected SignedData, but !SecCmsMessageIsSigned()\n");
+ ourRtn = -1;
+ }
+ break;
+ case CTV_SignEnvelop:
+ if(!b) {
+ fprintf(stderr, "***Expected Signed&Enveloped, but !SecCmsMessageIsSigned()\n");
+ ourRtn = -1;
+ }
+ break;
+ case CTV_Envelop:
+ if(b) {
+ fprintf(stderr, "***Expected EnvelopedData, but SecCmsMessageIsSigned() "
+ "TRUE\n");
+ ourRtn = -1;
+ }
+ break;
+ }
+ int numContentInfos = SecCmsMessageContentLevelCount(cmsMsg);
+ if(!quiet) {
+ fprintf(stderr, "=== CMS message info ===\n");
+ fprintf(stderr, " Signed : %s\n", b ? "true" : "false");
+ b = SecCmsMessageIsEncrypted(cmsMsg);
+ fprintf(stderr, " Encrypted : %s\n", b ? "true" : "false");
+ b = SecCmsMessageContainsCertsOrCrls(cmsMsg);
+ fprintf(stderr, " certs/crls : %s\n", b ? "present" : "not present");
+ fprintf(stderr, " Num ContentInfos : %d\n", numContentInfos);
+ }
+
+ /* FIXME needs work for CTV_SignEnvelop */
+ OidParser oidParser;
+ for(int dex=0; dex<numContentInfos; dex++) {
+ SecCmsContentInfoRef ci = SecCmsMessageContentLevel(cmsMsg, dex);
+ if(!quiet) {
+ /* can't use stderr - oidparser is fixed w/stdout */
+ printf(" Content Info %d :\n", dex);
+ CSSM_OID *typeOid = SecCmsContentInfoGetContentTypeOID(ci);
+ printf(" OID Tag : ");
+ if(typeOid == NULL) {
+ printf("***NONE FOUND***]n");
+ }
+ else if(typeOid->Length == 0) {
+ printf("***EMPTY***\n");
+ }
+ else {
+ char str[OID_PARSER_STRING_SIZE];
+ oidParser.oidParse(typeOid->Data, typeOid->Length, str);
+ printf("%s\n", str);
+ }
+ }
+ SECOidTag tag = SecCmsContentInfoGetContentTypeTag(ci);
+ switch(tag) {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ {
+ switch(vfyOp) {
+ case CTV_None: // caller doesn't care
+ case CTV_Sign: // got what we wanted
+ break;
+ case CTV_Envelop:
+ fprintf(stderr, "***Expected EnvelopedData, got SignedData\n");
+ ourRtn = -1;
+ break;
+ case CTV_SignEnvelop:
+ printf("CTV_SignEnvelop code on demand\n");
+ break;
+ }
+ foundOneSigned = true;
+ SecCmsSignedDataRef sd =
+ (SecCmsSignedDataRef) SecCmsContentInfoGetContent(ci);
+ parseSignedData(sd, arena,
+ detachedData, detachedDataLen,
+ vfyOp, quiet, parseSignerCert);
+ break;
+ }
+ case SEC_OID_PKCS7_DATA:
+ case SEC_OID_OTHER:
+ break;
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ foundOneEnveloped = true;
+ if(vfyOp == CTV_Sign) {
+ fprintf(stderr, "***Expected SignedData, EnvelopedData\n");
+ ourRtn = -1;
+ break;
+ }
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ switch(vfyOp) {
+ case CTV_None:
+ break;
+ case CTV_Sign:
+ fprintf(stderr, "***Expected SignedData, got EncryptedData\n");
+ ourRtn = -1;
+ break;
+ case CTV_Envelop:
+ fprintf(stderr, "***Expected EnvelopedData, got EncryptedData\n");
+ ourRtn = -1;
+ break;
+ case CTV_SignEnvelop:
+ printf("CTV_SignEnvelop code on demand\n");
+ break;
+ }
+ break;
+ default:
+ fprintf(stderr, " other content type TBD\n");
+ }
+ }
+ if(outData) {
+ CSSM_DATA_PTR odata = SecCmsMessageGetContent(cmsMsg);
+ if(odata == NULL) {
+ fprintf(stderr, "***No inner content available\n");
+ }
+ else {
+ *outData = (unsigned char *)malloc(odata->Length);
+ memmove(*outData, odata->Data, odata->Length);
+ *outDataLen = odata->Length;
+ }
+ }
+ if(arena) {
+ SecArenaPoolFree(arena, false);
+ }
+ switch(vfyOp) {
+ case CTV_None:
+ break;
+ case CTV_Sign:
+ if(!foundOneSigned) {
+ fprintf(stderr, "Expected signed, never saw a SignedData\n");
+ ourRtn = -1;
+ }
+ break;
+ case CTV_Envelop:
+ if(!foundOneEnveloped) {
+ fprintf(stderr, "Expected enveloped, never saw an EnvelopedData\n");
+ ourRtn = -1;
+ }
+ break;
+ case CTV_SignEnvelop:
+ if(!foundOneSigned) {
+ fprintf(stderr, "Expected signed, never saw a SignedData\n");
+ ourRtn = -1;
+ }
+ if(!foundOneEnveloped) {
+ fprintf(stderr, "Expected enveloped, never saw an EnvelopedData\n");
+ ourRtn = -1;
+ }
+ break;
+ }
+ /* free decoder? cmsMsg? */
+ return ourRtn;
+}
+
+/*
+ * Common encode routine.
+ */
+#if 1
+/* the simple way, when 3655861 is fixed */
+static OSStatus encodeCms(
+ SecCmsMessageRef cmsMsg,
+ const unsigned char *inData, // add in this
+ unsigned inDataLen,
+ unsigned char **outData, // mallocd and RETURNED
+ unsigned *outDataLen) // RETURNED
+{
+ SecArenaPoolRef arena = NULL;
+ SecArenaPoolCreate(1024, &arena);
+ CSSM_DATA cdataIn = {inDataLen, (uint8 *)inData};
+ CSSM_DATA cdataOut = {0, NULL};
+
+ OSStatus ortn = SecCmsMessageEncode(cmsMsg, &cdataIn, arena, &cdataOut);
+ if((ortn == noErr) && (cdataOut.Length != 0)) {
+ *outData = (unsigned char *)malloc(cdataOut.Length);
+ memmove(*outData, cdataOut.Data, cdataOut.Length);
+ *outDataLen = cdataOut.Length;
+ }
+ else {
+ cssmPerror("SecCmsMessageEncode", ortn);
+ *outData = NULL;
+ *outDataLen = 0;
+ }
+ SecArenaPoolFree(arena, false);
+ return ortn;
+}
+
+#else
+
+/* the hard way back when SecCmsMessageEncode() didn't work */
+static OSStatus encodeCms(
+ SecCmsMessageRef cmsMsg,
+ const unsigned char *inData, // add in this
+ unsigned inDataLen,
+ unsigned char **outData, // mallocd and RETURNED
+ unsigned *outDataLen) // RETURNED
+{
+ SecArenaPoolRef arena = NULL;
+ SecArenaPoolCreate(1024, &arena);
+ SecCmsEncoderRef cmsEnc = NULL;
+ CSSM_DATA output = { 0, NULL };
+ OSStatus ortn;
+
+ ortn = SecCmsEncoderCreate(cmsMsg,
+ NULL, NULL, // no callback
+ &output, arena, // data goes here
+ NULL, NULL, // no password callback (right?)
+ NULL, NULL, // decrypt key callback
+ NULL, NULL, // detached digests
+ &cmsEnc);
+ if(ortn) {
+ cssmPerror("SecKeychainItemCopyKeychain", ortn);
+ goto errOut;
+ }
+ ortn = SecCmsEncoderUpdate(cmsEnc, (char *)inData, inDataLen);
+ if(ortn) {
+ cssmPerror("SecCmsEncoderUpdate", ortn);
+ goto errOut;
+ }
+ ortn = SecCmsEncoderFinish(cmsEnc);
+ if(ortn) {
+ cssmPerror("SecCMsEncoderFinish", ortn);
+ goto errOut;
+ }
+
+ /* Did we get any data? */
+ if(output.Length) {
+ *outData = (unsigned char *)malloc(output.Length);
+ memmove(*outData, output.Data, output.Length);
+ *outDataLen = output.Length;
+ }
+ else {
+ *outData = NULL;
+ *outDataLen = 0;
+ }
+errOut:
+ if(arena) {
+ SecArenaPoolFree(arena, false);
+ }
+ return ortn;
+}
+
+#endif
+
+static OSStatus doSign(
+ SecIdentityRef signerId,
+ const unsigned char *inData,
+ unsigned inDataLen,
+ bool detachedContent,
+ const CSSM_OID *eContentType, // OPTIONAL
+ unsigned char **outData, // mallocd and RETURNED
+ unsigned *outDataLen) // RETURNED
+{
+ if((inData == NULL) || (inDataLen == 0) || (outData == NULL)) {
+ fprintf(stderr, "***Sign requires input file. Aborting.\n");
+ return paramErr;
+ }
+ if(signerId == NULL) {
+ fprintf(stderr, "***Sign requires a signing identity. Aborting.\n");
+ return paramErr;
+ }
+
+ SecCmsMessageRef cmsMsg = NULL;
+ SecCmsContentInfoRef contentInfo = NULL;
+ SecCmsSignedDataRef signedData = NULL;
+ SecCertificateRef ourCert = NULL;
+ SecCmsSignerInfoRef signerInfo;
+ OSStatus ortn;
+ SecKeychainRef ourKc = NULL;
+
+ ortn = SecIdentityCopyCertificate(signerId, &ourCert);
+ if(ortn) {
+ cssmPerror("SecIdentityCopyCertificate", ortn);
+ return ortn;
+ }
+ ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)ourCert, &ourKc);
+ if(ortn) {
+ cssmPerror("SecKeychainItemCopyKeychain", ortn);
+ goto errOut;
+ }
+
+ // build chain of objects: message->signedData->data
+ cmsMsg = SecCmsMessageCreate(NULL);
+ if(cmsMsg == NULL) {
+ fprintf(stderr, "***Error creating SecCmsMessageRef\n");
+ ortn = -1;
+ goto errOut;
+ }
+ signedData = SecCmsSignedDataCreate(cmsMsg);
+ if(signedData == NULL) {
+ printf("***Error creating SecCmsSignedDataRef\n");
+ ortn = -1;
+ goto errOut;
+ }
+ contentInfo = SecCmsMessageGetContentInfo(cmsMsg);
+ ortn = SecCmsContentInfoSetContentSignedData(cmsMsg, contentInfo, signedData);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentSignedData", ortn);
+ goto errOut;
+ }
+ contentInfo = SecCmsSignedDataGetContentInfo(signedData);
+ if(eContentType != NULL) {
+ ortn = SecCmsContentInfoSetContentOther(cmsMsg, contentInfo,
+ NULL /* data */,
+ detachedContent,
+ eContentType);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentData", ortn);
+ goto errOut;
+ }
+ }
+ else {
+ ortn = SecCmsContentInfoSetContentData(cmsMsg, contentInfo, NULL /* data */,
+ detachedContent);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentData", ortn);
+ goto errOut;
+ }
+ }
+
+ /*
+ * create & attach signer information
+ */
+ signerInfo = SecCmsSignerInfoCreate(cmsMsg, signerId, SEC_OID_SHA1);
+ if (signerInfo == NULL) {
+ fprintf(stderr, "***Error on SecCmsSignerInfoCreate\n");
+ ortn = -1;
+ goto errOut;
+ }
+ /* we want the cert chain included for this one */
+ /* FIXME - what's the significance of the usage? */
+ ortn = SecCmsSignerInfoIncludeCerts(signerInfo, SecCmsCMCertChain, certUsageEmailSigner);
+ if(ortn) {
+ cssmPerror("SecCmsSignerInfoIncludeCerts", ortn);
+ goto errOut;
+ }
+
+ /* other options go here - signing time, etc. */
+
+ ortn = SecCmsSignerInfoAddSMIMEEncKeyPrefs(signerInfo, ourCert, ourKc);
+ if(ortn) {
+ cssmPerror("SecCmsSignerInfoAddSMIMEEncKeyPrefs", ortn);
+ goto errOut;
+ }
+ ortn = SecCmsSignedDataAddCertificate(signedData, ourCert);
+ if(ortn) {
+ cssmPerror("SecCmsSignedDataAddCertificate", ortn);
+ goto errOut;
+ }
+
+ ortn = SecCmsSignedDataAddSignerInfo(signedData, signerInfo);
+ if(ortn) {
+ cssmPerror("SecCmsSignedDataAddSignerInfo", ortn);
+ goto errOut;
+ }
+
+ /* go */
+ ortn = encodeCms(cmsMsg, inData, inDataLen, outData, outDataLen);
+errOut:
+ /* free resources */
+ if(cmsMsg) {
+ SecCmsMessageDestroy(cmsMsg);
+ }
+ if(ourCert) {
+ CFRelease(ourCert);
+ }
+ if(ourKc) {
+ CFRelease(ourKc);
+ }
+ return ortn;
+}
+
+static OSStatus doEncrypt(
+ SecCertificateRef recipCert, // eventually more than one
+ const unsigned char *inData,
+ unsigned inDataLen,
+ unsigned char **outData, // mallocd and RETURNED
+ unsigned *outDataLen) // RETURNED
+{
+ if((inData == NULL) || (inDataLen == 0) || (outData == NULL)) {
+ fprintf(stderr, "***Encrypt requires input file. Aborting.\n");
+ return paramErr;
+ }
+ if(recipCert == NULL) {
+ fprintf(stderr, "***Encrypt requires a recipient certificate. Aborting.\n");
+ return paramErr;
+ }
+
+ SecCmsMessageRef cmsMsg = NULL;
+ SecCmsContentInfoRef contentInfo = NULL;
+ SecCmsEnvelopedDataRef envelopedData = NULL;
+ SecCmsRecipientInfoRef recipientInfo = NULL;
+ OSStatus ortn;
+ SecCertificateRef allCerts[2] = { recipCert, NULL};
+
+ SECOidTag algorithmTag;
+ int keySize;
+
+ ortn = SecSMIMEFindBulkAlgForRecipients(allCerts, &algorithmTag, &keySize);
+ if(ortn) {
+ cssmPerror("SecSMIMEFindBulkAlgForRecipients", ortn);
+ return ortn;
+ }
+
+ // build chain of objects: message->envelopedData->data
+ cmsMsg = SecCmsMessageCreate(NULL);
+ if(cmsMsg == NULL) {
+ fprintf(stderr, "***Error creating SecCmsMessageRef\n");
+ ortn = -1;
+ goto errOut;
+ }
+ envelopedData = SecCmsEnvelopedDataCreate(cmsMsg, algorithmTag, keySize);
+ if(envelopedData == NULL) {
+ fprintf(stderr, "***Error creating SecCmsEnvelopedDataRef\n");
+ ortn = -1;
+ goto errOut;
+ }
+ contentInfo = SecCmsMessageGetContentInfo(cmsMsg);
+ ortn = SecCmsContentInfoSetContentEnvelopedData(cmsMsg, contentInfo, envelopedData);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentEnvelopedData", ortn);
+ goto errOut;
+ }
+ contentInfo = SecCmsEnvelopedDataGetContentInfo(envelopedData);
+ ortn = SecCmsContentInfoSetContentData(cmsMsg, contentInfo, NULL /* data */, false);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentData", ortn);
+ goto errOut;
+ }
+
+ /*
+ * create & attach recipient information
+ */
+ recipientInfo = SecCmsRecipientInfoCreate(cmsMsg, recipCert);
+ ortn = SecCmsEnvelopedDataAddRecipient(envelopedData, recipientInfo);
+ if(ortn) {
+ cssmPerror("SecCmsEnvelopedDataAddRecipient", ortn);
+ goto errOut;
+ }
+
+
+ /* go */
+ ortn = encodeCms(cmsMsg, inData, inDataLen, outData, outDataLen);
+errOut:
+ /* free resources */
+ if(cmsMsg) {
+ SecCmsMessageDestroy(cmsMsg);
+ }
+ return ortn;
+}
+
+/* create nested message: msg = EnvelopedData(SignedData(inData)) */
+static OSStatus doSignEncrypt(
+ SecCertificateRef recipCert, // encryption recipient
+ SecIdentityRef signerId, // signer
+ const CSSM_OID *eContentType, // OPTIONAL - for signedData
+ const unsigned char *inData,
+ unsigned inDataLen,
+ unsigned char **outData, // mallocd and RETURNED
+ unsigned *outDataLen) // RETURNED
+{
+ if((inData == NULL) || (inDataLen == 0) || (outData == NULL)) {
+ fprintf(stderr, "***Sign/Encrypt requires input file. Aborting.\n");
+ return paramErr;
+ }
+ if(recipCert == NULL) {
+ fprintf(stderr, "***Sign/Encrypt requires a recipient certificate. Aborting.\n");
+ return paramErr;
+ }
+ if(signerId == NULL) {
+ fprintf(stderr, "***Sign/Encrypt requires a signer Identity. Aborting.\n");
+ return paramErr;
+ }
+
+ OSStatus ortn;
+ unsigned char *signedData = NULL;
+ unsigned signedDataLen = 0;
+ SecCmsMessageRef cmsMsg = NULL;
+ SecCmsContentInfoRef contentInfo = NULL;
+ SecCmsEnvelopedDataRef envelopedData = NULL;
+ SecCmsRecipientInfoRef recipientInfo = NULL;
+ SecCertificateRef allCerts[2] = { recipCert, NULL};
+ SECOidTag algorithmTag;
+ int keySize;
+
+ /* first get a SignedData */
+ ortn = doSign(signerId, inData, inDataLen,
+ false, /* can't do detached content here */
+ eContentType,
+ &signedData, &signedDataLen);
+ if(ortn) {
+ printf("***Error generating inner signedData. Aborting.\n");
+ return ortn;
+ }
+
+ /* extract just the content - don't need the whole ContentINfo */
+ unsigned char *signedDataContent = NULL;
+ unsigned signedDataContentLen = 0;
+ ortn = ContentInfoContent(signedData, signedDataLen, &signedDataContent, &signedDataContentLen);
+ if(ortn) {
+ goto errOut;
+ }
+
+ /* now wrap that in an EnvelopedData */
+ ortn = SecSMIMEFindBulkAlgForRecipients(allCerts, &algorithmTag, &keySize);
+ if(ortn) {
+ cssmPerror("SecSMIMEFindBulkAlgForRecipients", ortn);
+ return ortn;
+ }
+
+ // build chain of objects: message->envelopedData->data
+ cmsMsg = SecCmsMessageCreate(NULL);
+ if(cmsMsg == NULL) {
+ fprintf(stderr, "***Error creating SecCmsMessageRef\n");
+ ortn = -1;
+ goto errOut;
+ }
+ envelopedData = SecCmsEnvelopedDataCreate(cmsMsg, algorithmTag, keySize);
+ if(envelopedData == NULL) {
+ fprintf(stderr, "***Error creating SecCmsEnvelopedDataRef\n");
+ ortn = -1;
+ goto errOut;
+ }
+ contentInfo = SecCmsMessageGetContentInfo(cmsMsg);
+ ortn = SecCmsContentInfoSetContentEnvelopedData(cmsMsg, contentInfo, envelopedData);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentEnvelopedData", ortn);
+ goto errOut;
+ }
+ contentInfo = SecCmsEnvelopedDataGetContentInfo(envelopedData);
+
+ /* here's the difference: we override the 'data' content with a SignedData type,
+ * but we fool the smime lib into thinking it's a plain old data so it doesn't try
+ * to encode the SignedData */
+ ortn = SecCmsContentInfoSetContentOther(cmsMsg, contentInfo,
+ NULL /* data */,
+ false,
+ &CSSMOID_PKCS7_SignedData);
+ if(ortn) {
+ cssmPerror("SecCmsContentInfoSetContentData", ortn);
+ goto errOut;
+ }
+
+ /*
+ * create & attach recipient information
+ */
+ recipientInfo = SecCmsRecipientInfoCreate(cmsMsg, recipCert);
+ ortn = SecCmsEnvelopedDataAddRecipient(envelopedData, recipientInfo);
+ if(ortn) {
+ cssmPerror("SecCmsEnvelopedDataAddRecipient", ortn);
+ goto errOut;
+ }
+
+
+ /* go */
+ ortn = encodeCms(cmsMsg, signedDataContent, signedDataContentLen, outData, outDataLen);
+errOut:
+ /* free resources */
+ if(cmsMsg) {
+ SecCmsMessageDestroy(cmsMsg);
+ }
+ if(signedData) {
+ free(signedData);
+ }
+ if(signedDataContent) {
+ free(signedDataContent);
+ }
+ return ortn;
+}
+
+int main(int argc, char **argv)
+{
+ if(argc < 2) {
+ usage(argv);
+ }
+
+ CT_Op op;
+ bool needId = false;
+ if(!strcmp(argv[1], "sign")) {
+ op = CTO_Sign;
+ needId = true;
+ }
+ else if(!strcmp(argv[1], "envel")) {
+ op = CTO_Envelop;
+ }
+ else if(!strcmp(argv[1], "signEnv")) {
+ op = CTO_SignEnvelop;
+ needId = true;
+ }
+ else if(!strcmp(argv[1], "parse")) {
+ op = CTO_Parse;
+ }
+ else {
+ fprintf(stderr, "***Unrecognized cmd.\n");
+ usage(argv);
+ }
+
+ extern int optind;
+ extern char *optarg;
+ int arg;
+
+ /* optional args */
+ const char *keychainName = NULL;
+ char *inFileName = NULL;
+ char *outFileName = NULL;
+ bool detachedContent = false;
+ char *detachedFile = NULL;
+ bool useIdPicker = false;
+ char *recipient = NULL;
+ bool quiet = false;
+ bool parseSignerCert = false;
+ CT_Vfy vfyOp = CTV_None;
+ const CSSM_OID *eContentType = NULL;
+
+ optind = 2;
+ while ((arg = getopt(argc, argv, "i:o:k:pr:e:dD:qcv:")) != -1) {
+ switch (arg) {
+ case 'i':
+ inFileName = optarg;
+ break;
+ case 'o':
+ outFileName = optarg;
+ break;
+ case 'k':
+ keychainName = optarg;
+ break;
+ case 'p':
+ useIdPicker = true;
+ break;
+ case 'r':
+ recipient = optarg;
+ break;
+ case 'c':
+ parseSignerCert = true;
+ break;
+ case 'v':
+ if(!strcmp(optarg, "sign")) {
+ vfyOp = CTV_Sign;
+ }
+ else if(!strcmp(optarg, "encr")) {
+ vfyOp = CTV_Envelop;
+ }
+ else if(!strcmp(optarg, "signEnv")) {
+ vfyOp = CTV_SignEnvelop;
+ }
+ else {
+ usage(argv);
+ }
+ break;
+ case 'e':
+ switch(optarg[0]) {
+ case 'a':
+ eContentType = &CSSMOID_PKINIT_AUTH_DATA;
+ break;
+ case 'r':
+ eContentType = &CSSMOID_PKINIT_RKEY_DATA;
+ break;
+ default:
+ usage(argv);
+ }
+ break;
+ case 'd':
+ if(op != CTO_Sign) {
+ printf("-d only valid for op sign\n");
+ exit(1);
+ }
+ detachedContent = true;
+ break;
+ case 'D':
+ if(op != CTO_Parse) {
+ printf("-D only valid for op sign\n");
+ exit(1);
+ }
+ detachedFile = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ default:
+ case '?':
+ usage(argv);
+ }
+ }
+ if(optind != argc) {
+ /* getopt does not return '?' */
+ usage(argv);
+ }
+
+ SecIdentityRef idRef = NULL;
+ SecKeychainRef kcRef = NULL;
+ SecCertificateRef recipientCert = NULL;
+ unsigned char *inData = NULL;
+ unsigned inDataLen = 0;
+ unsigned char *outData = NULL;
+ unsigned outDataLen = 0;
+ unsigned char *detachedData = NULL;
+ unsigned detachedDataLen = 0;
+ OSStatus ortn;
+
+ if(inFileName) {
+ if(readFile(inFileName, &inData, &inDataLen)) {
+ fprintf(stderr, "***Error reading infile %s. Aborting.\n", inFileName);
+ exit(1);
+ }
+ }
+ if(detachedFile) {
+ if(readFile(detachedFile, &detachedData, &detachedDataLen)) {
+ fprintf(stderr, "***Error reading detachedFile %s. Aborting.\n", detachedFile);
+ exit(1);
+ }
+ }
+ if(keychainName) {
+ ortn = SecKeychainOpen(keychainName, &kcRef);
+ if(ortn) {
+ cssmPerror("SecKeychainOpen", ortn);
+ exit(1);
+ }
+ }
+ if(useIdPicker) {
+ ortn = sslSimpleIdentPicker(kcRef, &idRef);
+ if(ortn) {
+ fprintf(stderr, "***Error obtaining identity via picker. Aborting.\n");
+ exit(1);
+ }
+ }
+ else if(needId) {
+ /* use first identity in specified keychain */
+ CFArrayRef array = sslKcRefToCertArray(kcRef, CSSM_FALSE, CSSM_FALSE,
+ NULL, // no verify policy
+ NULL);
+ if(array == NULL) {
+ fprintf(stderr, "***Error finding a signing cert. Aborting.\n");
+ exit(1);
+ }
+ idRef = (SecIdentityRef)CFArrayGetValueAtIndex(array, 0);
+ if(idRef == NULL) {
+ fprintf(stderr, "***No identities found. Aborting.\n");
+ exit(1);
+ }
+ CFRetain(idRef);
+ CFRelease(array);
+ }
+ if(recipient) {
+ ortn = findCert(recipient, kcRef, &recipientCert);
+ if(ortn) {
+ exit(1);
+ }
+ }
+
+ switch(op) {
+ case CTO_Sign:
+ ortn = doSign(idRef, inData, inDataLen,
+ detachedContent, eContentType,
+ &outData, &outDataLen);
+ break;
+ case CTO_Envelop:
+ if(recipientCert == NULL) {
+ if(idRef == NULL) {
+ printf("***Need a recipient or an identity to encrypt\n");
+ exit(1);
+ }
+ ortn = SecIdentityCopyCertificate(idRef, &recipientCert);
+ if(ortn) {
+ cssmPerror("SecIdentityCopyCertificate", ortn);
+ exit(1);
+ }
+ }
+ ortn = doEncrypt(recipientCert, inData, inDataLen, &outData, &outDataLen);
+ break;
+ case CTO_SignEnvelop:
+ ortn = doSignEncrypt(recipientCert, idRef, eContentType,
+ inData, inDataLen, &outData, &outDataLen);
+ break;
+ case CTO_Parse:
+ ortn = doParse(inData, inDataLen,
+ detachedData, detachedDataLen,
+ vfyOp, parseSignerCert, quiet,
+ &outData, &outDataLen);
+ break;
+ }
+ if(ortn) {
+ goto errOut;
+ }
+ if(outData && outFileName) {
+ if(writeFile(outFileName, outData, outDataLen)) {
+ fprintf(stderr, "***Error writing to %s.\n", outFileName);
+ ortn = -1;
+ }
+ else {
+ if(!quiet) {
+ fprintf(stderr, "...wrote %u bytes to %s.\n", outDataLen, outFileName);
+ }
+ }
+ }
+ else if(outData) {
+ fprintf(stderr, "...generated %u bytes but no place to write it.\n", outDataLen);
+ }
+ else if(outFileName) {
+ fprintf(stderr, "...nothing to write to file %s.\n", outFileName);
+ /* assume this is an error, caller wanted something */
+ ortn = -1;
+ }
+errOut:
+ return ortn;
+}