/*
 * Copyright (c) 2002 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */
 
/*
 * cuPrintCert.cpp - Parse a cert or CRL, dump contents.
 */
#include "cuCdsaUtils.h"
#include <stdio.h>
#include <stdlib.h>
#include <Security/oidscert.h>
#include <Security/oidscrl.h>
#include <Security/x509defs.h>
#include <Security/oidsattr.h>
#include <Security/cssmapple.h>
#include <string.h>
#include "cuPrintCert.h"
#include "cuOidParser.h"
#include "cuTimeStr.h"
#include <Security/certextensions.h>

static char *months[] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun", 
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 
};
	
static void printTimeStr(const CSSM_DATA *cssmTime)
{
	struct tm tm;
	
	/* ignore cssmTime->timeType for now */
	if(cuTimeStringToTm((char *)cssmTime->Data, cssmTime->Length, &tm)) {
		printf("***Bad time string format***\n");
		return;
	}
	if(tm.tm_mon > 11) {
		printf("***Bad time string format***\n");
		return;
	}
	printf("%02d:%02d:%02d %s %d, %04d\n",
		tm.tm_hour, tm.tm_min, tm.tm_sec,
		months[tm.tm_mon], tm.tm_mday, tm.tm_year + 1900);

}


static void printTime(const CSSM_X509_TIME *cssmTime)
{
	/* ignore cssmTime->timeType for now */
	printTimeStr(&cssmTime->time);
}

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");
	}
}

/*
 * Identify CSSM_BER_TAG with a C string.
 */
static char *tagTypeString(
	CSSM_BER_TAG tagType)
{
	static char unknownType[80];
	
	switch(tagType) {
		case BER_TAG_UNKNOWN:
			return "BER_TAG_UNKNOWN";
		case BER_TAG_BOOLEAN:
			return "BER_TAG_BOOLEAN";
		case BER_TAG_INTEGER:
			return "BER_TAG_INTEGER";
		case BER_TAG_BIT_STRING:
			return "BER_TAG_BIT_STRING";
		case BER_TAG_OCTET_STRING:
			return "BER_TAG_OCTET_STRING";
		case BER_TAG_NULL:
			return "BER_TAG_NULL";
		case BER_TAG_OID:
			return "BER_TAG_OID";
		case BER_TAG_SEQUENCE:
			return "BER_TAG_SEQUENCE";
		case BER_TAG_SET:
			return "BER_TAG_SET";
		case BER_TAG_PRINTABLE_STRING:
			return "BER_TAG_PRINTABLE_STRING";
		case BER_TAG_T61_STRING:
			return "BER_TAG_T61_STRING";
		case BER_TAG_IA5_STRING:
			return "BER_TAG_IA5_STRING";
		case BER_TAG_UTC_TIME:
			return "BER_TAG_UTC_TIME";
		case BER_TAG_GENERALIZED_TIME:
			return "BER_TAG_GENERALIZED_TIME";
		default:
			sprintf(unknownType, "Other type (0x%x)", tagType);
			return unknownType;
	}
}

/*
 * Print an OID, assumed to be in BER encoded "Intel" format
 * Length is inferred from oid->Length
 * Tag is implied
 */
static void printOid(OidParser &parser, const CSSM_DATA *oid)
{
	char strBuf[OID_PARSER_STRING_SIZE];
	
	if(oid == NULL) {
		printf("NULL\n");
		return;
	}
	if((oid->Length == 0) || (oid->Data == NULL)) {
		printf("EMPTY\n");
		return;
	}
	parser.oidParse(oid->Data, oid->Length, strBuf);
	printf("%s\n", strBuf);
}

/*
 * Used to print generic blobs which we don't really understand.
 * The bytesToPrint argument is usually thing->Length; it's here because snacc
 * peports lengths of bit strings in BITS. Caller knows this and
 * modifies bytesToPrint accordingly. In any case, bytesToPrint is the
 * max number of valid bytes in *thing->Data.
 */ 
#define BLOB_LENGTH_PRINT	3

static void printBlobBytes(
	char 			*blobType,
	char 			*quanta,		// e.g., "bytes', "bits"
	uint32			bytesToPrint,
	const CSSM_DATA	*thing)
{
	uint32 dex;
	uint32 toPrint = bytesToPrint;
	
	if(toPrint > BLOB_LENGTH_PRINT) {
		toPrint = BLOB_LENGTH_PRINT;
	}
	printf("%s; Length %u %s; data = ", 
		blobType, (unsigned)thing->Length, quanta);
	for(dex=0; dex<toPrint; dex++) {
		printf("0x%x ", thing->Data[dex]);
		if(dex == (toPrint - 1)) {
			break;
		}
	}
	if(dex < bytesToPrint) {
		printf(" ...\n");
	}
	else {
		printf("\n");
	}
}

/*
 * Print an IA5String or Printable string. Null terminator is not assumed. 
 * Trailing newline is printed.
 */
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 printDerThing(
	CSSM_BER_TAG		tagType,
	const CSSM_DATA		*thing,
	OidParser 			&parser)
{
	switch(tagType) {
		case BER_TAG_INTEGER:
			printf("%d\n", cuDER_ToInt(thing));
			return;
		case BER_TAG_BOOLEAN:
			if(thing->Length != 1) {
				printf("***malformed BER_TAG_BOOLEAN: length %u data ",
					(unsigned)thing->Length);
			}
			printf("%u\n", cuDER_ToInt(thing));
			return;
		case BER_TAG_PRINTABLE_STRING:
		case BER_TAG_IA5_STRING:	
		case BER_TAG_T61_STRING:		
		case BER_TAG_PKIX_UTF8_STRING:	// mostly printable....	
			printString(thing);
			return;
		case BER_TAG_OCTET_STRING:
			printBlobBytes("Byte string", "bytes", thing->Length, thing);
			return;
		case BER_TAG_BIT_STRING:
			printBlobBytes("Bit string", "bits", (thing->Length + 7) / 8, thing);
			return;
		case BER_TAG_SEQUENCE:
			printBlobBytes("Sequence", "bytes", thing->Length, thing);
			return;
		case BER_TAG_SET:
			printBlobBytes("Set", "bytes", thing->Length, thing);
			return;
		case BER_TAG_OID:
			printf("OID = ");
			printOid(parser, thing);
			break;
		default:
			printf("not displayed (tagType = %s; length %u)\n", 
				tagTypeString(tagType), (unsigned)thing->Length);
			break;
			
	}
}

static void printSigAlg(
	const CSSM_X509_ALGORITHM_IDENTIFIER *sigAlg,
	OidParser 							&parser)
{
	printOid(parser, &sigAlg->algorithm);
	if(sigAlg->parameters.Data != NULL) {
		printf("   alg params      : ");
		printDataAsHex(&sigAlg->parameters, 8);
	}
}

/* compare two OIDs, return CSSM_TRUE if identical */
static CSSM_BOOL compareOids(
	const CSSM_OID *oid1,
	const CSSM_OID *oid2)
{
	if((oid1 == NULL) || (oid2 == NULL)) {
		return CSSM_FALSE;
	}	
	if(oid1->Length != oid2->Length) {
		return CSSM_FALSE;
	}
	if(memcmp(oid1->Data, oid2->Data, oid1->Length)) {
		return CSSM_FALSE;
	}
	else {
		return CSSM_TRUE;
	}
}	

static void printRdn(
	const CSSM_X509_RDN			*rdnp,
	OidParser 					&parser)
{
	CSSM_X509_TYPE_VALUE_PAIR 	*ptvp;
	unsigned					pairDex;
	char						*fieldName;
	
	for(pairDex=0; pairDex<rdnp->numberOfPairs; pairDex++) {
		ptvp = &rdnp->AttributeTypeAndValue[pairDex];
		if(compareOids(&ptvp->type, &CSSMOID_CountryName)) {
			fieldName = "Country       ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_OrganizationName)) {
			fieldName = "Org           ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_LocalityName)) {
			fieldName = "Locality      ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_OrganizationalUnitName)) {
			fieldName = "OrgUnit       ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_CommonName)) {
			fieldName = "Common Name   ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_Surname)) {
			fieldName = "Surname       ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_Title)) {
			fieldName = "Title         ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_Surname)) {
			fieldName = "Surname       ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_StateProvinceName)) {
			fieldName = "State         ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_CollectiveStateProvinceName)) {
			fieldName = "Coll. State   ";      
		}
		else if(compareOids(&ptvp->type, &CSSMOID_EmailAddress)) {
			/* deprecated, used by Thawte */
			fieldName = "Email addrs   ";      
		}
		else {
			fieldName = "Other name    ";      
		}
		printf("   %s  : ", fieldName);
		printDerThing(ptvp->valueType, &ptvp->value, parser);
	}	/* for each type/value pair */
}

static CSSM_RETURN printName(
	const CSSM_X509_NAME	 	*x509Name,
	OidParser 					&parser)
{
	CSSM_X509_RDN_PTR    		rdnp;
	unsigned					rdnDex;
	
	for(rdnDex=0; rdnDex<x509Name->numberOfRDNs; rdnDex++) {
		rdnp = &x509Name->RelativeDistinguishedName[rdnDex];
		printRdn(rdnp, parser);
	}		
	
	return CSSM_OK;
}

static void printKeyHeader(
	const CSSM_KEYHEADER &hdr)
{
	printf("   Algorithm       : ");
	switch(hdr.AlgorithmId) {
		case CSSM_ALGID_RSA:
			printf("RSA\n");
			break;
		case CSSM_ALGID_DSA:
			printf("DSA\n");
			break;
		case CSSM_ALGID_FEE:
			printf("FEE\n");
			break;
		case CSSM_ALGID_DH:
			printf("Diffie-Hellman\n");
			break;
		default:
			printf("Unknown(%u(d), 0x%x)\n", (unsigned)hdr.AlgorithmId, 
				(unsigned)hdr.AlgorithmId);
	}
	printf("   Key Size        : %u bits\n", (unsigned)hdr.LogicalKeySizeInBits);
	printf("   Key Use         : ");
	CSSM_KEYUSE usage = hdr.KeyUsage;
	if(usage & CSSM_KEYUSE_ANY) {
		printf("CSSM_KEYUSE_ANY ");
	}
	if(usage & CSSM_KEYUSE_ENCRYPT) {
		printf("CSSM_KEYUSE_ENCRYPT ");
	}
	if(usage & CSSM_KEYUSE_DECRYPT) {
		printf("CSSM_KEYUSE_DECRYPT ");
	}
	if(usage & CSSM_KEYUSE_SIGN) {
		printf("CSSM_KEYUSE_SIGN ");
	}
	if(usage & CSSM_KEYUSE_VERIFY) {
		printf("CSSM_KEYUSE_VERIFY ");
	}
	if(usage & CSSM_KEYUSE_SIGN_RECOVER) {
		printf("CSSM_KEYUSE_SIGN_RECOVER ");
	}
	if(usage & CSSM_KEYUSE_VERIFY_RECOVER) {
		printf("CSSM_KEYUSE_VERIFY_RECOVER ");
	}
	if(usage & CSSM_KEYUSE_WRAP) {
		printf("CSSM_KEYUSE_WRAP ");
	}
	if(usage & CSSM_KEYUSE_UNWRAP) {
		printf("CSSM_KEYUSE_UNWRAP ");
	}
	if(usage & CSSM_KEYUSE_DERIVE) {
		printf("CSSM_KEYUSE_DERIVE ");
	}
	printf("\n");

}

/*
 * Print contents of a CE_GeneralName as best we can.
 */
static void printGeneralName(
	const CE_GeneralName	*name,
	OidParser 				&parser)
{
	switch(name->nameType) {
		case GNT_RFC822Name:
			printf("   RFC822Name      : ");
			printString(&name->name);
			break;
		case GNT_DNSName:
			printf("   DNSName         : ");
			printString(&name->name);
			break;
		case GNT_URI:
			printf("   URI             : ");
			printString(&name->name);
			break;
		case GNT_IPAddress:
			printf("   IP Address      : ");
			for(unsigned i=0; i<name->name.Length; i++) {
				printf("%d", name->name.Data[i]);
				if(i < (name->name.Length - 1)) {
					printf(".");
				}
			}
			printf("\n");
			break;
		case GNT_RegisteredID:
			printf("   RegisteredID    : ");
			printOid(parser, &name->name);
			break;
		case GNT_X400Address:
			/* ORAddress, a very complicated struct - punt */
			printf("   X400Address     : ");
			printBlobBytes("Sequence", "bytes", name->name.Length, &name->name);
			break;
		case GNT_DirectoryName:
			if(!name->berEncoded) {
				/* CL parsed it for us into an CSSM_X509_NAME */
				if(name->name.Length != sizeof(CSSM_X509_NAME)) {
					printf("***MALFORMED GNT_DirectoryName\n");
					break;
				}
				const CSSM_X509_NAME *x509Name = 
					(const CSSM_X509_NAME *)name->name.Data;
				printf("   Dir Name        :\n");
				printName(x509Name, parser);
			}
			else {
				/* encoded Name (i.e. CSSM_X509_NAME) */
				printf("   Dir Name        : ");
				printBlobBytes("Byte string", "bytes", 
					name->name.Length, &name->name);
			}
			break;
		case GNT_EdiPartyName:
			/* sequence EDIPartyName */
			printf("   EdiPartyName    : ");
			printBlobBytes("Sequence", "bytes", name->name.Length, &name->name);
			break;
		case GNT_OtherName:
		{
			printf("   OtherName       :\n");
			if(name->name.Length != sizeof(CE_OtherName)) {
				printf("***Malformed CE_OtherName\n");
				break;
			}
			CE_OtherName *other = (CE_OtherName *)name->name.Data;
			printf("      typeID       : ");
			printOid(parser, &other->typeId);
			printf("      value        : ");
			printDataAsHex(&other->value, 0);
			break;
		}
	}
}


/*
 * Print contents of a CE_GeneralNames as best we can.
 */
static void printGeneralNames(
	const CE_GeneralNames	*generalNames,
	OidParser 				&parser)
{
	unsigned			i;
	CE_GeneralName		*name;
	
	for(i=0; i<generalNames->numNames; i++) {
		name = &generalNames->generalName[i];
		printGeneralName(name, parser);
	}
}

static int printCdsaExtensionCommon(
	const CSSM_X509_EXTENSION 	*cssmExt,
	OidParser					&parser,
	bool						expectParsed,
	CSSM_BOOL					verbose,
	bool						extraIndent = false)
{
	if(extraIndent) {
		printf("   Extension       : "); printOid(parser, &cssmExt->extnId);
		printf("      Critical     : %s\n", cssmExt->critical ? "TRUE" : "FALSE");
	}
	else {
		printf("Extension struct   : "); printOid(parser, &cssmExt->extnId);
		printf("   Critical        : %s\n", cssmExt->critical ? "TRUE" : "FALSE");
	}
	
	/* currently (since Radar 3593624), these are both always valid */
	if((cssmExt->BERvalue.Data == NULL) || 
	   (cssmExt->value.parsedValue == NULL)) {  /* actually, one of three variants */
		printf("***Malformed CSSM_X509_EXTENSION (1)\n");
		return 1;
	}
	switch(cssmExt->format) {
		case CSSM_X509_DATAFORMAT_ENCODED:
			if(expectParsed) {
				printf("Bad CSSM_X509_EXTENSION; expected FORMAT_PARSED\n");
				return 1;
			}
			break;
		case CSSM_X509_DATAFORMAT_PARSED:
			if(!expectParsed) {
				printf("Bad CSSM_X509_EXTENSION; expected FORMAT_ENCODED\n");
				return 1;
			}
			break;
		case CSSM_X509_DATAFORMAT_PAIR:
			/* unsupported */
			printf("Bad CSSM_X509_EXTENSION format:FORMAT_PAIR\n");
			return 1;
		default:
			printf("***Unknown CSSM_X509_EXTENSION.format\n");
			return 1;
	}
	return 0;
}

static int printExtensionCommon(
	const CSSM_DATA		&value,
	OidParser			&parser,
	CSSM_BOOL			verbose,
	bool				expectParsed = true)
{
	if(value.Length != sizeof(CSSM_X509_EXTENSION)) {
		printf("***malformed CSSM_FIELD (1)\n");
		return 1;
	}
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	return printCdsaExtensionCommon(cssmExt, parser, expectParsed, verbose);
}


static void printKeyUsage(
	const CSSM_DATA &value)
{
	CE_KeyUsage usage;
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	
	usage = *((CE_KeyUsage *)cssmExt->value.parsedValue);
	printf("   usage           : ");
	if(usage & CE_KU_DigitalSignature) {
		printf("DigitalSignature ");
	}
	if(usage & CE_KU_NonRepudiation) {
		printf("NonRepudiation ");
	}
	if(usage & CE_KU_KeyEncipherment) {
		printf("KeyEncipherment ");
	}
	if(usage & CE_KU_DataEncipherment) {
		printf("DataEncipherment ");
	}
	if(usage & CE_KU_KeyAgreement) {
		printf("KeyAgreement ");
	}
	if(usage & CE_KU_KeyCertSign) {
		printf("KeyCertSign ");
	}
	if(usage & CE_KU_CRLSign) {
		printf("CRLSign ");
	}
	if(usage & CE_KU_EncipherOnly) {
		printf("EncipherOnly ");
	}
	if(usage & CE_KU_DecipherOnly) {
		printf("DecipherOnly ");
	}
	printf("\n");

}

static void printBasicConstraints(
	const CSSM_DATA &value)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_BasicConstraints *bc = (CE_BasicConstraints *)cssmExt->value.parsedValue;
	printf("   CA              : %s\n", bc->cA ? "TRUE" : "FALSE");
	if(bc->pathLenConstraintPresent) {
		printf("   pathLenConstr   : %u\n", (unsigned)bc->pathLenConstraint);
	}
}
		
static void printExtKeyUsage(
	const CSSM_DATA 	&value,
	OidParser 			&parser)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)cssmExt->value.parsedValue;
	unsigned oidDex;
	for(oidDex=0; oidDex<eku->numPurposes; oidDex++) {
		printf("   purpose %2d      : ", oidDex);
		printOid(parser, &eku->purposes[oidDex]);
	}
}

static void printCssmAuthorityKeyId(
	const CE_AuthorityKeyID *akid,
	OidParser 				&parser)
{
	if(akid->keyIdentifierPresent) {
		printf("   Auth KeyID      : "); 
		printDataAsHex(&akid->keyIdentifier,
8);
	}
	if(akid->generalNamesPresent) {
		printGeneralNames(akid->generalNames, parser);
	}
	if(akid->serialNumberPresent) {
		printf("   serialNumber    : "); 
		printDataAsHex(&akid->serialNumber, 8);
	}
}

static void printAuthorityKeyId(
	const CSSM_DATA 	&value,
	OidParser 			&parser)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_AuthorityKeyID *akid = (CE_AuthorityKeyID *)cssmExt->value.parsedValue;
	printCssmAuthorityKeyId(akid, parser);
}

static void printSubjectIssuerAltName(
	const CSSM_DATA 	&value,
	OidParser 			&parser)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_GeneralNames *san = (CE_GeneralNames *)cssmExt->value.parsedValue;
	printGeneralNames(san, parser);
}

static void printDistPointName(
	const CE_DistributionPointName	*dpn,
	OidParser						&parser)
{
	switch(dpn->nameType) {
		case CE_CDNT_FullName:
			printGeneralNames(dpn->dpn.fullName, parser);
			break;
		case CE_CDNT_NameRelativeToCrlIssuer:
			printRdn(dpn->dpn.rdn, parser);
			break;
		default:
			printf("***BOGUS CE_DistributionPointName.nameType\n");
			break;
	}
}

static void printDistPoint(
	const CE_CRLDistributionPoint	*dp,
	OidParser						&parser)
{
	if(dp->distPointName) {
		printf("   Dist pt Name    :\n");
		printDistPointName(dp->distPointName, parser);
	}
	printf("   reasonsPresent  : %s\n", dp->reasonsPresent ? "TRUE" : "FALSE");
	if(dp->reasonsPresent) {
		/* FIXME - parse */
		printf("  reasons           : 0x%X\n", dp->reasons);
	}
	if(dp->crlIssuer) {
		printf("  CRLIssuer        :\n");
		printGeneralNames(dp->crlIssuer, parser);
	}
}

static void printDistributionPoints(
	const CSSM_DATA 	&value,
	OidParser 			&parser)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_CRLDistPointsSyntax *dps = (CE_CRLDistPointsSyntax *)cssmExt->value.parsedValue;
	
	for(unsigned dex=0; dex<dps->numDistPoints; dex++) {
		printf("   Dist pt %d       :\n", dex); 
		printDistPoint(&dps->distPoints[dex], parser);
	}
}

static void printValueOrNotPresent(
	CSSM_BOOL present,
	CSSM_BOOL value)
{
	if(!present) {
		printf("<Not Present>\n");
	}
	else if(value) {
		printf("TRUE\n");
	}
	else {
		printf("FALSE");
	}
}

static void printIssuingDistributionPoint(
	const CE_IssuingDistributionPoint 	*idp,
	OidParser 							&parser)
{
	if(idp->distPointName) {
		printf("   Dist pt          :\n"); 
		printDistPointName(idp->distPointName, parser);
	}
	printf("   Only user certs : "); 
	printValueOrNotPresent(idp->onlyUserCertsPresent, idp->onlyUserCerts);
	printf("   Only CA certs   : "); 
	printValueOrNotPresent(idp->onlyCACertsPresent, idp->onlyCACerts);
	printf("   Only some reason: "); 
	printValueOrNotPresent(idp->onlySomeReasonsPresent, idp->onlySomeReasons);
	printf("   Indirectl CRL   : "); 
	printValueOrNotPresent(idp->indirectCrlPresent, idp->indirectCrl);
}

static void printCertPolicies(
	const CSSM_DATA 	&value,
	OidParser 			&parser)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_CertPolicies *cdsaObj = (CE_CertPolicies *)cssmExt->value.parsedValue;
	for(unsigned polDex=0; polDex<cdsaObj->numPolicies; polDex++) {
		CE_PolicyInformation *cPolInfo = &cdsaObj->policies[polDex];
		printf("   Policy %2d       : ID ", polDex); 
		printOid(parser, &cPolInfo->certPolicyId);
		for(unsigned qualDex=0; qualDex<cPolInfo->numPolicyQualifiers; qualDex++) {
			CE_PolicyQualifierInfo *cQualInfo = &cPolInfo->policyQualifiers[qualDex];
			printf("      Qual %2d      : ID ", qualDex); 
			printOid(parser, &cQualInfo->policyQualifierId);
			if(cuCompareCssmData(&cQualInfo->policyQualifierId,
					&CSSMOID_QT_CPS)) {
				printf("         CPS       : ");
				printString(&cQualInfo->qualifier);
			}
			else {
				printf("         unparsed  : ");
				printDataAsHex(&cQualInfo->qualifier, 8);
			}
		}
	}
}

static void printNetscapeCertType(
	const CSSM_DATA &value)
{
	CE_NetscapeCertType certType;
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	
	certType = *((CE_NetscapeCertType *)cssmExt->value.parsedValue);
	printf("   certType        : ");
	if(certType & CE_NCT_SSL_Client) {
		printf("SSL_Client ");
	}
	if(certType & CE_NCT_SSL_Server) {
		printf("SSL_Server ");
	}
	if(certType & CE_NCT_SMIME) {
		printf("S/MIME ");
	}
	if(certType & CE_NCT_ObjSign) {
		printf("ObjectSign ");
	}
	if(certType & CE_NCT_Reserved) {
		printf("Reserved ");
	}
	if(certType & CE_NCT_SSL_CA) {
		printf("SSL_CA ");
	}
	if(certType & CE_NCT_SMIME_CA) {
		printf("SMIME_CA ");
	}
	if(certType & CE_NCT_ObjSignCA) {
		printf("ObjSignCA ");
	}
	printf("\n");
}

static void printAuthorityInfoAccess(
	const CSSM_DATA 	&value,
	OidParser 			&parser)
{
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
	CE_AuthorityInfoAccess	*info = (CE_AuthorityInfoAccess *)cssmExt->value.parsedValue;

	printf("   numDescriptions : %lu\n", info->numAccessDescriptions);
	for(unsigned dex=0; dex<info->numAccessDescriptions; dex++) {
		printf("   description %u   : \n", dex);
		printf("   accessMethod    : ");
		CE_AccessDescription *descr = &info->accessDescriptions[dex];
		printOid(parser, &descr->accessMethod);
		printGeneralName(&descr->accessLocation, parser);
	}
}

/* print one field */
void printCertField(
	const CSSM_FIELD 	&field,
	OidParser 			&parser,
	CSSM_BOOL			verbose)
{
	const CSSM_DATA *thisData = &field.FieldValue;
	const CSSM_OID  *thisOid = &field.FieldOid;
	
	if(cuCompareCssmData(thisOid, &CSSMOID_X509V1Version)) {
		if(verbose) {
			printf("Version            : %u\n", cuDER_ToInt(thisData));
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SerialNumber)) {
		printf("Serial Number      : "); printDataAsHex(thisData, 0);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1IssuerNameCStruct)) {
		printf("Issuer Name        :\n");
		CSSM_X509_NAME_PTR name = (CSSM_X509_NAME_PTR)thisData->Data;
		if((name == NULL) || (thisData->Length != sizeof(CSSM_X509_NAME))) {
			printf("   ***malformed CSSM_X509_NAME\n");
		}
		else {
			printName(name, parser);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SubjectNameCStruct)) {
		printf("Subject Name       :\n");
		CSSM_X509_NAME_PTR name = (CSSM_X509_NAME_PTR)thisData->Data;
		if((name == NULL) || (thisData->Length != sizeof(CSSM_X509_NAME))) {
			printf("   ***malformed CSSM_X509_NAME\n");
		}
		else {
			printName(name, parser);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1ValidityNotBefore)) {
		CSSM_X509_TIME *cssmTime = (CSSM_X509_TIME *)thisData->Data;
		if((cssmTime == NULL) || (thisData->Length != sizeof(CSSM_X509_TIME))) {
			printf("   ***malformed CSSM_X509_TIME\n");
		}
		else if(verbose) {
			printf("Not Before         : "); printString(&cssmTime->time);
			printf("                   : ");
			printTime(cssmTime);
		}
		else {
			printf("Not Before         : ");
			printTime(cssmTime);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1ValidityNotAfter)) {
		CSSM_X509_TIME *cssmTime = (CSSM_X509_TIME *)thisData->Data;
		if((cssmTime == NULL) || (thisData->Length != sizeof(CSSM_X509_TIME))) {
			printf("   ***malformed CSSM_X509_TIME\n");
		}
		else if(verbose) {
			printf("Not After          : "); printString(&cssmTime->time);
			printf("                   : ");
			printTime(cssmTime);
		}
		else {
			printf("Not After          : ");
			printTime(cssmTime);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SignatureAlgorithmTBS)) {
		if(verbose) {
			/* normally skip, it's the same as TBS sig alg */
			printf("TBS Sig Algorithm  : ");
			CSSM_X509_ALGORITHM_IDENTIFIER *algId = 
				(CSSM_X509_ALGORITHM_IDENTIFIER *)thisData->Data;
			if((algId == NULL) || 
			(thisData->Length != sizeof(CSSM_X509_ALGORITHM_IDENTIFIER))) {
				printf("   ***malformed CSSM_X509_ALGORITHM_IDENTIFIER\n");
			}
			else {
				printSigAlg(algId, parser);
			}
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SignatureAlgorithm)) {
		printf("Cert Sig Algorithm : ");
		CSSM_X509_ALGORITHM_IDENTIFIER *algId = 
			(CSSM_X509_ALGORITHM_IDENTIFIER *)thisData->Data;
		if((algId == NULL) || 
		   (thisData->Length != sizeof(CSSM_X509_ALGORITHM_IDENTIFIER))) {
			printf("   ***malformed CSSM_X509_ALGORITHM_IDENTIFIER\n");
		}
		else {
			printSigAlg(algId, parser);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1CertificateIssuerUniqueId)) {
		if(verbose) {
			printf("Issuer UniqueId    : ");
			printDerThing(BER_TAG_BIT_STRING, thisData, parser);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1CertificateSubjectUniqueId)) {
		if(verbose) {
			printf("Subject UniqueId   : ");
			printDerThing(BER_TAG_BIT_STRING, thisData, parser);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SubjectPublicKeyCStruct)) {
		CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *pubKeyInfo = 
			(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *)thisData->Data;
		printf("Pub Key Algorithm  : ");
		if((pubKeyInfo == NULL) || 
		   (thisData->Length != sizeof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO))) {
			printf("   ***malformed CSSM_X509_SUBJECT_PUBLIC_KEY_INFO\n");
		}
		else {
			printSigAlg(&pubKeyInfo->algorithm, parser);
			printf("Pub key Bytes      : Length %u bytes : ",
				(unsigned)pubKeyInfo->subjectPublicKey.Length);
			printDataAsHex(&pubKeyInfo->subjectPublicKey, 8);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_CSSMKeyStruct)) {
		CSSM_KEY_PTR cssmKey =  (CSSM_KEY_PTR)thisData->Data;
		printf("CSSM Key           :\n");
		if((cssmKey == NULL) || 
		   (thisData->Length != sizeof(CSSM_KEY))) {
			printf("   ***malformed CSSM_KEY\n");
		}
		else {
			printKeyHeader(cssmKey->KeyHeader);
			if(verbose) {
				printf("   Key Blob        : ");
				printDataAsHex(&cssmKey->KeyData, 8);
			}
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1Signature)) {
		printf("Signature          : %u bytes : ", (unsigned)thisData->Length);
		printDataAsHex(thisData, 8);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V3CertificateExtensionCStruct)) {
		if(printExtensionCommon(*thisData, parser, verbose, false)) {
			return;
		}
		CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)thisData->Data;
		printf("   Unparsed data   : "); printDataAsHex(&cssmExt->BERvalue, 8);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_KeyUsage)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printKeyUsage(*thisData);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_BasicConstraints)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printBasicConstraints(*thisData);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_ExtendedKeyUsage)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printExtKeyUsage(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_SubjectKeyIdentifier)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)thisData->Data;
		CSSM_DATA_PTR cdata = (CSSM_DATA_PTR)cssmExt->value.parsedValue;
		if((cdata == NULL) || (cdata->Data == NULL)) {
			printf("****Malformed extension (no parsedValue)\n");
		}
		else {
			printf("   Subject KeyID   : "); printDataAsHex(cdata, 8);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_AuthorityKeyIdentifier)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printAuthorityKeyId(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_SubjectAltName)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printSubjectIssuerAltName(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_IssuerAltName)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printSubjectIssuerAltName(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_CertificatePolicies)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printCertPolicies(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_NetscapeCertType)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printNetscapeCertType(*thisData);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_CrlDistributionPoints)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printDistributionPoints(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_AuthorityInfoAccess)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printAuthorityInfoAccess(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_SubjectInfoAccess)) {
		if(printExtensionCommon(*thisData, parser, verbose)) {
			return;
		}
		printAuthorityInfoAccess(*thisData, parser);
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1IssuerName)) {
		if(verbose) {
			printf("Normalized Issuer  : ");
			printDataAsHex(thisData, 8);
		}
	}
	else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SubjectName)) {
		if(verbose) {
			printf("Normalized Subject : ");
			printDataAsHex(thisData, 8);
		}
	}
	else {
		printf("Other field:       : "); printOid(parser, thisOid);
	}
}

void printCrlExten(
	const CSSM_X509_EXTENSION *exten,
	CSSM_BOOL			verbose,
	OidParser 			&parser)
{
	const CSSM_OID *oid = &exten->extnId;
	const void *thisData = exten->value.parsedValue;
	
	if(exten->format == CSSM_X509_DATAFORMAT_ENCODED) {
		if(printCdsaExtensionCommon(exten, parser, false, verbose)) {
			return;
		}
		printf("   Unparsed data   : "); printDataAsHex(&exten->BERvalue, 8);
	}
	else if(exten->format != CSSM_X509_DATAFORMAT_PARSED) {
		printf("***Badly formatted CSSM_X509_EXTENSION\n");
		return;
	}
	else if(cuCompareCssmData(oid, &CSSMOID_AuthorityKeyIdentifier)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose)) {
			return;
		}
		printCssmAuthorityKeyId((CE_AuthorityKeyID *)thisData, parser);
	} 
	else if(cuCompareCssmData(oid, &CSSMOID_IssuerAltName)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose)) {
			return;
		}
		printGeneralNames((CE_GeneralNames *)thisData, parser);
	}
	else if(cuCompareCssmData(oid, &CSSMOID_CrlNumber)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose)) {
			return;
		}
		printf("   CRL Number      : %u\n", *((unsigned *)thisData));
	}
	else if(cuCompareCssmData(oid, &CSSMOID_DeltaCrlIndicator)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose)) {
			return;
		}
		printf("   Delta CRL Base  : %u\n", *((unsigned *)thisData));
	}
	else if(cuCompareCssmData(oid, &CSSMOID_IssuingDistributionPoint)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose)) {
			return;
		}
		printIssuingDistributionPoint((CE_IssuingDistributionPoint *)thisData,
			parser);
	}
	else {
		/* should never happen - we're out of sync with the CL */
		printf("UNKNOWN EXTENSION  : "); printOid(parser, oid);
	}
}

void printCrlEntryExten(
	const CSSM_X509_EXTENSION *exten,
	CSSM_BOOL			verbose,
	OidParser 			&parser)
{
	const CSSM_OID *oid = &exten->extnId;
	const void *thisData = exten->value.parsedValue;
	
	if(exten->format == CSSM_X509_DATAFORMAT_ENCODED) {
		if(printCdsaExtensionCommon(exten, parser, false, verbose, true)) {
			return;
		}
		printf("      Unparsed data: "); printDataAsHex(&exten->BERvalue, 8);
	}
	else if(exten->format != CSSM_X509_DATAFORMAT_PARSED) {
		printf("***Badly formatted CSSM_X509_EXTENSION\n");
		return;
	}
	else if(cuCompareCssmData(oid, &CSSMOID_CrlReason)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose, true)) {
			return;
		}
		CE_CrlReason *cr = (CE_CrlReason *)thisData;
		char *reason = "UNKNOWN";
		switch(*cr) {
			case CE_CR_Unspecified: 
				reason = "CE_CR_Unspecified"; break;
			case CE_CR_KeyCompromise: 
				reason = "CE_CR_KeyCompromise"; break;
			case CE_CR_CACompromise: 
				reason = "CE_CR_CACompromise"; break;
			case CE_CR_AffiliationChanged: 
				reason = "CE_CR_AffiliationChanged"; break;
			case CE_CR_Superseded: 
				reason = "CE_CR_Superseded"; break;
			case CE_CR_CessationOfOperation: 
				reason = "CE_CR_CessationOfOperation"; break;
			case CE_CR_CertificateHold: 
				reason = "CE_CR_CertificateHold"; break;
			case CE_CR_RemoveFromCRL:
				reason = "CE_CR_RemoveFromCRL"; break;
			default:
				break;
		}
		printf("      CRL Reason   : %s\n", reason);
	}
	else if(cuCompareCssmData(oid, &CSSMOID_HoldInstructionCode)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose, true)) {
			return;
		}
		printf("      Hold Instr   : ");
		printOid(parser, (CSSM_OID_PTR)thisData);
	}
	else if(cuCompareCssmData(oid, &CSSMOID_InvalidityDate)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose, true)) {
			return;
		}
		printf("      Invalid Date : ");
		printTimeStr((CSSM_DATA_PTR)thisData);
	}
	else if(cuCompareCssmData(oid, &CSSMOID_CertIssuer)) {
		if(printCdsaExtensionCommon(exten, parser, true, verbose, true)) {
			return;
		}
		printGeneralNames((CE_GeneralNames *)thisData, parser);
	}
	else {
		/* should never happen - we're out of sync with the CL */
		printf("UNKNOWN EXTENSION  : "); printOid(parser, oid);
	}
}

void printCrlFields(
	const CSSM_X509_SIGNED_CRL *signedCrl,
	CSSM_BOOL					verbose,
	OidParser 					&parser)
{
	unsigned i;
	const CSSM_X509_TBS_CERTLIST *tbsCrl = &signedCrl->tbsCertList;
	
	if(tbsCrl->version.Data) {
		printf("Version            : %d\n", cuDER_ToInt(&tbsCrl->version));
	}
	
	printf("TBS Sig Algorithm  : ");
	const CSSM_X509_ALGORITHM_IDENTIFIER *algId = &tbsCrl->signature;
	printSigAlg(algId, parser);
	
	printf("Issuer Name        :\n");
	printName(&tbsCrl->issuer, parser);

	printf("This Update        : ");
	printTime(&tbsCrl->thisUpdate);
	printf("Next Update        : ");
	if(tbsCrl->nextUpdate.time.Data) {
		printTime(&tbsCrl->nextUpdate);
	}
	else {
		printf("<not present>\n");
	}
	
	CSSM_X509_REVOKED_CERT_LIST_PTR certList = tbsCrl->revokedCertificates;
	if(certList) {
		if(verbose) {
			printf("Num Revoked Certs  : %d\n", 
				(int)certList->numberOfRevokedCertEntries);
			for(i=0; i<certList->numberOfRevokedCertEntries; i++) {
				CSSM_X509_REVOKED_CERT_ENTRY_PTR entry;
				entry = &certList->revokedCertEntry[i];
				printf("Revoked Cert %d     :\n", (int)i);
				printf("   Serial number   : ");
				printDataAsHex(&entry->certificateSerialNumber, 0);
				printf("   Revocation time : ");
				printTime(&entry->revocationDate);
				const CSSM_X509_EXTENSIONS *cssmExtens = &entry->extensions;
				uint32 numExtens = cssmExtens->numberOfExtensions;
				if(numExtens == 0) {
					continue;
				}
				printf("   Num Extensions  : %u\n", (unsigned)numExtens);
				for(unsigned dex=0; dex<numExtens; dex++) {
					printCrlEntryExten(&cssmExtens->extensions[dex], verbose, 
						parser);
				}
			}
		}
		else {
			printf("Num Revoked Certs  : %d (use verbose option to see)\n", 
				(int)certList->numberOfRevokedCertEntries);
		}
	}

	const CSSM_X509_EXTENSIONS *crlExtens = &tbsCrl->extensions;
	if(crlExtens->numberOfExtensions) {
		printf("Num CRL Extensions : %d\n",
			(int)crlExtens->numberOfExtensions);
		for(i=0; i<crlExtens->numberOfExtensions; i++) {
			printCrlExten(&crlExtens->extensions[i], verbose, parser);
		}
	}
	
	const CSSM_X509_SIGNATURE *sig = &signedCrl->signature;
	if(sig->encrypted.Data) {
		printf("Signature          : %u bytes : ", (unsigned)sig->encrypted.Length);
		printDataAsHex(&sig->encrypted, 8);
	}
}


/* connect to CSSM/CL lazily, once */
static CSSM_CL_HANDLE clHand = 0;

int printCert(
	const unsigned char	*certData,
	unsigned		certLen,
	CSSM_BOOL		verbose)
{
	CSSM_FIELD_PTR				fieldPtr;		// mallocd by CL
	uint32						i;
	uint32						numFields;
	OidParser 					parser;
	CSSM_DATA					cert;
	
	if(clHand == 0) {
		clHand = cuClStartup();
		if(clHand == 0) {
			printf("***Error connecting to CSSM cert module; aborting cert "
				"display\n");
			return 0;
		}
	}
	cert.Data = (uint8 *)certData;
	cert.Length = certLen;
	
	CSSM_RETURN crtn = CSSM_CL_CertGetAllFields(clHand,
		&cert,
		&numFields,
		&fieldPtr);
	if(crtn) {
		cuPrintError("CSSM_CL_CertGetAllFields", crtn);
		return crtn;
	}

	for(i=0; i<numFields; i++) {
		printCertField(fieldPtr[i], parser, verbose);
	}	

	crtn = CSSM_CL_FreeFields(clHand, numFields, &fieldPtr);
	if(crtn) {
		cuPrintError("CSSM_CL_FreeFields", crtn);
		return crtn;
	}
	return 0;
}

/* parse CRL */
/* This one's easier, we just get one field - the whole parsed CRL */
int printCrl(
	const  unsigned char 	*crlData,
	unsigned				crlLen,
	CSSM_BOOL				verbose)
{
	CSSM_DATA_PTR				value;		// mallocd by CL
	uint32						numFields;
	OidParser 					parser;
	CSSM_DATA					crl;
	CSSM_HANDLE					result;
	
	if(clHand == 0) {
		clHand = cuClStartup();
		if(clHand == 0) {
			printf("***Error connecting to CSSM cert module; aborting CRL"
				"display\n");
			return 0;
		}
	}
	crl.Data = (uint8 *)crlData;
	crl.Length = crlLen;
	
	CSSM_RETURN crtn = CSSM_CL_CrlGetFirstFieldValue(clHand,
		&crl,
		&CSSMOID_X509V2CRLSignedCrlCStruct,
		&result,
		&numFields,
		&value);
	if(crtn) {
		cuPrintError("CSSM_CL_CrlGetFirstFieldValue", crtn);
		return crtn;
	}
	if(numFields != 1) {
		printf("***CSSM_CL_CrlGetFirstFieldValue: numFields error\n");
		printf("   expected 1, got %d\n", (int)numFields);
		return 1;
	}
	crtn = CSSM_CL_CrlAbortQuery(clHand, result);
	if(crtn) {
		cuPrintError("CSSM_CL_CertAbortQuery", crtn);
		return crtn;
	}
	
	if(value == NULL) {
		printf("***CSSM_CL_CrlGetFirstFieldValue: value error (1)\n");
		return 1;
	}
	if((value->Data == NULL) || 
	   (value->Length != sizeof(CSSM_X509_SIGNED_CRL))) {
		printf("***CSSM_CL_CrlGetFirstFieldValue: value error (2)\n");
		return 1;
	}
	const CSSM_X509_SIGNED_CRL *signedCrl = 
		(const CSSM_X509_SIGNED_CRL *)value->Data;
	printCrlFields(signedCrl, verbose, parser);

	crtn = CSSM_CL_FreeFieldValue(clHand, 
		&CSSMOID_X509V2CRLSignedCrlCStruct, 
		value);
	if(crtn) {
		cuPrintError("CSSM_CL_FreeFieldValue", crtn);
		return crtn;
	}
	return 0;
}


void printCertShutdown()
{
	if(clHand != 0) {
		CSSM_ModuleDetach(clHand);
	}
}
