DecodedCert.cpp   [plain text]


/*
 * Copyright (c) 2000-2001 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.
 */


/*
 * DecodedCert.cpp - object representing a decoded cert, in NSS
 * format, with extensions parsed and decoded (still in NSS format).
 *
 * Created 9/1/2000 by Doug Mitchell. 
 * Copyright (c) 2000 by Apple Computer. 
 */

#include "DecodedCert.h"
#include "clNssUtils.h"
#include "cldebugging.h"
#include "AppleX509CLSession.h"
#include "CSPAttacher.h"
#include <Security/cssmapple.h>
#include <Security/oidscert.h>

DecodedCert::DecodedCert(
	AppleX509CLSession	&session)
	: DecodedItem(session)
{
	memset(&mCert, 0, sizeof(mCert));
}

/* one-shot constructor, decoding from DER-encoded data */
DecodedCert::DecodedCert(
	AppleX509CLSession	&session,
	const CssmData 	&encodedCert)
	: DecodedItem(session)
{
	memset(&mCert, 0, sizeof(mCert));
	PRErrorCode prtn = mCoder.decode(encodedCert.data(), encodedCert.length(), 
		kSecAsn1SignedCertTemplate, &mCert);
	if(prtn) {
		CssmError::throwMe(CSSMERR_CL_UNKNOWN_FORMAT);
	}
	mDecodedExtensions.decodeFromNss(mCert.tbs.extensions);
	mState = IS_DecodedAll;
}
		
DecodedCert::~DecodedCert()
{
}
	
/* decode TBSCert and its extensions */
void DecodedCert::decodeTbs(
	const CssmData	&encodedTbs)
{
	assert(mState == IS_Empty);
	
	memset(&mCert, 0, sizeof(mCert));
	PRErrorCode prtn = mCoder.decode(encodedTbs.data(), encodedTbs.length(), 
		kSecAsn1TBSCertificateTemplate, &mCert.tbs);
	if(prtn) {
		CssmError::throwMe(CSSMERR_CL_UNKNOWN_FORMAT);
	}
	mDecodedExtensions.decodeFromNss(mCert.tbs.extensions);
	mState = IS_DecodedTBS;
}

void DecodedCert::encodeExtensions()
{
	NSS_TBSCertificate &tbs = mCert.tbs;
	assert(mState == IS_Building);
	assert(tbs.extensions == NULL);

	if(mDecodedExtensions.numExtensions() == 0) {
		/* no extensions, no error */
		return;
	}
	mDecodedExtensions.encodeToNss(tbs.extensions);
}

/*
 * FIXME : how to determine max encoding size at run time!?
 */
#define MAX_TEMPLATE_SIZE	(8 * 1024)

/* encode TBS component; only called from CertCreateTemplate */
void DecodedCert::encodeTbs(
	CssmOwnedData	&encodedTbs)
{
	encodeExtensions();
	assert(mState == IS_Building);

	/* enforce required fields - could go deeper, maybe we should */
	NSS_TBSCertificate &tbs = mCert.tbs;
	if((tbs.signature.algorithm.Data == NULL) ||
	   (tbs.issuer.rdns == NULL) ||
	   (tbs.subject.rdns == NULL) ||
	   (tbs.subjectPublicKeyInfo.subjectPublicKey.Data == NULL)) {
		clErrorLog("DecodedCert::encodeTbs: incomplete TBS");
		/* an odd, undocumented error return */
		CssmError::throwMe(CSSMERR_CL_NO_FIELD_VALUES);
	}
	
	PRErrorCode prtn;
	prtn = SecNssEncodeItemOdata(&tbs, kSecAsn1TBSCertificateTemplate,
		encodedTbs);
	if(prtn) {
		CssmError::throwMe(CSSMERR_CL_MEMORY_ERROR);
	}
}

/*
 * Cook up CSSM_KEYUSE, gleaning as much as possible from
 * (optional) extensions. If no applicable extensions available,
 * we'll just return CSSM_KEYUSE_ANY.
 *
 * Note that the standard KeyUsage flags involving 'signing' translate
 * to verify since we're only dealing with public keys. 
 */
CSSM_KEYUSE DecodedCert::inferKeyUsage() const
{
	CSSM_KEYUSE keyUse = 0;
	const DecodedExten *decodedExten;
	uint32 numFields;
	
	/* Basic KeyUsage */
	decodedExten = DecodedItem::findDecodedExt(CSSMOID_KeyUsage, false, 
		0, numFields);
	if(decodedExten) {
		CSSM_DATA *ku = (CSSM_DATA *)decodedExten->nssObj();
		assert(ku != NULL);
		CE_KeyUsage kuse = clBitStringToKeyUsage(*ku);
		if(kuse & CE_KU_DigitalSignature) {
			keyUse |= CSSM_KEYUSE_VERIFY;
		}
		if(kuse & CE_KU_NonRepudiation) {
			keyUse |= CSSM_KEYUSE_VERIFY;
		}
		if(kuse & CE_KU_KeyEncipherment) {
			keyUse |= CSSM_KEYUSE_WRAP;
		}
		if(kuse & CE_KU_KeyAgreement) {
			keyUse |= CSSM_KEYUSE_DERIVE;
		}
		if(kuse & CE_KU_KeyCertSign) {
			keyUse |= CSSM_KEYUSE_VERIFY;
		}
		if(kuse & CE_KU_CRLSign) {
			keyUse |= CSSM_KEYUSE_VERIFY;
		}
		if(kuse & CE_KU_DataEncipherment) {
			keyUse |= CSSM_KEYUSE_ENCRYPT;
		}
	}
	
	/* Extended key usage */
	decodedExten = DecodedItem::findDecodedExt(CSSMOID_ExtendedKeyUsage, 
			false, 0, numFields);
	if(decodedExten) {
		NSS_ExtKeyUsage *euse = (NSS_ExtKeyUsage *)decodedExten->nssObj();
		assert(euse != NULL);
		unsigned numUses = clNssArraySize((const void **)euse->purposes);
		for(unsigned dex=0; dex<numUses; dex++) {
		const CSSM_OID *thisUse = euse->purposes[dex];
			if(clCompareCssmData(thisUse, &CSSMOID_ExtendedKeyUsageAny)) {
				/* we're done */
				keyUse = CSSM_KEYUSE_ANY;	
				break;
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_ServerAuth)) {
				keyUse |= (CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DERIVE);	
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_ClientAuth)) {
				keyUse |= (CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DERIVE);	
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_ExtendedUseCodeSigning)) {
				keyUse |= CSSM_KEYUSE_VERIFY;	
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_EmailProtection)) {
				keyUse |= 
					(CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP | CSSM_KEYUSE_DERIVE);
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_TimeStamping)) {
				keyUse |= CSSM_KEYUSE_VERIFY;	
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_OCSPSigning)) {
				keyUse |= CSSM_KEYUSE_VERIFY;	
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_APPLE_EKU_SYSTEM_IDENTITY)) {
				/* system identity - fairly liberal: CMS as well as SSL */
				keyUse |= 
					(CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP | CSSM_KEYUSE_ENCRYPT);
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_KERBv5_PKINIT_KP_CLIENT_AUTH)) {
				/* 
				 * Kerberos PKINIT client: 
				 * -- KDC verifies client signature in a CMS msg in AS-REQ
				 * -- KDC encrypts for client in a CMS msg in AS-REP
				 */
				keyUse |= (CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP);
			}
			else if(clCompareCssmData(thisUse, &CSSMOID_KERBv5_PKINIT_KP_KDC)) {
				/* 
				 * Kerberos PKINIT server: 
				 * -- client verifies KDC signature in a CMS msg in AS-REP
				 */
				keyUse |= CSSM_KEYUSE_VERIFY;
			}
		}
	}
	
	/* NetscapeCertType */
	decodedExten = DecodedItem::findDecodedExt(CSSMOID_NetscapeCertType, 
			false, 0, numFields);
	if(decodedExten) {
		/* nssObj() is a CSSM_DATA ptr, whose Data points to the bits we want */
		CSSM_DATA *nctData = (CSSM_DATA *)decodedExten->nssObj();
		if((nctData != NULL) && (nctData->Length > 0)) {
			CE_NetscapeCertType nct = ((uint16)nctData->Data[0]) << 8;
			if(nctData->Length > 1) {
				nct |= nctData->Data[1];
			}
		
			/* All this usage bits imply signature verify capability */
			if(nct & (CE_NCT_SSL_Client | CE_NCT_SSL_Server | CE_NCT_SMIME | CE_NCT_ObjSign |
					  CE_NCT_SSL_CA | CE_NCT_SMIME_CA | CE_NCT_ObjSignCA)) {
				keyUse |= CSSM_KEYUSE_VERIFY;
			}
		}
	}
	if(keyUse == 0) {
		/* Nothing found; take the default. */
		keyUse = CSSM_KEYUSE_ANY;
	}
	return keyUse;
}

/*
 * Obtain a CSSM_KEY from a decoded cert, inferring as much as we can
 * from required fields (subjectPublicKeyInfo) and extensions (for 
 * KeyUse).
 */
CSSM_KEY_PTR DecodedCert::extractCSSMKey(
	Allocator		&alloc)	const
{
	const CSSM_X509_SUBJECT_PUBLIC_KEY_INFO &keyInfo = 
		mCert.tbs.subjectPublicKeyInfo;
	return CL_extractCSSMKeyNSS(keyInfo, alloc, this);
}