cmstool.cpp   [plain text]


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