]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_cdsa_utils/lib/cuPrintCert.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_cdsa_utils / lib / cuPrintCert.cpp
diff --git a/Security/libsecurity_cdsa_utils/lib/cuPrintCert.cpp b/Security/libsecurity_cdsa_utils/lib/cuPrintCert.cpp
new file mode 100644 (file)
index 0000000..5f0281b
--- /dev/null
@@ -0,0 +1,1504 @@
+/*
+ * Copyright (c) 2002,2011-2012,2014 Apple 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/oidsalg.h>
+#include <Security/cssmapple.h>
+#include <string.h>
+#include "cuPrintCert.h"
+#include "cuOidParser.h"
+#include "cuTimeStr.h"
+#include <Security/certextensions.h>
+#include <Security/SecAsn1Coder.h>
+#include <Security/keyTemplates.h>
+
+static const 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, (unsigned int)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 = (uint32)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 const 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, (unsigned int)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(
+       const char                      *blobType,
+       const 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", (uint32)thing->Length, thing);
+                       return;
+               case BER_TAG_BIT_STRING:
+                       printBlobBytes("Bit string", "bits", (uint32)(thing->Length + 7) / 8, thing);
+                       return;
+               case BER_TAG_SEQUENCE:
+                       printBlobBytes("Sequence", "bytes", (uint32)thing->Length, thing);
+                       return;
+               case BER_TAG_SET:
+                       printBlobBytes("Set", "bytes", (uint32)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;
+                       
+       }
+}
+
+/* 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;
+       }
+}      
+
+/* 
+ * Following a CSSMOID_ECDSA_WithSpecified algorithm is another encoded
+ * CSSM_X509_ALGORITHM_IDENTIFIER containing the digest algorithm OID. 
+ * Decode and print the OID.
+ */
+static void printECDSA_SigAlgParams(
+       const CSSM_DATA *params,
+       OidParser &parser)
+{
+       SecAsn1CoderRef coder = NULL;
+       if(SecAsn1CoderCreate(&coder)) {
+               printf("***Error in SecAsn1CoderCreate()\n");
+               return;
+       }
+       CSSM_X509_ALGORITHM_IDENTIFIER algParams;
+       memset(&algParams, 0, sizeof(algParams));
+       if(SecAsn1DecodeData(coder, params, kSecAsn1AlgorithmIDTemplate,
+                       &algParams)) {
+               printf("***Error decoding CSSM_X509_ALGORITHM_IDENTIFIER\n");
+               goto errOut;
+       }
+       printOid(parser, &algParams.algorithm);
+errOut:
+       SecAsn1CoderRelease(coder);
+}
+
+static void printSigAlg(
+       const CSSM_X509_ALGORITHM_IDENTIFIER *sigAlg,
+       OidParser                                                       &parser)
+{
+       printOid(parser, &sigAlg->algorithm);
+       if(sigAlg->parameters.Data != NULL) {
+               printf("   alg params      : ");
+               if(compareOids(&sigAlg->algorithm, &CSSMOID_ecPublicKey) &&
+                  (sigAlg->parameters.Data[0] == BER_TAG_OID) &&
+                  (sigAlg->parameters.Length > 2)) {
+                       /* 
+                        * An OID accompanying an ECDSA public key. The OID is an ECDSA curve. 
+                        * Do a quickie DER-decode of the OID - it's here in encoded form
+                        * because this field is an ASN_ANY - and print the resulting OID.
+                        */
+                       CSSM_OID curveOid = {sigAlg->parameters.Length-2, sigAlg->parameters.Data+2};
+                       printOid(parser, &curveOid);
+               }
+               else if(compareOids(&sigAlg->algorithm, &CSSMOID_ECDSA_WithSpecified)) {
+                       /* 
+                        * The accompanying params specify the digest algorithm.
+                        */ 
+                       printECDSA_SigAlgParams(&sigAlg->parameters, parser);
+               }
+               else {
+                       /* All others - ASN_ANY - punt */
+                       printDataAsHex(&sigAlg->parameters, 8);
+               }
+       }
+}
+
+static void printRdn(
+       const CSSM_X509_RDN                     *rdnp,
+       OidParser                                       &parser)
+{
+       CSSM_X509_TYPE_VALUE_PAIR       *ptvp;
+       unsigned                                        pairDex;
+       const 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 if(compareOids(&ptvp->type, &CSSMOID_Description)) {
+                       fieldName = "Description   ";      
+               }
+               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;
+               case CSSM_ALGID_ECDSA:
+                       printf("ECDSA\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", (uint32)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", 
+                                       (uint32)name->name.Length, &name->name);
+                       }
+                       break;
+               case GNT_EdiPartyName:
+                       /* sequence EDIPartyName */
+                       printf("   EdiPartyName    : ");
+                       printBlobBytes("Sequence", "bytes", (uint32)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 0
+       /* this prevents printing pre-encoded extensions in clxutils/extenTest */
+       if((cssmExt->BERvalue.Data == NULL) || 
+          (cssmExt->value.parsedValue == NULL)) {  /* actually, one of three variants */
+               printf("***Malformed CSSM_X509_EXTENSION (1)\n");
+               return 1;
+       }
+       #endif  
+       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", (unsigned long)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);
+       }
+}
+
+static void printQualCertStatements(
+       const CSSM_DATA         &value,
+       OidParser                       &parser)
+{
+       CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)value.Data;
+       CE_QC_Statements        *qcss = (CE_QC_Statements *)cssmExt->value.parsedValue;
+
+       printf("   numQCStatements : %lu\n", (unsigned long)qcss->numQCStatements);
+       for(unsigned dex=0; dex<qcss->numQCStatements; dex++) {
+               CE_QC_Statement *qcs = &qcss->qcStatements[dex];
+
+               printf("   statement %u     : \n", dex);
+               printf("   statementId     : ");
+               printOid(parser, &qcs->statementId);
+               if(qcs->semanticsInfo) {
+                       printf("   semanticsInfo   :\n");
+                       CE_SemanticsInformation *si = qcs->semanticsInfo;
+                       if(si->semanticsIdentifier) {
+                               printf("   semanticsId     : ");
+                               printOid(parser, si->semanticsIdentifier);
+                       }
+                       if(si->nameRegistrationAuthorities) {
+                               printf("   nameRegAuth     :\n");
+                               printGeneralNames(si->nameRegistrationAuthorities, parser);
+                       }
+               }
+               if(qcs->otherInfo) {
+                       printf("   otherInfo       : "); printDataAsHex(qcs->otherInfo, 8);
+               }
+       }
+}
+
+/* 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_QC_Statements)) {
+               if(printExtensionCommon(*thisData, parser, verbose)) {
+                       return;
+               }
+               printQualCertStatements(*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 if(cuCompareCssmData(thisOid, &CSSMOID_X509V1IssuerNameStd)) {
+               if(verbose) {
+                       printf("DER-encoded issuer : ");
+                       printDataAsHex(thisData, 8);
+               }
+       }
+       else if(cuCompareCssmData(thisOid, &CSSMOID_X509V1SubjectNameStd)) {
+               if(verbose) {
+                       printf("DER-encoded subject: ");
+                       printDataAsHex(thisData, 8);
+               }
+       }
+       else {
+               printf("Other field:       : "); printOid(parser, thisOid);
+       }
+}
+
+static
+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);
+       }
+}
+
+
+static
+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;
+               const 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);
+       }
+}
+
+static
+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);
+       }
+}