void CL_certCrlDecodeComponents(
	const CssmData 	&signedItem,		// DER-encoded cert or CRL
	CssmOwnedData	&tbsBlob,			// still DER-encoded
	CssmOwnedData	&algId,				// ditto
	CssmOwnedData	&rawSig)			// raw bits (not an encoded AsnBits)
{
	/* BER-decode into temp memory */
	NSS_SignedCertOrCRL nssObj;
	SecNssCoder coder;
	PRErrorCode prtn;
	
	memset(&nssObj, 0, sizeof(nssObj));
	prtn = coder.decode(signedItem.data(), signedItem.length(),
		kSecAsn1SignedCertOrCRLTemplate, &nssObj);
	if(prtn) {
		CssmError::throwMe(CSSMERR_CL_UNKNOWN_FORMAT);
	}
	
	/* tbsBlob and algId are raw ASN_ANY including tags, which we pass 
	 * back to caller intact */
	tbsBlob.copy(nssObj.tbsBlob.Data, nssObj.tbsBlob.Length);
	algId.copy(nssObj.signatureAlgorithm.Data, 
		nssObj.signatureAlgorithm.Length);
		
	/* signature is a bit string which we do in fact decode */
	rawSig.copy(nssObj.signature.Data,
		(nssObj.signature.Length + 7) / 8);
}
static bool getFieldIssuerStd(
	DecodedItem		 	&item,
	unsigned			index,			// which occurrence (0 = first)
	uint32				&numFields,		// RETURNED
	CssmOwnedData		&fieldValue)	// RETURNED
{
	if(index != 0) {
		return false;
	}
	const DecodedCert &cert = dynamic_cast<const DecodedCert &>(item);
	fieldValue.copy(cert.mCert.tbs.derIssuer);
	numFields = 1;
	return true;
}
/***
 *** Signature
 *** Format = raw bytes
 *** read-only
 ***/
static bool getField_Signature (
	DecodedItem		 	&item,
	unsigned			index,			// which occurrence (0 = first)
	uint32				&numFields,		// RETURNED
	CssmOwnedData		&fieldValue)	// RETURNED
{
	const DecodedCert &cert = dynamic_cast<const DecodedCert &>(item);
	const CSSM_DATA &sigBits = cert.mCert.signature;
	if(!tbsGetCheck(sigBits.Data, index)) {
		return false;
	}
	fieldValue.copy(sigBits.Data, (sigBits.Length + 7) / 8);
	numFields = 1;
	return true;
}
/***
 *** Serial Number
 *** Format = DER-encoded int, variable length
 ***/
static bool getField_SerialNumber (
	DecodedItem		 	&item,
	unsigned			index,			// which occurrence (0 = first)
	uint32				&numFields,		// RETURNED
	CssmOwnedData		&fieldValue)	// RETURNED
{
	const DecodedCert &cert = dynamic_cast<const DecodedCert &>(item);
	const CSSM_DATA &sn = cert.mCert.tbs.serialNumber;
	if(!tbsGetCheck(sn.Data, index)) {
		return false;
	}
	fieldValue.copy(sn.Data, sn.Length);
	numFields = 1;
	return true;
}
/***
 *** Version
 *** Format = DER-encoded int (max of four bytes in this case)
 ***/
static bool getField_Version (
	DecodedItem		 	&item,
	unsigned			index,			// which occurrence (0 = first)
	uint32				&numFields,		// RETURNED
	CssmOwnedData		&fieldValue)	// RETURNED
{
	const DecodedCert &cert = dynamic_cast<const DecodedCert &>(item);
	const CSSM_DATA &vers = cert.mCert.tbs.version;
	if(!tbsGetCheck(vers.Data, index)) {
		/* not present, optional */
		return false;
	}
	fieldValue.copy(vers.Data, vers.Length);
	numFields = 1;
	return true;
}
static bool getField_IssuerUniqueId (
	DecodedItem		 	&item,
	unsigned			index,			// which occurrence (0 = first)
	uint32				&numFields,		// RETURNED
	CssmOwnedData		&fieldValue)	// RETURNED
{
	const DecodedCert &cert = dynamic_cast<const DecodedCert &>(item);
	const CSSM_DATA &srcBits = cert.mCert.tbs.issuerID;
	if(!tbsGetCheck(srcBits.Data, index)) {
		return false;
	}

	/* That CSSM_DATA is a decoded BITSTRING; its length is in bits */
	CSSM_DATA tmp = srcBits;
	tmp.Length = (tmp.Length + 7) / 8;
	fieldValue.copy(tmp.Data, tmp.Length);
	numFields = 1;
	return true;
}
/* 
 * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName
 * attribute.
 */
void impExpOpensshInferDescData(
	SecKeyRef keyRef,
	CssmOwnedData &descData)
{
	OSStatus ortn;
	SecKeychainAttributeInfo attrInfo;
	SecKeychainAttrType	attrType = kSecKeyPrintName;
	attrInfo.count = 1;
	attrInfo.tag = &attrType;
	attrInfo.format = NULL;	
	SecKeychainAttributeList *attrList = NULL;
	
	ortn = SecKeychainItemCopyAttributesAndData(
		(SecKeychainItemRef)keyRef, 
		&attrInfo,
		NULL,			// itemClass
		&attrList, 
		NULL,			// don't need the data
		NULL);
	if(ortn) {
		SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", (unsigned long)ortn);
		return;
	}
	/* subsequent errors to errOut: */
	SecKeychainAttribute *attr = attrList->attr;
		
	/*
	 * On a previous import, we would have set this to something like 
	 * "OpenSSHv2 Public Key: comment".
	 * We want to strip off everything up to the actual comment.
	 */
	unsigned toStrip = 0;	
	
	/* min length of attribute value for this code to be meaningful */
	unsigned len = strlen(SSHv2_PUB_KEY_NAME) + 1;	
	char *printNameStr = NULL;
	if(len < attr->length) {
		printNameStr = (char *)malloc(attr->length + 1);
		memmove(printNameStr, attr->data, attr->length);
		printNameStr[attr->length] = '\0';
		if(strstr(printNameStr, SSHv2_PUB_KEY_NAME) == printNameStr) {
			toStrip = strlen(SSHv2_PUB_KEY_NAME);
		}
		else if(strstr(printNameStr, SSHv1_PUB_KEY_NAME) == printNameStr) {
			toStrip = strlen(SSHv1_PUB_KEY_NAME);
		}
		else if(strstr(printNameStr, SSHv1_PRIV_KEY_NAME) == printNameStr) {
			toStrip = strlen(SSHv1_PRIV_KEY_NAME);
		}
		if(toStrip) {
			/* only strip if we have ": " after toStrip bytes */
			if((printNameStr[toStrip] == ':') && (printNameStr[toStrip+1] == ' ')) {
				toStrip += 2;
			}
		}
	}
	if(printNameStr) {
		free(printNameStr);
	}
	len = attr->length;

	unsigned char *attrVal;

	if(len < toStrip) {
		SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
		goto errOut;
	}
	if(len > toStrip) {
		/* Normal case of stripping off leading header */
		len -= toStrip;
	}
	else {
		/* 
		 * If equal, then the attr value *is* "OpenSSHv2 Public Key: " with 
		 * no comment. Not sure how that could happen, but let's be careful.
		 */
		toStrip = 0;
	}
	
	attrVal = ((unsigned char *)attr->data) + toStrip;
	descData.copy(attrVal, len);
errOut:
	SecKeychainItemFreeAttributesAndData(attrList, NULL);
	return;
}