ocspTool.cpp   [plain text]


/* 
 * ocspTool - simple OCSP request/response generator and parser
 */
#include <Security/Security.h>
#include <Security/SecAsn1Coder.h>
#include <Security/ocspTemplates.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_cdsa_utils/cuOidParser.h>
#include <security_cdsa_utils/cuPrintCert.h>
#include <clAppUtils/CertParser.h>
#include <clAppUtils/timeStr.h>
#include <clAppUtils/identPicker.h>
#include <CommonCrypto/CommonDigest.h>
#include <security_ocspd/ocspExtensions.h>
#include <security_ocspd/ocspdUtils.h>
#include <utilLib/common.h>
#include "ocspUtils.h"
#include "ocspRequest.h"
#include "ocspNetwork.h"
#include "findOcspUrl.h"

static void usage(char **argv) 
{
	printf("Usage: %s cmd [option...]\n", argv[0]);
	printf("cmds:\n");
	printf("   g generate request\n");
	printf("   G parse request\n");
	printf("   r generate reply\n");
	printf("   R parse reply\n");
	printf("   p generate OCSP request, post, get reply (use -p and/or -o)\n");
	printf("Options\n");
	printf("  -c cert_file         -- for generating request\n");
	printf("  -C issuer_cert_file  -- for generating request\n");
	printf("  -i in_file           -- for parsing\n");
	printf("  -o out_file          -- for generating\n");
	printf("  -s status            -- cert status: g(ood)|r(evoked)|u(nknown)\n");
	printf("  -r crlReason         -- integer 0..8\n");
	printf("  -k keychain          -- keychain containing signing cert\n");
	printf("  -p                   -- parse reply from post op\n");
	printf("  -u responderURI      -- OCSP responder here, not from cert's AIA extension\n");
	printf("  -v                   -- verbose; e.g., print certs\n");
	exit(1);
}

void doIndent(int indent)
{
	for(int dex=0; dex<indent; dex++) {
		printf(" ");
	}
}

static void printString(
	const CSSM_DATA *str)
{
	unsigned i;
	char *cp = (char *)str->Data;
	for(i=0; i<str->Length; i++) {
		printf("%c", *cp++);
	}
	printf("\n");
}

static void printDataAsHex(
	const CSSM_DATA *d,
	unsigned maxToPrint = 0)		// optional, 0 means print it all
{
	unsigned i;
	bool more = false;
	uint32 len = d->Length;
	uint8 *cp = d->Data;
	
	if((maxToPrint != 0) && (len > maxToPrint)) {
		len = maxToPrint;
		more = true;
	}	
	for(i=0; i<len; i++) {
		printf("%02X ", ((unsigned char *)cp)[i]);
	}
	if(more) {
		printf("...\n");
	}
	else {
		printf("\n");
	}
}

static void printTaggedItem(
	const NSS_TaggedItem &ti)
{
	switch(ti.tag) {
		case BER_TAG_PRINTABLE_STRING:
		case BER_TAG_T61_STRING:
		case BER_TAG_IA5_STRING:
		case BER_TAG_UTC_TIME:
		case BER_TAG_GENERALIZED_TIME:
			printString(&ti.item);
			break;
		default:
			printDataAsHex(&ti.item, 0);
	}
}

static void printName(
	const NSS_Name &name,
	int indent)
{
	OidParser parser;
	
	unsigned numRdns = ocspdArraySize((const void **)name.rdns);
	for(unsigned rdnDex=0; rdnDex<numRdns; rdnDex++) {
		NSS_RDN *rdn = name.rdns[rdnDex];
		unsigned numATVs = ocspdArraySize((const void **)rdn->atvs);
		for(unsigned atvDex=0; atvDex<numATVs; atvDex++) {
			NSS_ATV *atv = rdn->atvs[atvDex];
			char buf[OID_PARSER_STRING_SIZE];
			parser.oidParse(atv->type.Data, atv->type.Length, buf);
			doIndent(indent);
			printf("%s : ", buf);
			printTaggedItem(atv->value);
		}
	}
}	

static uint8 nullParam[2] = {5, 0};

/*
 * Given the following, create a ResponseData (to be signed by caller).
 *
 *		cert status (CS_{Good,Revoked,Unknown})
 *		cert being verified
 *		issuer cert
 *		this update time
 *		next update time (optional)
 *		nonce (optional)
 */
static int genTbsResp(
	SecAsn1CoderRef coder,			// result in this coder's address space
	CSSM_CL_HANDLE clHand, 
	SecAsn1OCSPCertStatusTag status,
	CE_CrlReason reason,			// for CS_Revoked
	const CSSM_DATA	&subjectCert,
	const CSSM_DATA &issuerCert,
	unsigned thisUpdate,			// required, seconds from now
	unsigned nextUpdate,			// optional, seconds from now, 0 ==> skip
	const CSSM_DATA *nonce,			// optional
	CSSM_DATA &encodedTbs)			// allocated in coder space and RETURNED
{
	char *nextUpdStr = NULL;
	CSSM_DATA nextUpdateData;
	char *thisUpdStr = NULL;
	CSSM_DATA *thisUpdateData;
	SecAsn1OCSPResponseData responseData;
	OCSPNonce *nonceExt = NULL;
	char *producedAt = NULL;
	SecAsn1OCSPSingleResponse singleResp;
	SecAsn1OCSPSingleResponse *respArray[2] = {&singleResp, NULL};
	SecAsn1OCSPResponderID responderID;
	NSS_CertExtension *extenArray[2] = {NULL, NULL};
	
	/* SingleResponse */
	memset(&singleResp, 0, sizeof(singleResp));
	
	/* SingleResponse.CertID */
	SecAsn1OCSPCertID &certId = singleResp.certID;
	CertParser parser(clHand);
	CertParser issuerParser(clHand);
	CSSM_RETURN crtn = parser.initWithData(subjectCert);
	if(crtn) {
		cssmPerror("CertParser.initWithData for subject cert", crtn);
		return -1;
	}
	crtn = issuerParser.initWithData(issuerCert);
	if(crtn) {
		cssmPerror("CertParser.initWithData for issuer", crtn);
		return -1;
	}

	/* algId refers to the hash we'll perform in issuer name and key */
	certId.algId.algorithm = CSSMOID_SHA1;
	certId.algId.parameters.Data = nullParam;
	certId.algId.parameters.Length = sizeof(nullParam);
	
	/* SHA1(issuerName) */
	CSSM_DATA issuerName = {0, NULL};
	issuerName.Data = (uint8 *)parser.fieldForOid(CSSMOID_X509V1IssuerNameStd, 
		issuerName.Length);
	if(issuerName.Data == NULL) {
		printf("***Error fetching issuer name. Aborting.\n");
		return 1;
	}
	uint8 issuerNameHash[CC_SHA1_DIGEST_LENGTH];
	ocspdSha1(issuerName.Data, issuerName.Length, issuerNameHash);
	
	/* SHA1(issuer public key) */
	CSSM_KEY_PTR pubKey = NULL;
	CSSM_SIZE pubKeyLen = sizeof(CSSM_KEY);
	pubKey = (CSSM_KEY_PTR)issuerParser.fieldForOid(CSSMOID_CSSMKeyStruct, pubKeyLen);
	if(pubKey == NULL) {
		printf("***Error fetching public key from issuer cert. Aborting.\n");
		return 1;
	}
	uint8 pubKeyHash[CC_SHA1_DIGEST_LENGTH];
	ocspdSha1(pubKey->KeyData.Data, pubKey->KeyData.Length, pubKeyHash);

	/* serial number */
	CSSM_DATA serialNum = {0, NULL};
	serialNum.Data = (uint8 *)parser.fieldForOid(CSSMOID_X509V1SerialNumber, 
		serialNum.Length);
	if(serialNum.Data == NULL) {
		printf("***Error fetching serial number. Aborting.\n");
		return 1;
	}

	/* build the CertID from those components */
	certId.issuerNameHash.Data = issuerNameHash;
	certId.issuerNameHash.Length = CC_SHA1_DIGEST_LENGTH;
	certId.issuerPubKeyHash.Data = pubKeyHash;
	certId.issuerPubKeyHash.Length = CC_SHA1_DIGEST_LENGTH;	
	certId.serialNumber = serialNum;

	/* SingleResponse.CertStatus - to be encoded on its own */
	SecAsn1OCSPCertStatus certStatus;
	memset(&certStatus, 0, sizeof(certStatus));
	SecAsn1OCSPRevokedInfo revokedInfo;
	char *revokedAt = NULL;
	CSSM_DATA reasonData;
	OSStatus ortn;
	
	if(status == CS_Revoked) { 
		/* cook up SecAsn1OCSPRevokedInfo */
		certStatus.revokedInfo = &revokedInfo;
		revokedAt = appTimeAtNowPlus(-3600, TIME_GEN);
		revokedInfo.revocationTime.Data = (uint8 *)revokedAt; 
		revokedInfo.revocationTime.Length = strlen(revokedAt);
		uint8 theReason = reason;
		reasonData.Data = &theReason;
		reasonData.Length = 1;
		revokedInfo.revocationReason = &reasonData;
		ortn = SecAsn1EncodeItem(coder, &certStatus, 
			kSecAsn1OCSPCertStatusRevokedTemplate, 
			&singleResp.certStatus);
	}
	else {
		ortn = SecAsn1EncodeItem(coder, &certStatus, 
			kSecAsn1OCSPCertStatusGoodTemplate,
			&singleResp.certStatus);
	}
	if(ortn) {
		printf("***Error encoding certStatus\n"); 
		goto errOut;
	}
	
	/* SingleResponse.thisUpdate */
	thisUpdStr = appTimeAtNowPlus(thisUpdate, TIME_GEN);
	thisUpdateData = &singleResp.thisUpdate;
	thisUpdateData->Data = (uint8 *)thisUpdStr;
	thisUpdateData->Length = strlen(thisUpdStr);
	
	/* SingleResponse.nextUpdate, optional */
	if(nextUpdate) {
		nextUpdStr = appTimeAtNowPlus(nextUpdate, TIME_GEN); 
		nextUpdateData.Data = (uint8 *)nextUpdStr;
		nextUpdateData.Length = strlen(nextUpdStr);
		singleResp.nextUpdate = &nextUpdateData;
	}
	
	/* Single Extensions - none for now */
	 
	/* Now up to ResponseData */
	memset(&responseData, 0, sizeof(responseData));
	
	/* skip version */
	
	/* 
	 * ResponseData.responderID: KeyHash (of signer); we already got this for CertID.
	 * WE have to encode this one separately and drop it in as an ASN_ANY. 
	 */
	responderID.byKey = certId.issuerPubKeyHash;
	ortn = SecAsn1EncodeItem(coder, &responderID, 
		kSecAsn1OCSPResponderIDAsKeyTemplate,
		&responseData.responderID);
	if(ortn) {
		printf("***Error encoding responderID\n");
		goto errOut;
	}
	
	/* ResponseData.producedAt = now */
	producedAt = appTimeAtNowPlus(0, TIME_GEN);
	responseData.producedAt.Data = (uint8 *)producedAt;
	responseData.producedAt.Length = strlen(producedAt);
		
	/* ResponseData.responses - one of 'em */
	responseData.responses = respArray;
	
	/* ResponseData.responseExtensions - optionally one, nonce */
	if(nonce) {
		nonceExt = new OCSPNonce(coder, false, *nonce);
		extenArray[0] = nonceExt->nssExt();
		responseData.responseExtensions = extenArray;
	}
	else {
		responseData.responseExtensions = NULL;
	}
	
	/* encode it */
	encodedTbs.Data = NULL;
	encodedTbs.Length = 0;
	ortn = SecAsn1EncodeItem(coder, &responseData, 
		kSecAsn1OCSPResponseDataTemplate,
		&encodedTbs);
	if(ortn) {
		printf("***Error encoding SecAsn1OCSPResponseData\n");
	}
errOut:
	/* free resources */
	if(revokedAt) {
		CSSM_FREE(revokedAt);
	}
	if(thisUpdStr) {
		CSSM_FREE(thisUpdStr);
	}
	if(nextUpdStr) {
		CSSM_FREE(nextUpdStr);
	}
	if(nonceExt) {
		delete nonceExt;
	}
	return ortn;
}

static int genOcspReq(
	CSSM_CL_HANDLE clHand,
	const unsigned char *certFile, 
	unsigned certFileLen, 
	const unsigned char *issuerCertFile, 
	unsigned issuerCertFileLen, 
	unsigned char **outFile,		// RETURNED 
	unsigned *outFileLen)			// RETURNED
{
	CertParser parser(clHand);
	CertParser issuerParser(clHand);
	CSSM_DATA certData = {certFileLen, (uint8 *)certFile};
	CSSM_RETURN crtn;
	crtn = parser.initWithData(certData);
	if(crtn) {
		cssmPerror("CertParser.initWithData for subject cert", crtn);
		return -1;
	}
	certData.Data = (uint8 *)issuerCertFile;
	certData.Length = issuerCertFileLen;
	crtn = issuerParser.initWithData(certData);
	if(crtn) {
		cssmPerror("CertParser.initWithData for issuer", crtn);
		return -1;
	}
	
	/* 
	 * One single request, no extensions
	 */
	SecAsn1OCSPRequest singleReq;
	memset(&singleReq, 0, sizeof(singleReq));
	SecAsn1OCSPCertID &certId = singleReq.reqCert;

	/* algId refers to the hash we'll perform in issuer name and key */
	certId.algId.algorithm = CSSMOID_SHA1;
	certId.algId.parameters.Data = nullParam;
	certId.algId.parameters.Length = sizeof(nullParam);
	
	/* SHA1(issuerName) */
	CSSM_DATA issuerName = {0, NULL};
	issuerName.Data = (uint8 *)parser.fieldForOid(CSSMOID_X509V1IssuerNameStd, 
		issuerName.Length);
	if(issuerName.Data == NULL) {
		printf("***Error fetching issuer name. Aborting.\n");
		return 1;
	}
	uint8 issuerNameHash[CC_SHA1_DIGEST_LENGTH];
	ocspdSha1(issuerName.Data, issuerName.Length, issuerNameHash);
	
	/* SHA1(issuer public key) */
	CSSM_KEY_PTR pubKey = NULL;
	CSSM_SIZE pubKeyLen = sizeof(CSSM_KEY);
	pubKey = (CSSM_KEY_PTR)issuerParser.fieldForOid(CSSMOID_CSSMKeyStruct, pubKeyLen);
	if(pubKey == NULL) {
		printf("***Error fetching public key from issuer cert. Aborting.\n");
		return 1;
	}
	uint8 pubKeyHash[CC_SHA1_DIGEST_LENGTH];
	ocspdSha1(pubKey->KeyData.Data, pubKey->KeyData.Length, pubKeyHash);

	/* serial number */
	CSSM_DATA serialNum = {0, NULL};
	serialNum.Data = (uint8 *)parser.fieldForOid(CSSMOID_X509V1SerialNumber, 
		serialNum.Length);
	if(serialNum.Data == NULL) {
		printf("***Error fetching serial number. Aborting.\n");
		return 1;
	}

	/* build the CertID from those components */
	certId.issuerNameHash.Data = issuerNameHash;
	certId.issuerNameHash.Length = CC_SHA1_DIGEST_LENGTH;
	certId.issuerPubKeyHash.Data = pubKeyHash;
	certId.issuerPubKeyHash.Length = CC_SHA1_DIGEST_LENGTH;	
	certId.serialNumber = serialNum;

	/* 
	 * Build top level request with one entry in requestList, no signature,
	 * one extension (a nonce)
	 */
	SecAsn1OCSPSignedRequest signedReq;
	SecAsn1OCSPRequest *reqArray[2] = { &singleReq, NULL };
	SecAsn1OCSPTbsRequest &tbs = signedReq.tbsRequest;
	memset(&signedReq, 0, sizeof(signedReq));
	uint8 version = 0;
	CSSM_DATA vers = {1, &version};
	tbs.version = &vers;
	tbs.requestList = reqArray;

	/* one extension - the nonce */
	SecAsn1CoderRef coder;
	SecAsn1CoderCreate(&coder);
	uint8 nonceBytes[8] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
	CSSM_DATA nonceData = {8, nonceBytes};
	OCSPNonce *nonce = new OCSPNonce(coder, false, nonceData);
	NSS_CertExtension *extenArray[2] = {nonce->nssExt(), NULL};
	tbs.requestExtensions = extenArray;
	
	/* Encode */
	OSStatus ortn;
	CSSM_DATA encoded = {0, NULL};
	ortn = SecAsn1EncodeItem(coder, &signedReq, kSecAsn1OCSPSignedRequestTemplate,
		&encoded);
	if(ortn) {
		printf("***Error encoding SecAsn1OCSPSignedRequest\n");
	}
	else {
		*outFile = (unsigned char *)malloc(encoded.Length);
		*outFileLen = encoded.Length;
		memmove(*outFile, encoded.Data, encoded.Length);
	}
	SecAsn1CoderRelease(coder);
	return (int)ortn;
}

static void dumpCertID(
	SecAsn1OCSPCertID *certID,
	int indent)
{
	doIndent(indent);
	printf("algId            : ");
	printDataAsHex(&certID->algId.algorithm);
	
	doIndent(indent);
	printf("issuerNameHash   : ");
	printDataAsHex(&certID->issuerNameHash);
	
	doIndent(indent);
	printf("issuerPubKeyHash : ");
	printDataAsHex(&certID->issuerPubKeyHash);
	
	doIndent(indent);
	printf("serialNumber     : ");
	printDataAsHex(&certID->serialNumber);
}

static void printCritical(
	int indent,
	OCSPExtension *ocspExt)
{
	doIndent(indent);
	printf("Critical           : %s\n", ocspExt->critical() ? "true" : "false");
}

static void printOcspExt(
	SecAsn1CoderRef coder,
	NSS_CertExtension *nssExt, 
	int indent)
{
	OCSPExtension *ocspExt = NULL;
	try {
		ocspExt = OCSPExtension::createFromNSS(coder, *nssExt);
	}
	catch(...) {
		doIndent(indent);
		printf("***Error thrown parsing extension\n");
		return;
	}
	switch(ocspExt->tag()) {
		case OET_Unknown: 
			doIndent(indent);
			printf("Extension type: Unknown\n");
			printCritical(indent, ocspExt);
			return;
		case OET_Nonce: 
		{
			doIndent(indent);
			printf("Extension type     : Nonce\n");
			printCritical(indent, ocspExt);
			doIndent(indent);
			OCSPNonce *nonce = dynamic_cast<OCSPNonce *>(ocspExt);
			if(nonce == NULL) {
				printf("***dynamic_cast failure in OCSPNonce!\n");
				return;
			}
			printf("nonce value        : ");
			printDataAsHex(&nonce->nonce());
			break;
		}
		case OET_CrlReference: 
			doIndent(indent);
			printf("Extension type     : CrlReference");
			printCritical(indent, ocspExt);
			/* TBD */
			return;
		case OET_AcceptResponse: 
			doIndent(indent);
			printf("Extension type     : AcceptResponse");
			printCritical(indent, ocspExt);
			/* TBD */
			return;
		case OET_ArchiveCutoff:
			doIndent(indent);
			printf("Extension type     : ArchiveCutoff");
			printCritical(indent, ocspExt);
			/* TBD */
			return;
		case OET_ServiceLocator:
			doIndent(indent);
			printf("Extension type     : ServiceLocator");
			printCritical(indent, ocspExt);
			/* TBD */
			return;
		default:
			/* this code is out of sync with ocspExtensions.{h,cpp} */
			doIndent(indent);
			printf("Extension type     : unrecognized - code sync error");
			printCritical(indent, ocspExt);
			return;
			
	}
}

static int parseOcspReq(
	CSSM_CL_HANDLE clHand, 
	unsigned char *inFile, 
	unsigned inFileLen,
	bool verbose)
{
	SecAsn1CoderRef coder;
	SecAsn1OCSPSignedRequest signedReq;
	SecAsn1OCSPTbsRequest &tbs = signedReq.tbsRequest;
	OSStatus ortn;
	int indent;
	unsigned numExts;
	unsigned numReqs;
	
	SecAsn1CoderCreate(&coder);
	memset(&signedReq, 0, sizeof(signedReq));
	
	ortn = SecAsn1Decode(coder, inFile, inFileLen, kSecAsn1OCSPSignedRequestTemplate,
		&signedReq);
	if(ortn) {
		printf("***Error decoding SecAsn1OCSPSignedRequest\n");
		goto errOut;
	}
	printf("SecAsn1OCSPSignedRequest:\n");
	
	printf("SecAsn1OCSPTbsRequest:\n");
	indent = 2;
	if(tbs.version) {
		doIndent(indent);
		printf("Version : ");
		printDataAsHex(tbs.version);
	}
	if(tbs.requestorName) {
		doIndent(indent);
		printf("NSS_GeneralName found; print it later maybe\n");
	}
	numReqs = ocspdArraySize((const void **)tbs.requestList);
	for(unsigned dex=0; dex<numReqs; dex++) {
		SecAsn1OCSPRequest *req = tbs.requestList[dex];
		doIndent(indent);
		printf("Request List Entry %u\n", dex);
		indent += 2;
		doIndent(indent);
		printf("CertID:\n");
		indent += 2;
		SecAsn1OCSPCertID *certID = &req->reqCert;
		dumpCertID(certID, indent);
		indent -= 2;
		numExts = ocspdArraySize((const void **)req->extensions);
		for(unsigned extDex=0; extDex<numExts; extDex++) {
			doIndent(indent);
			printf("singleExtension[%u]\n", extDex);
			printOcspExt(coder, req->extensions[dex], indent + 2);
		}
		indent -= 2;
	}

	numExts = ocspdArraySize((const void **)tbs.requestExtensions);
	for(unsigned extDex=0; extDex<numExts; extDex++) {
		doIndent(indent);
		printf("requestExtension[%u]\n", extDex);
		printOcspExt(coder, tbs.requestExtensions[extDex], indent + 2);
	}

	indent -= 2;
	
	if(signedReq.signature) {
		printf("SecAsn1OCSPSignature:\n");
		indent += 2;
		doIndent(indent);
		printf("==unparsed for now ==\n");
		/* ... */
		indent -= 2;
	}
errOut:
	SecAsn1CoderRelease(coder);
	return ortn;
}

static int genOcspResp(
	CSSM_CL_HANDLE clHand, 
	SecAsn1OCSPCertStatusTag status,
	CE_CrlReason reason,			// for CS_Revoked
	const unsigned char *subjectCert,
	unsigned subjectCertLen,
	const unsigned char *issuerCert,
	unsigned issuerCertLen,
	SecIdentityRef signer,
	unsigned char **outData,
	unsigned *outDataLen)
{
	SecAsn1CoderRef coder;
	SecAsn1CoderCreate(&coder);

	CSSM_DATA subjectCertData = {subjectCertLen, (uint8 *)subjectCert};
	CSSM_DATA issuerCertData = {issuerCertLen, (uint8 *)issuerCert};
	uint8 nonceBytes[8] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
	CSSM_DATA nonceData = {8, nonceBytes};
	CSSM_DATA tbs;
	CSSM_DATA encoded = {0, NULL};
	SecAsn1OCSPResponse topResponse;
	SecAsn1OCSPResponseBytes responseBytes;
	uint8 responseStatusByte;
	CSSM_DATA resp = {0, NULL};
	CSSM_DATA sig = {0, NULL};
	
	int irtn = genTbsResp(coder, clHand, status, reason, 
		subjectCertData, issuerCertData,
		0,			// thisUpdate
		2600 * 24,	// next update
		&nonceData,
		tbs);
	if(irtn) {
		printf("***Error encoding tbsResp\n");
		return irtn;
	}
	
	/* 
	 * That's the TBSResponseData. Sign it.
	 */
	OSStatus ortn;
	SecAsn1OCSPBasicResponse basicResp;
	memset(&basicResp, 0, sizeof(basicResp));
	ortn = ocspSign(signer, tbs, CSSM_ALGID_SHA1WithRSA, sig);
	if(ortn) {
		printf("***Error signing basicResponse.\n");
		goto errOut;
	}
	basicResp.algId.algorithm = CSSMOID_SHA1WithRSA;
	basicResp.algId.parameters.Data = nullParam;
	basicResp.algId.parameters.Length = sizeof(nullParam);
	basicResp.tbsResponseData = tbs;
	basicResp.sig = sig;
	/* ASN1 encoder needs to know length in bits */
	basicResp.sig.Length *= 8;
	/* no certs for now */
	/* encode SecAsn1OCSPBasicResponse */
	
	ortn = SecAsn1EncodeItem(coder, &basicResp, kSecAsn1OCSPBasicResponseTemplate,
		&encoded);
	if(ortn) {
		printf("***Error encoding SecAsn1OCSPBasicResponse\n");
	}
	
	/* put that into a SecAsn1OCSPResponse */
	responseBytes.responseType = CSSMOID_PKIX_OCSP_BASIC;
	responseBytes.response = encoded;
	responseStatusByte = RS_Success;
	topResponse.responseStatus.Data = &responseStatusByte;
	topResponse.responseStatus.Length = 1;
	topResponse.responseBytes = &responseBytes;
	ortn = SecAsn1EncodeItem(coder, &topResponse, kSecAsn1OCSPResponseTemplate,
		&resp);
	if(ortn) {
		printf("***Error encoding SecAsn1OCSPBasicResponse\n");
		goto errOut;
	}

	/* TA DA */
	*outData = (unsigned char *)malloc(resp.Length);
	*outDataLen = resp.Length;
	memmove(*outData, resp.Data, resp.Length);
errOut:
	SecAsn1CoderRelease(coder);
	if(sig.Data) {
		APP_FREE(sig.Data);
	}
	return ortn;
}

/* decode and parse tbsResponseData, sitting in SecAsn1OCSPBasicResponse as an 
 * ASN_ANY */
static int 	parseResponseData(
	SecAsn1CoderRef coder,
	int indent, 
	const CSSM_DATA &tbsResponseData)
{
	SecAsn1OCSPResponseData	respData;
	SecAsn1OCSPResponderID responderID;
	uint8 tag;
	const SecAsn1Template *templ;
	unsigned numExts;
	
	memset(&respData, 0, sizeof(respData));
	OSStatus ortn = SecAsn1DecodeData(coder, &tbsResponseData,
		kSecAsn1OCSPResponseDataTemplate, &respData);
	if(ortn) {
		printf("***Error decoding ResponseData\n");
		return 1;
	}
	if(respData.version && respData.version->Data) {
		doIndent(indent);
		printf("version: %u\n", respData.version->Data[0]);
	}
	doIndent(indent);
	printf("ResponderID:\n");
	indent += 2;
	memset(&responderID, 0, sizeof(responderID));
	if(respData.responderID.Data == NULL) {
		doIndent(indent);
		printf("***Malformed(empty)***\n");
		return 1;
	}
	
	/* lame-o choice processing */
	tag = respData.responderID.Data[0] & SEC_ASN1_TAGNUM_MASK;
	switch(tag) {
		case RIT_Name: 
			templ = kSecAsn1OCSPResponderIDAsNameTemplate; 
			break;
		case RIT_Key: 
			templ = kSecAsn1OCSPResponderIDAsKeyTemplate; 
			break;
		default:
			doIndent(indent);
			printf("**Unknown tag for ResponderID (%u)\n", tag);
			return 1;
	}
	ortn = SecAsn1DecodeData(coder, &respData.responderID, templ, &responderID);
	if(ortn) {
		doIndent(indent);
		printf("***Error decoding ResponderID\n");
		return 1;
	}
	doIndent(indent);
	switch(tag) {
		case RIT_Name:
			printf("byName:\n");
			printName((NSS_Name &)responderID.byName, indent + 2);
			break;
		case RIT_Key:
			printf("byKey : ");
			printDataAsHex(&responderID.byKey);
			break;
	}
	indent -= 2;		// end of ResponderID
	
	doIndent(indent);
	printf("producedAt: ");
	printString(&respData.producedAt);
	unsigned numResps = ocspdArraySize((const void **)respData.responses);
	doIndent(indent);
	printf("Num responses: %u\n", numResps);
	for(unsigned dex=0; dex<numResps; dex++) {
		SecAsn1OCSPSingleResponse *resp = respData.responses[dex];
		doIndent(indent);
		printf("Response %u:\n", dex);
		indent += 2;
		doIndent(indent);
		printf("CertID:\n");
		dumpCertID(&resp->certID, indent + 2);
		
		doIndent(indent);
		printf("certStatus: ");
		/* lame-o choice processing */
		tag = resp->certStatus.Data[0] & SEC_ASN1_TAGNUM_MASK;
		switch(tag) {
			case CS_Good:
				printf("Good\n");
				break;
			case CS_Unknown:
				printf("Unknown\n");
				break;
			default:
				printf("**MALFORMED cert status tag (%u)\n", tag);
				break;
			case CS_Revoked:
			{
				printf("Revoked\n");
				doIndent(indent);
				SecAsn1OCSPCertStatus certStatus;
				memset(&certStatus, 0, sizeof(certStatus));
				ortn = SecAsn1DecodeData(coder, &resp->certStatus, 
					kSecAsn1OCSPCertStatusRevokedTemplate, &certStatus);
				if(ortn) {
					doIndent(indent);
					printf("***error parsing RevokedInfo\n");
					break;
				}
				if(certStatus.revokedInfo == NULL) {
					doIndent(indent);
					printf("***GAK! Malformed (empty) revokedInfo\n");break;
				}
				printf("RevokedIndfo:\n");
				indent += 2;
				doIndent(indent);
				printf("revocationTime: ");
				printString(&certStatus.revokedInfo->revocationTime);
				if(certStatus.revokedInfo->revocationReason) {
					doIndent(indent);
					printf("reason: %u\n", 
						certStatus.revokedInfo->revocationReason->Data[0]);
				}
				indent -= 2;		// end of RevokedInfo
				break;
			}
		}	/* switch cert status tag */
		
		doIndent(indent);
		printf("thisUpdate: ");
		printString(&resp->thisUpdate);
		
		if(resp->nextUpdate) {
			doIndent(indent);
			printf("nextUpdate: ");
			printString(resp->nextUpdate);
		}

		numExts = ocspdArraySize((const void **)resp->singleExtensions);
		for(unsigned extDex=0; extDex<numExts; extDex++) {
			doIndent(indent);
			printf("singleExtensions[%u]\n", extDex);
			printOcspExt(coder, resp->singleExtensions[extDex], indent + 2);
		}
		
		indent -= 2;		// end of resp[dex]
	}
	
	numExts = ocspdArraySize((const void **)respData.responseExtensions);
	for(unsigned extDex=0; extDex<numExts; extDex++) {
		doIndent(indent);
		printf("responseExtensions[%u]\n", extDex);
		printOcspExt(coder, respData.responseExtensions[extDex], indent + 2);
	}
	return 0;
}

static int parseOcspResp(
	CSSM_CL_HANDLE clHand, 
	unsigned char *inFile, 
	unsigned inFileLen,
	bool verbose)
{
	SecAsn1OCSPResponse topResp;
	SecAsn1CoderRef coder;
	OSStatus ortn;
	int indent = 0;
	const char *str;
	SecAsn1OCSPBasicResponse basicResp;
	unsigned numCerts = 0;
	
	SecAsn1CoderCreate(&coder);	
	memset(&topResp, 0, sizeof(topResp));
	ortn = SecAsn1Decode(coder, inFile, inFileLen, kSecAsn1OCSPResponseTemplate,
		&topResp);
	if(ortn) {
		printf("***Error decoding SecAsn1OCSPResponse\n");
		goto errOut;
	}
	printf("OCSPResponse:\n");
	indent += 2;
	doIndent(indent);
	printf("responseStatus: ");
	if(topResp.responseStatus.Length == 0) {
		printf("**MALFORMED**\n");
	}
	else {
		switch(topResp.responseStatus.Data[0]) {
			case RS_Success: str = "RS_Success"; break;
			case RS_MalformedRequest: str = "RS_MalformedRequest"; break;
			case RS_InternalError: str = "RS_InternalError"; break;
			case RS_TryLater: str = "RS_TryLater"; break;
			case RS_Unused: str = "RS_Unused"; break;
			case RS_SigRequired: str = "RS_SigRequired"; break;
			case RS_Unauthorized: str = "RS_Unauthorized"; break;
			default: str = "MALFORMED (unknown enum)\n"; break;
		}
		printf("%s (%u(d))\n", str, topResp.responseStatus.Data[0]);
	}
	doIndent(indent);
	printf("ResponseBytes: ");
	if(topResp.responseBytes == NULL) {
		printf("empty\n");
		goto errOut;
	}
	printf("\n");
	indent += 2;
	doIndent(indent);
	printf("responseType: ");
	if(appCompareCssmData(&topResp.responseBytes->responseType,
			&CSSMOID_PKIX_OCSP_BASIC)) {
		str = "ocsp-basic";
	}
	else {
		str = "Unknown type\n";
	}
	printf("%s\n", str);
		
	/* decode the BasicOCSPResponse */
	memset(&basicResp, 0, sizeof(basicResp));
	ortn = SecAsn1DecodeData(coder, &topResp.responseBytes->response,
		kSecAsn1OCSPBasicResponseTemplate, &basicResp);
	if(ortn) {
		printf("***Error decoding BasicOCSPResponse\n");
		goto errOut;
	}
	
	doIndent(indent);
	printf("BasicOCSPResponse:\n");
	indent += 2;
	doIndent(indent);
	printf("ResponseData:\n");
	parseResponseData(coder, indent + 2, basicResp.tbsResponseData);
	doIndent(indent);
	printf("sig: ");
	printDataAsHex(&basicResp.sig, 8);
	numCerts = ocspdArraySize((const void **)basicResp.certs);
	doIndent(indent);
	printf("Num Certs: %u\n", numCerts);
	
	if(verbose) {
		for(unsigned dex=0; dex<numCerts; dex++) {
			printf("+++++++++++++++++++++++++ Cert %u +++++++++++++++++++++++++\n", dex);
			printCert(basicResp.certs[dex]->Data, basicResp.certs[dex]->Length, 
				CSSM_FALSE);
			printf("+++++++++++++++++++++++ End Cert %u +++++++++++++++++++++++\n", dex);
		}
	}
	indent -= 2;		// end of BasicOCSPResponse
	indent -= 2;		// end of ResponseBytes
	indent -= 2;		// end of OCSPResponse
errOut:
	SecAsn1CoderRelease(coder);
	return ortn;
}

static int postOcspReq(
	CSSM_CL_HANDLE clHand,
	const unsigned char *certFile, 
	unsigned certFileLen, 
	const unsigned char *issuerCertFile, 
	unsigned issuerCertFileLen, 
	const char *responderURI,
	bool doParse,
	bool verbose,
	unsigned char **outFile,		// RETURNED 
	unsigned *outFileLen)			// RETURNED
{
	auto_ptr<CertParser> subject;
	auto_ptr<CertParser> issuer;
	CSSM_DATA uriData = {0, NULL};
	CSSM_DATA *url = NULL;
	
	try {
		CSSM_DATA cdata = {certFileLen, (uint8 *)certFile};
		subject.reset(new CertParser(clHand, cdata));
	}
	catch(...) {
		printf("***Error parsing subject cert. Aborting.\n");
		return -1;
	}
	try {
		CSSM_DATA cdata = {issuerCertFileLen, (uint8 *)issuerCertFile};
		issuer.reset(new CertParser(clHand, cdata));
	}
	catch(...) {
		printf("***Error parsing issuer cert. Aborting.\n");
		return -1;
	}
	
	SecAsn1CoderRef coder;
	SecAsn1CoderCreate(&coder);
	/* subsequent errors to errOut: */
	int ourRtn = 0;
	const CSSM_DATA *derReq = NULL;
	auto_ptr<OCSPRequest> ocspReq;
	
	if(responderURI != NULL) {
		uriData.Data = (uint8 *)responderURI;
		uriData.Length = strlen(responderURI);
		url = &uriData;
	}
	else {
		/* get OCSP URL from subject cert */
		url = ocspUrlFromCert(*subject, coder);
		if(url == NULL) {
			printf("Sorry, no can do.\n");
			ourRtn = -1;
			goto errOut;
		}
	}
	
	/* create DER-encoded OCSP request for subject */
	try {
		ocspReq.reset(new OCSPRequest(*subject, *issuer, false));
		derReq = ocspReq->encode();
	}
	catch(...) {
		printf("***Error creating OCSP request. Aborting.\n");
		ourRtn = -1;
		goto errOut;
	}
	
	/* do it */
	CSSM_DATA ocspResp;
	CSSM_RETURN crtn;
	crtn = ocspdHttpPost(coder, *url, *derReq, ocspResp);
	if(crtn) {
		printf("***Error fetching OCSP response***\n");
		cssmPerror("ocspdHttpPost", crtn);
		ourRtn = -1;
		goto errOut;
	}
	*outFile = ocspResp.Data;
	*outFileLen = ocspResp.Length;
	if(doParse) {
		parseOcspResp(clHand, ocspResp.Data, ocspResp.Length, verbose);
	}
	/* copy out */
	*outFile = (unsigned char *)malloc(ocspResp.Length);
	*outFileLen = ocspResp.Length;
	memmove(*outFile, ocspResp.Data, ocspResp.Length);

errOut:
	SecAsn1CoderRelease(coder);
	return ourRtn;
}

typedef enum {
	op_genReq,
	op_parseReq,
	op_genReply,
	op_parseResp,
	op_post
} ocspOp;

int main(int argc, char **argv)
{
	if(argc < 2) {
		usage(argv);
	}
	ocspOp op;
	switch(argv[1][0]) {
		case 'g': op = op_genReq; break;
		case 'G': op = op_parseReq; break;
		case 'r': op = op_genReply; break;
		case 'R': op = op_parseResp; break;
		case 'p': op = op_post; break;
		default: usage(argv);
	}
	
	/* user defined vars */
	char *inFile = NULL;
	char *outFile = NULL;
	char *inCertName = NULL;
	char *issuerCertName = NULL;
	SecAsn1OCSPCertStatusTag certStatus = CS_Good;
	CE_CrlReason crlReason = CE_CR_Unspecified;
	char *kcName = NULL;
	bool verbose = false;
	bool doParse = false;
	const char *responderURI = NULL;
	
    extern int optind;
	optind = 2;
    extern char *optarg;
	int arg;
    while ((arg = getopt(argc, argv, "c:C:i:o:s:r:k:phvu:")) != -1) {
		switch (arg) {
			case 'c':
				inCertName = optarg;
				break;
			case 'C':
				issuerCertName = optarg;
				break;
			case 'i':
				inFile = optarg;
				break;
			case 'o':
				outFile = optarg;
				break;
			case 's':
				switch(optarg[0]) {
					case 'g':
						certStatus = CS_Good;
						break;
					case 'r':
						certStatus = CS_Revoked;
						break;
					case 'u':
						certStatus = CS_Unknown;
						break;
					default:
						printf("***Unrecognized certStatus***\n");
						usage(argv);
				}
				break;
			case 'r':
				crlReason = atoi(optarg);
				break;
			case 'k':
				kcName = optarg;
				break;
			case 'v':
				verbose = 1;
				break;
			case 'p':
				doParse = true;
				break;
			case 'u':
				responderURI = optarg;
				break;
			default:
			case '?':
				usage(argv);
		}
	}
	if(optind != argc) {
		/* this happens if you give getopt() an arg which doesn't start with '-' */
		usage(argv);
	}
	
	unsigned char *certData = NULL;
	unsigned certDataLen = 0;
	unsigned char *issuerCertData = NULL;
	unsigned issuerCertDataLen = 0;
	unsigned char *inData = NULL;
	unsigned inDataLen = 0;
	unsigned char *outData = NULL;
	unsigned outDataLen = 0;
	SecKeychainRef kcRef = NULL;
	OSStatus ortn;
	
	if(inCertName) {
		if(readFile(inCertName, &certData, &certDataLen)) {
			printf("***Error reading cert file %s. Aborting.\n", inCertName);
			exit(1);
		}
	}
	if(issuerCertName) {
		if(readFile(issuerCertName, &issuerCertData, &issuerCertDataLen)) {
			printf("***Error reading cert file %s. Aborting.\n", issuerCertName);
			exit(1);
		}
	}
	if(inFile) {
		if(readFile(inFile, &inData, &inDataLen)) {
			printf("***Error reading input file %s. Aborting.\n", inFile);
			exit(1);
		}
	}
	if(kcName) {
		ortn = SecKeychainOpen(kcName, &kcRef);
		if(ortn) {
			cssmPerror("SecKeychainOpen", ortn);
			return ortn;
		}
	}
	CSSM_CL_HANDLE clHand = cuClStartup();
	
	switch(op) {
		case op_genReq:
			ortn = genOcspReq(clHand, certData, certDataLen, 
				issuerCertData, issuerCertDataLen,
				&outData, &outDataLen);
			break;
		case op_parseReq:
			ortn = parseOcspReq(clHand, inData, inDataLen, verbose);
			break;
		case op_genReply:
		{
			SecIdentityRef idRef = NULL;
			ortn = sslSimpleIdentPicker(kcRef, &idRef);
			if(ortn) {
				printf("***Error choosing identity. Aborting.\n");
				exit(1);
			}
			ortn = genOcspResp(clHand, certStatus, crlReason,
				certData, certDataLen, issuerCertData, issuerCertDataLen,
				idRef, &outData, &outDataLen);
			CFRelease(idRef);
			break;
		}
		case op_parseResp:
			ortn = parseOcspResp(clHand, inData, inDataLen, verbose);
			break;
		case op_post:
			ortn = postOcspReq(clHand, certData, certDataLen, 
				issuerCertData, issuerCertDataLen, responderURI,
				doParse, verbose,
				&outData, &outDataLen);
			break;
		default:	
			printf("Op %s is not yet implemented.\n", argv[1]);
			exit(1);
	}
	
	if(ortn == 0) {
		if(outData != NULL) {
			if(outFile== NULL) {
				printf("...generated %u bytes but no place to write it.\n", outDataLen);
			}
			else {
				ortn = writeFile(outFile, outData, outDataLen);
				if(ortn) {
					printf("***Error writing output to %s.\n", outFile);
				}
				else {
					printf("...wrote %u bytes to %s\n", outDataLen, outFile);
				}
			}
		}
	}
	return ortn;
}