AppleCSP.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.
 */


//
// AppleCSP.cpp - top-level plugin and session implementation 
//
#include "AppleCSP.h"
#include "AppleCSPSession.h"
#include "AppleCSPUtils.h"
#include <stdio.h> 
#include "cspdebugging.h" 
#include <security_cdsa_plugin/CSPsession.h>
#include <security_utilities/alloc.h>
#ifdef	BSAFE_CSP_ENABLE
#include "bsafecsp.h"
#include "bsafecspi.h"
#endif
#ifdef	CRYPTKIT_CSP_ENABLE
#include "cryptkitcsp.h"
#include "FEEKeys.h"
#endif
#include <miscAlgFactory.h> 
#ifdef	ASC_CSP_ENABLE
#include "ascFactory.h"
#endif
#include <RSA_DSA_csp.h>
#include <RSA_DSA_keys.h>
#include <DH_csp.h>
#include <DH_keys.h>

#include "YarrowConnection.h"

//
// Make and break the plugin object
//
AppleCSPPlugin::AppleCSPPlugin() :
	normAllocator(Allocator::standard(Allocator::normal)),
	privAllocator(Allocator::standard(Allocator::sensitive)),
	#ifdef	BSAFE_CSP_ENABLE
	bSafe4Factory(new BSafeFactory(&normAllocator, &privAllocator)),
	#endif
	#ifdef	CRYPTKIT_CSP_ENABLE
	cryptKitFactory(new CryptKitFactory(&normAllocator, &privAllocator)),
	#endif
	miscAlgFactory(new MiscAlgFactory(&normAllocator, &privAllocator)),
	#ifdef	ASC_CSP_ENABLE
	ascAlgFactory(new AscAlgFactory(&normAllocator, &privAllocator)),
	#endif
	rsaDsaAlgFactory(new RSA_DSA_Factory(&normAllocator, &privAllocator)),
	dhAlgFactory(new DH_Factory(&normAllocator, &privAllocator))
{
	// misc. once-per-address-space cruft...
}

AppleCSPPlugin::~AppleCSPPlugin()
{
	#ifdef	BSAFE_CSP_ENABLE
	delete bSafe4Factory;
	#endif
	#ifdef	CRYPTKIT_CSP_ENABLE
	delete cryptKitFactory;
	#endif
	delete miscAlgFactory;
	#ifdef	ASC_CSP_ENABLE
	delete ascAlgFactory;
	#endif
	delete rsaDsaAlgFactory;
	delete dhAlgFactory;
}


//
// Create a new plugin session, our way
//
PluginSession *AppleCSPPlugin::makeSession(
	CSSM_MODULE_HANDLE handle,
    const CSSM_VERSION &version,
    uint32 subserviceId,
    CSSM_SERVICE_TYPE subserviceType,
    CSSM_ATTACH_FLAGS attachFlags,
    const CSSM_UPCALLS &upcalls)
{
    switch (subserviceType) {
        case CSSM_SERVICE_CSP:
            return new AppleCSPSession(handle,
				*this,
				version,
				subserviceId,
				subserviceType,
				attachFlags,
				upcalls);
        default:
            CssmError::throwMe(CSSMERR_CSSM_INVALID_SERVICE_MASK);
            return 0;	// placebo
    }
}


//
// Session constructor
//
AppleCSPSession::AppleCSPSession(
	CSSM_MODULE_HANDLE handle,
	AppleCSPPlugin &plug,
	const CSSM_VERSION &version,
	uint32 subserviceId,
	CSSM_SERVICE_TYPE subserviceType,
	CSSM_ATTACH_FLAGS attachFlags,
	const CSSM_UPCALLS &upcalls)
		: CSPFullPluginSession(handle, 
			plug, 
			version, 
			subserviceId, 
			subserviceType,
			attachFlags, 
			upcalls),
		#ifdef	BSAFE_CSP_ENABLE
		bSafe4Factory(*(dynamic_cast<BSafeFactory *>(plug.bSafe4Factory))), 
		#endif
		#ifdef	CRYPTKIT_CSP_ENABLE
		cryptKitFactory(*(dynamic_cast<CryptKitFactory *>(plug.cryptKitFactory))),
		#endif
		miscAlgFactory(*(dynamic_cast<MiscAlgFactory *>(plug.miscAlgFactory))),
		#ifdef	ASC_CSP_ENABLE
		ascAlgFactory(*(dynamic_cast<AscAlgFactory *>(plug.ascAlgFactory))),
		#endif
		rsaDsaAlgFactory(*(dynamic_cast<RSA_DSA_Factory *>(plug.rsaDsaAlgFactory))),
		dhAlgFactory(*(dynamic_cast<DH_Factory *>(plug.dhAlgFactory))),
		normAllocator(*this),
		privAllocator(plug.privAlloc())
{
	// anything? 
}

AppleCSPSession::~AppleCSPSession()
{
	// anything?
}

//
// Called at (CSSM) context create time. This is ignored; we do a full 
// context setup later, at setupContext time. 
//
CSPFullPluginSession::CSPContext *
AppleCSPSession::contextCreate(
	CSSM_CC_HANDLE handle, 
	const Context &context) 
{
	return NULL;
}

//
// Called by CSPFullPluginSession when an op is actually commencing.
// Context can safely assumed to be fully formed and stable for the
// duration of the  op; thus we wait until now to set up our 
// CSPContext as appropriate to the op.
//
void AppleCSPSession::setupContext(
	CSPContext * &cspCtx,
	const Context &context, 
	bool encoding)
{
	/*
	 * Note we leave the decision as to whether it's OK to 
	 * reuse a context to the individual factories.
	 */
	#ifdef	BSAFE_CSP_ENABLE
	/* Give BSAFE the firsrt shot if it's present */
	if (bSafe4Factory.setup(*this, cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	#endif
	if (rsaDsaAlgFactory.setup(*this, cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	if (miscAlgFactory.setup(*this, cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	if (dhAlgFactory.setup(*this, cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	#ifdef	CRYPTKIT_CSP_ENABLE
	if (cryptKitFactory.setup(*this, cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	#endif
	#ifdef	ASC_CSP_ENABLE
	if (ascAlgFactory.setup(*this, cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	#endif
	if(setup(cspCtx, context)) {
		CASSERT(cspCtx != NULL);
		return;
	}
	dprintf0("AppleCSPSession::setupContext: invalid algorithm\n");
	CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
}

/* 
 * Used for generating crypto contexts at this level. 
 * Analogous to AlgorithmFactory.setup().
 */
bool AppleCSPSession::setup(
	CSPFullPluginSession::CSPContext * &cspCtx, 
	const Context &context)
{
	if (cspCtx) {
		return false;	// not ours or already set
	}
	
	switch(context.type()) {
		case CSSM_ALGCLASS_RANDOMGEN:
			switch (context.algorithm()) {
				case CSSM_ALGID_APPLE_YARROW:
					cspCtx = new YarrowContext(*this);
					return true;
				/* other random algs here */
				default:
					return false;
			}
		/* other contexts here */
		default:
			return false;
	}
	/* NOT REACHED */
	return false;

}

//
// Context for CSSM_ALGID_APPLE_YARROW.
//
YarrowContext::YarrowContext(AppleCSPSession &session)
	: AppleCSPContext(session)
{
	// nothing for now 
}

YarrowContext::~YarrowContext()
{
	// nothing for now 
}

//
// Only job here is to snag the length and process the optional seed argument
//
void YarrowContext::init(
	const Context &context, 
	bool encoding)
{
	/* stash requested length for use later in outputSize() */
	outSize = context.getInt(CSSM_ATTRIBUTE_OUTPUT_SIZE,
		CSSMERR_CSP_INVALID_ATTR_OUTPUT_SIZE);
		
	/* optional seed */
	CssmCryptoData *cseed = context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
	if(cseed == NULL) {
		/* we're done */
		return;
	}
	CssmData seed = (*cseed)();
	if((seed.Length == 0) ||
	   (seed.Data == NULL)) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SEED);
	}
	session().addEntropy((size_t)seed.Length, seed.Data);
}

void YarrowContext::final(
	CssmData &out)
{
	session().getRandomBytes((size_t)out.Length, out.Data);
}

/***
 *** Binary Key support.
 ***/

// Given a CSSM_DATA, extract its KeyRef. 
static KeyRef CssmDataToKeyRef(
	const CSSM_DATA &data)
{
	if(data.Length != sizeof(KeyRef)) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);
	}
	
	uint8 *cp = data.Data + sizeof(KeyRef) - 1;
	KeyRef keyRef = 0;
	for(unsigned dex=0; dex<sizeof(KeyRef); dex++) {
		keyRef <<= 8;
		keyRef |= *cp--;
	}
	return keyRef;
}

// Place a KeyRef into a CSSM_DATA, mallocing if necessary.
static void keyRefToCssmData(
	KeyRef			keyRef,
	CSSM_DATA		&data,
	Allocator	&allocator)
{
	if(data.Length > sizeof(keyRef)) {
		/* don't leave old raw key material lying around */
		memset(data.Data + sizeof(keyRef), 0, data.Length - sizeof(keyRef));
	}
	else if(data.Length < sizeof(keyRef)) {
		/* not enough space for even a keyRef, force realloc */
		allocator.free(data.Data);
		data.Data = NULL;
		data.Length = 0;
	}
	setUpData(data, sizeof(keyRef), allocator);
	
	uint8 *cp = data.Data;
	for(unsigned i=0; i<sizeof(keyRef); i++) {
		*cp++ = keyRef & 0xff;
		keyRef >>= 8;
	}
}

// Look up a BinaryKey by its KeyRef. Returns NULL if not 
// found. refKeyMapLock held on entry and exit.
BinaryKey *AppleCSPSession::lookupKeyRef(
	KeyRef	keyRef)
{
	const BinaryKey *binKey;
	
	// use safe version, don't create new entry if this key
	// isn't there
	keyMap::iterator it = refKeyMap.find(keyRef);
	if(it == refKeyMap.end()) {
		return NULL;
	}
	binKey = it->second;
	assert(binKey == reinterpret_cast<const BinaryKey *>(keyRef));
	assert(binKey->mKeyRef == keyRef);
	return const_cast<BinaryKey *>(binKey);
}

// add a BinaryKey to our refKeyMap. Sets up cssmKey
// as appropriate.
void AppleCSPSession::addRefKey(
	BinaryKey	&binKey,
	CssmKey		&cssmKey)
{
	// for now, KeyRef is just the address of the BinaryKey
	KeyRef			keyRef = reinterpret_cast<KeyRef>(&binKey);
	
	binKey.mKeyRef = keyRef;
	binKey.mKeyHeader = CssmKey::Header::overlay(cssmKey.KeyHeader);
	{
		StLock<Mutex> _(refKeyMapLock);
		assert(lookupKeyRef(keyRef) == NULL);
		refKeyMap[keyRef] = &binKey;
	}
	cssmKey.KeyHeader.BlobType = CSSM_KEYBLOB_REFERENCE;
	cssmKey.KeyHeader.Format = CSSM_KEYBLOB_REF_FORMAT_INTEGER;
	keyRefToCssmData(keyRef, cssmKey.KeyData, normAllocator);
	secdebug("freeKey", "CSP addRefKey key %p keyData %p keyRef %p", 
		&cssmKey, cssmKey.KeyData.Data, &binKey);
}
	
// Given a CssmKey in reference form, obtain the associated
// BinaryKey. Throws CSSMERR_CSP_INVALID_KEY_REFERENCE if
// key not found in session key map.
BinaryKey & AppleCSPSession::lookupRefKey(
	const CssmKey		&cssmKey)
{
	KeyRef 		keyRef;
	BinaryKey	*binKey;

	keyRef = CssmDataToKeyRef(cssmKey.KeyData);
	{
		StLock<Mutex> _(refKeyMapLock);
		binKey = lookupKeyRef(keyRef);
	}
	if(binKey == NULL) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);
	}
	assert(Guid::overlay(binKey->mKeyHeader.CspId) == plugin.myGuid());

	/*
	 * Verify sensitive fields have not changed between when the BinaryKey was
	 * created/stored and when the caller passed in the ref key.
	 * Some fields were changed by addRefKey, so make a local copy....
	 */
	CSSM_KEYHEADER localHdr = cssmKey.KeyHeader;
	localHdr.BlobType = binKey->mKeyHeader.BlobType;
	localHdr.Format = binKey->mKeyHeader.Format;
	if(memcmp(&localHdr, &binKey->mKeyHeader, sizeof(CSSM_KEYHEADER))) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);
	}
	return (*binKey);
}

// CSPFullPluginSession declares & implements this.
// Note that we ignore the delete argument; since we don't
// store anything, freeing is the same as deleting. 
void AppleCSPSession::FreeKey(
	const AccessCredentials *AccessCred,
	CssmKey &KeyPtr,
	CSSM_BOOL Delete)
{

	if((KeyPtr.blobType() == CSSM_KEYBLOB_REFERENCE) &&
	   (KeyPtr.cspGuid() == plugin.myGuid())) {
		// it's a ref key we generated - delete associated BinaryKey 
		KeyRef keyRef = CssmDataToKeyRef(KeyPtr.KeyData);
		{
			StLock<Mutex> _(refKeyMapLock);
			BinaryKey *binKey = lookupKeyRef(keyRef);
			if(binKey != NULL) {
				secdebug("freeKey", "CSP FreeKey key %p keyData %p binKey %p", 
					&KeyPtr, KeyPtr.KeyData.Data, binKey);
				try {
					refKeyMap.erase(keyRef);
					delete binKey;
				}
				catch (...)  {
					errorLog0("Error deleting/erasing known "
							"ref key\n");
				}
			}
			else {
				secdebug("freeKey", "CSP freeKey unknown key");
			}
		}
	}
	CSPFullPluginSession::FreeKey(AccessCred, KeyPtr, Delete);
}

/* Passthrough, used for key digest */
void AppleCSPSession::PassThrough(
	CSSM_CC_HANDLE CCHandle,
	const Context &Context,
	uint32 PassThroughId,
	const void *InData,
	void **OutData)
{
	*OutData = NULL;
	
	/* validate context */
	if(Context.type() != CSSM_ALGCLASS_NONE) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_CONTEXT);
	}

	switch(PassThroughId) {
		case CSSM_APPLECSP_KEYDIGEST:
		{
			CssmKey &key = Context.get<CssmKey>(
				CSSM_ATTRIBUTE_KEY, 
				CSSMERR_CSP_MISSING_ATTR_KEY);
				
			/* validate key as best we can */
			switch(key.keyClass()) {
				case CSSM_KEYCLASS_PUBLIC_KEY:
				case CSSM_KEYCLASS_PRIVATE_KEY:
				case CSSM_KEYCLASS_SESSION_KEY:
					break;
				default:
					CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
			}

			/* 
			 * Ref key: obtain binary, ask it for blob 
			 * Raw key: get info provider, ask it for the blob. This
			 *          allows for an optimized path which avoids
			 *		    converting to a BinaryKey. 
			 */
			CssmData blobToHash;
			switch(key.blobType()) {
				case CSSM_KEYBLOB_RAW:
				{
					CSPKeyInfoProvider *provider = infoProvider(key);
					bool converted = 
						provider->getHashableBlob(privAllocator, blobToHash);
					if(converted) {
						/* took optimized case; proceed */
						delete provider;
						break;
					}
					
					/* convert to BinaryKey and ask it to do the work */
					BinaryKey *binKey;
					CSSM_KEYATTR_FLAGS flags = 0;	// not used
					provider->CssmKeyToBinary(NULL,	// no paramKey
						flags,
						&binKey);
					binKey->mKeyHeader = 
						CssmKey::Header::overlay(key.KeyHeader);
					CSSM_KEYBLOB_FORMAT rawFormat;
					rawFormat = CSSM_KEYBLOB_RAW_FORMAT_DIGEST;
					CSSM_KEYATTR_FLAGS	attrFlags = 0;
					binKey->generateKeyBlob(privAllocator,
							blobToHash,
							rawFormat,
							*this, 
							NULL,
							attrFlags);
					delete binKey;	
					delete provider;
					break;
				}
				case CSSM_KEYBLOB_REFERENCE:
					{
						BinaryKey &binKey = lookupRefKey(key);
						CSSM_KEYBLOB_FORMAT rawFormat;
						rawFormat = CSSM_KEYBLOB_RAW_FORMAT_DIGEST;
						CSSM_KEYATTR_FLAGS attrFlags = 0;
						binKey.generateKeyBlob(privAllocator,
							blobToHash,
							rawFormat,
							*this,
							NULL,
							attrFlags);
					}
					break;
				default:
					CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
			}
			
			/* obtain sha1 hash of blobToHash */
			
			CSSM_DATA_PTR outHash = NULL;
			try {
				outHash = 
					(CSSM_DATA_PTR)normAllocator.malloc(sizeof(CSSM_DATA));
				outHash->Data = 
					(uint8 *)normAllocator.malloc(SHA1_DIGEST_SIZE);
				outHash->Length = SHA1_DIGEST_SIZE;
			}
			catch(...) {
				freeCssmData(blobToHash, privAllocator);
				throw;
			}
			cspGenSha1Hash(blobToHash.data(), blobToHash.length(), 
				outHash->Data);
			freeCssmData(blobToHash, privAllocator);
			*OutData = outHash;
			return;
		}
		default:
			CssmError::throwMe(CSSMERR_CSP_INVALID_PASSTHROUGH_ID);
	}
	/* NOT REACHED */
}

/*
 * CSPSession version of QueryKeySizeInBits.
 */
void AppleCSPSession::getKeySize(const CssmKey &key, 
	CSSM_KEY_SIZE &size)
{
	CSPKeyInfoProvider *provider = infoProvider(key);
	try {
		provider->QueryKeySizeInBits(size);
	}
	catch(...) {
		/* don't leak this on error */
		delete provider;
		throw;
	}
	delete provider;
}

void AppleCSPSession::getRandomBytes(size_t length, uint8 *cp)
{
	try {
		cspGetRandomBytes(cp, (unsigned)length);
	}
	catch(...) {
		errorLog0("CSP: YarrowClient failure\n");
	}
}

void AppleCSPSession::addEntropy(size_t length, const uint8 *cp)
{
	try {
		cspAddEntropy(cp, (unsigned)length);
	}
	catch(...) {
		#if		CSP_ALLOW_FEE_RNG
		return;
		#else
		throw;
		#endif
	}
}

/***
 *** CSPKeyInfoProvider support.
 ***/
 
/*
 * Find a CSPKeyInfoProvider subclass for the specified key.
 */
CSPKeyInfoProvider *AppleCSPSession::infoProvider(
	const CssmKey	&key)
{
	CSPKeyInfoProvider *provider = NULL;
	
	#ifdef	BSAFE_CSP_ENABLE
	/* Give BSAFE first shot, if it's here */
	provider = BSafe::BSafeKeyInfoProvider::provider(key, *this);
	if(provider != NULL) {
		return provider;
	}
	#endif
	
	provider = RSAKeyInfoProvider::provider(key, *this);
	if(provider != NULL) {
		return provider;
	}
	
	provider = SymmetricKeyInfoProvider::provider(key, *this);
	if(provider != NULL) {
		return provider;
	}

	#ifdef	CRYPTKIT_CSP_ENABLE
	provider = CryptKit::FEEKeyInfoProvider::provider(key, *this);
	if(provider != NULL) {
		return provider;
	}
	#endif
	
	provider = DSAKeyInfoProvider::provider(key, *this);
	if(provider != NULL) {
		return provider;
	}
	
	provider = DHKeyInfoProvider::provider(key, *this);
	if(provider != NULL) {
		return provider;
	}
	
	CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
}