dbCert.cpp   [plain text]


/*
 * Copyright (c) 2003-2005,2008 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.
 */

/*
 * dbCert.cpp - import a possibly bad cert along with its private key
 */

#include "dbCert.h"
#include <Security/Security.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <security_cdsa_utils/cuDbUtils.h> 
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_cdsa_utils/cuPem.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* Copied from clxutils/clAppUtils/tpUtils */
/* defined in SecKeychainAPIPriv.h */
static const int kSecAliasItemAttr = 'alis';

/* Macro to declare a CSSM_DB_SCHEMA_ATTRIBUTE_INFO */
#define SCHEMA_ATTR_INFO(id, name, type)	\
	{ id, (char *)name, {0, NULL},  CSSM_DB_ATTRIBUTE_FORMAT_ ## type }
	
/* Too bad we can't get this from inside of the Security framework. */
static CSSM_DB_SCHEMA_ATTRIBUTE_INFO certSchemaAttrInfo[] = 
{
	SCHEMA_ATTR_INFO(kSecCertTypeItemAttr, "CertType", UINT32),
	SCHEMA_ATTR_INFO(kSecCertEncodingItemAttr, "CertEncoding", UINT32),
	SCHEMA_ATTR_INFO(kSecLabelItemAttr, "PrintName", BLOB),
	SCHEMA_ATTR_INFO(kSecAliasItemAttr, "Alias", BLOB),
	SCHEMA_ATTR_INFO(kSecSubjectItemAttr, "Subject", BLOB),
	SCHEMA_ATTR_INFO(kSecIssuerItemAttr, "Issuer", BLOB),
	SCHEMA_ATTR_INFO(kSecSerialNumberItemAttr, "SerialNumber", BLOB),
	SCHEMA_ATTR_INFO(kSecSubjectKeyIdentifierItemAttr, "SubjectKeyIdentifier", BLOB),
	SCHEMA_ATTR_INFO(kSecPublicKeyHashItemAttr, "PublicKeyHash", BLOB)
};
#define NUM_CERT_SCHEMA_ATTRS	\
	(sizeof(certSchemaAttrInfo) / sizeof(CSSM_DB_SCHEMA_ATTRIBUTE_INFO))

/* Macro to declare a CSSM_DB_SCHEMA_INDEX_INFO */
#define SCHEMA_INDEX_INFO(id, indexNum, indexType)	\
	{ id, CSSM_DB_INDEX_ ## indexType,  CSSM_DB_INDEX_ON_ATTRIBUTE }
	

static CSSM_DB_SCHEMA_INDEX_INFO certSchemaIndices[] = 
{
	SCHEMA_INDEX_INFO(kSecCertTypeItemAttr, 0, UNIQUE),
	SCHEMA_INDEX_INFO(kSecIssuerItemAttr, 0, UNIQUE),
	SCHEMA_INDEX_INFO(kSecSerialNumberItemAttr, 0, UNIQUE),
	SCHEMA_INDEX_INFO(kSecCertTypeItemAttr, 1, NONUNIQUE),
	SCHEMA_INDEX_INFO(kSecSubjectItemAttr, 2, NONUNIQUE),
	SCHEMA_INDEX_INFO(kSecIssuerItemAttr, 3, NONUNIQUE),
	SCHEMA_INDEX_INFO(kSecSerialNumberItemAttr, 4, NONUNIQUE),
	SCHEMA_INDEX_INFO(kSecSubjectKeyIdentifierItemAttr, 5, NONUNIQUE),
	SCHEMA_INDEX_INFO(kSecPublicKeyHashItemAttr, 6, NONUNIQUE)
};
#define NUM_CERT_INDICES	\
	(sizeof(certSchemaIndices) / sizeof(CSSM_DB_SCHEMA_INDEX_INFO))


CSSM_RETURN tpAddCertSchema(
	CSSM_DL_DB_HANDLE	dlDbHand)
{
	return CSSM_DL_CreateRelation(dlDbHand,
		CSSM_DL_DB_RECORD_X509_CERTIFICATE,
		"CSSM_DL_DB_RECORD_X509_CERTIFICATE",
		NUM_CERT_SCHEMA_ATTRS,
		certSchemaAttrInfo,
		NUM_CERT_INDICES,
		certSchemaIndices);		
}


/* copied verbatim from certTool */

/*
 * Find private key by label, modify its Label attr to be the
 * hash of the associated public key. 
 */
static CSSM_RETURN setPubKeyHash(
	CSSM_CSP_HANDLE 	cspHand,
	CSSM_DL_DB_HANDLE 	dlDbHand,
	const char			*keyLabel,		// look up by this
	CSSM_DATA			*rtnKeyDigest)	// optionally RETURNED, if so,
										// caller owns and must cuAppFree
{
	CSSM_QUERY						query;
	CSSM_SELECTION_PREDICATE		predicate;
	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
	CSSM_RETURN						crtn;
	CSSM_DATA						labelData;
	CSSM_HANDLE						resultHand;
	
	labelData.Data = (uint8 *)keyLabel;
	labelData.Length = strlen(keyLabel) + 1;	// incl. NULL
	query.RecordType = CSSM_DL_DB_RECORD_PRIVATE_KEY;
	query.Conjunctive = CSSM_DB_NONE;
	query.NumSelectionPredicates = 1;
	predicate.DbOperator = CSSM_DB_EQUAL;
	
	predicate.Attribute.Info.AttributeNameFormat = 
		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	predicate.Attribute.Info.Label.AttributeName = (char *)"Label";
	predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	/* hope this cast is OK */
	predicate.Attribute.Value = &labelData;
	query.SelectionPredicate = &predicate;
	
	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
	query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA;	// FIXME - used?

	/* build Record attribute with one attr */
	CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
	CSSM_DB_ATTRIBUTE_DATA attr;
	attr.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr.Info.Label.AttributeName = (char *)"Label";
	attr.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;

	recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_PRIVATE_KEY;
	recordAttrs.NumberOfAttributes = 1;
	recordAttrs.AttributeData = &attr;
	
	CSSM_DATA recordData = {0, NULL};
	crtn = CSSM_DL_DataGetFirst(dlDbHand,
		&query,
		&resultHand,
		&recordAttrs,
		&recordData,	
		&record);
	/* abort only on success */
	if(crtn != CSSM_OK) {
		cuPrintError("CSSM_DL_DataGetFirst", crtn);
		return crtn;
	}
	
	CSSM_KEY_PTR keyToDigest = (CSSM_KEY_PTR)recordData.Data;
	CSSM_DATA_PTR keyDigest = NULL;
	CSSM_CC_HANDLE ccHand;
	crtn = CSSM_CSP_CreatePassThroughContext(cspHand,
	 	keyToDigest,
		&ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreatePassThroughContext", crtn);
		return crtn;
	}
	crtn = CSSM_CSP_PassThrough(ccHand,
		CSSM_APPLECSP_KEYDIGEST,
		NULL,
		(void **)&keyDigest);
	if(crtn) {
		cuPrintError("CSSM_CSP_PassThrough(PUBKEYHASH)", crtn);
		return -1;
	}
	CSSM_FreeKey(cspHand, NULL, keyToDigest, CSSM_FALSE);
	CSSM_DeleteContext(ccHand);
	
	/* 
	 * Replace Label attr data with hash.
	 * NOTE: the module which allocated this attribute data - a DL -
	 * was loaded and attached by the Sec layer, not by us. Thus 
	 * we can't use the memory allocator functions *we* used when 
	 * attaching to the CSPDL - we have to use the ones
	 * which the Sec layer registered with the DL.
	 */
	CSSM_API_MEMORY_FUNCS memFuncs;
	crtn = CSSM_GetAPIMemoryFunctions(dlDbHand.DLHandle, &memFuncs);
	if(crtn) {
		cuPrintError("CSSM_GetAPIMemoryFunctions(DLHandle)", crtn);
		/* oh well, leak and continue */
	}
	else {
		memFuncs.free_func(attr.Value->Data, memFuncs.AllocRef);
		memFuncs.free_func(attr.Value, memFuncs.AllocRef);
	}
	attr.Value = keyDigest;
	
	/* modify key attributes */
	crtn = CSSM_DL_DataModify(dlDbHand,
			CSSM_DL_DB_RECORD_PRIVATE_KEY,
			record,
			&recordAttrs,
            NULL,				// DataToBeModified
			CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
	if(crtn) {
		cuPrintError("CSSM_DL_DataModify(PUBKEYHASH)", crtn);
		return crtn;
	}
	crtn = CSSM_DL_DataAbortQuery(dlDbHand, resultHand);
	if(crtn) {
		cuPrintError("CSSM_DL_DataAbortQuery", crtn);
		/* let's keep going in this case */
	}
	crtn = CSSM_DL_FreeUniqueRecord(dlDbHand, record);
	if(crtn) {
		cuPrintError("CSSM_DL_FreeUniqueRecord", crtn);
		/* let's keep going in this case */
		crtn = CSSM_OK;
	}
	
	/* free resources */
	if(rtnKeyDigest) {
		*rtnKeyDigest = *keyDigest;
	}
	else {
		cuAppFree(keyDigest->Data, NULL);
		/* FIXME - don't we have to free keyDigest itself? */
	}
	return CSSM_OK;
}

static CSSM_RETURN importPrivateKey(
	CSSM_DL_DB_HANDLE	dlDbHand,		
	CSSM_CSP_HANDLE		cspHand,
	const char			*privKeyFileName,
	CSSM_ALGORITHMS		keyAlg,
	CSSM_BOOL			pemFormat,	// of the file
	CSSM_KEYBLOB_FORMAT	keyFormat,	// of the key blob itself, NONE means 
									//   use default
	CSSM_DATA			*keyHash)	// OPTIONALLY RETURNED - if so, caller
									//   owns and must cuAppFree()
{
	unsigned char 			*derKey = NULL;
	unsigned 				derKeyLen;
	unsigned char 			*pemKey = NULL;
	unsigned 				pemKeyLen;
	CSSM_KEY 				wrappedKey;
	CSSM_KEY 				unwrappedKey;
	CSSM_ACCESS_CREDENTIALS	creds;
	CSSM_CC_HANDLE			ccHand = 0;
	CSSM_RETURN				crtn;
	CSSM_DATA				labelData;
	CSSM_KEYHEADER_PTR		hdr = &wrappedKey.KeyHeader;
	CSSM_DATA 				descData = {0, NULL};
	CSSM_CSP_HANDLE			rawCspHand = 0;
	const char				*privKeyLabel = NULL;
	
	/*
	 * Validate specified format for clarity 
	 */
	switch(keyAlg) {
		case CSSM_ALGID_RSA:
			switch(keyFormat) {
				case CSSM_KEYBLOB_RAW_FORMAT_NONE:
					keyFormat = CSSM_KEYBLOB_RAW_FORMAT_PKCS1;	// default
					break;
				case CSSM_KEYBLOB_RAW_FORMAT_PKCS1:
				case CSSM_KEYBLOB_RAW_FORMAT_PKCS8:
					break;
				default:
					printf("***RSA Private key must be in PKCS1 or PKCS8 "
						"format\n");
					return CSSMERR_CSSM_INTERNAL_ERROR;
			}
			privKeyLabel = "Imported RSA key";
			break;
		case CSSM_ALGID_DSA:
			switch(keyFormat) {
				case CSSM_KEYBLOB_RAW_FORMAT_NONE:
					keyFormat = CSSM_KEYBLOB_RAW_FORMAT_OPENSSL;	
								// default
					break;
				case CSSM_KEYBLOB_RAW_FORMAT_FIPS186:
				case CSSM_KEYBLOB_RAW_FORMAT_OPENSSL:
				case CSSM_KEYBLOB_RAW_FORMAT_PKCS8:
					break;
				default:
					printf("***DSA Private key must be in openssl, FIPS186, "
						"or PKCS8 format\n");
					return CSSMERR_CSSM_INTERNAL_ERROR;
			}
			privKeyLabel = "Imported DSA key";
			break;
		case CSSM_ALGID_DH:
			switch(keyFormat) {
				case CSSM_KEYBLOB_RAW_FORMAT_NONE:
					keyFormat = CSSM_KEYBLOB_RAW_FORMAT_PKCS8;	// default
					break;
				case CSSM_KEYBLOB_RAW_FORMAT_PKCS8:
					break;
				default:
					printf("***Diffie-Hellman Private key must be in"
						"PKCS8 format.\n");
					return CSSMERR_CSSM_INTERNAL_ERROR;
			}
			privKeyLabel = "Imported Diffie-Hellman key";
			break;
	}
	if(readFile(privKeyFileName, &pemKey, &pemKeyLen)) {
		printf("***Error reading private key from file %s. Aborting.\n",
			privKeyFileName);
		return CSSMERR_CSSM_INTERNAL_ERROR;
	}
	/* subsequent errors to done: */
	if(pemFormat) {
		int rtn = pemDecode(pemKey, pemKeyLen, &derKey, &derKeyLen);
		if(rtn) {
			printf("***%s: Bad PEM formatting. Aborting.\n", 
				privKeyFileName);
			crtn = CSSMERR_CSP_INVALID_KEY;
			goto done;
		}
	}
	else {
		derKey = pemKey;
		derKeyLen = pemKeyLen;
	}
	
	/* importing a raw key into the CSPDL involves a NULL unwrap */
	memset(&unwrappedKey, 0, sizeof(CSSM_KEY));
	memset(&wrappedKey, 0, sizeof(CSSM_KEY));
	
	/* set up the imported key to look like a CSSM_KEY */
	hdr->HeaderVersion 			= CSSM_KEYHEADER_VERSION;
	hdr->BlobType 				= CSSM_KEYBLOB_RAW;
	hdr->AlgorithmId		 	= keyAlg;
	hdr->KeyClass 				= CSSM_KEYCLASS_PRIVATE_KEY;
	hdr->KeyAttr 				= CSSM_KEYATTR_EXTRACTABLE;
	hdr->KeyUsage 				= CSSM_KEYUSE_ANY;
	hdr->Format 				= keyFormat;	
	wrappedKey.KeyData.Data 	= derKey;
	wrappedKey.KeyData.Length 	= derKeyLen;
	
	/* get key size in bits from raw CSP */
	rawCspHand = cuCspStartup(CSSM_TRUE);
	if(rawCspHand == 0) {
		printf("***Error attaching to CSP. Aborting.\n");
		crtn = CSSMERR_CSSM_INTERNAL_ERROR;
		goto done;
	}
	CSSM_KEY_SIZE keySize;
	crtn = CSSM_QueryKeySizeInBits(rawCspHand, CSSM_INVALID_HANDLE, &wrappedKey, &keySize);
	if(crtn) {
		cuPrintError("CSSM_QueryKeySizeInBits",crtn);
		goto done;
	}
	hdr->LogicalKeySizeInBits = keySize.LogicalKeySizeInBits;
	
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
		CSSM_ALGID_NONE,			// unwrapAlg
		CSSM_ALGMODE_NONE,			// unwrapMode
		&creds,
		NULL, 						// unwrappingKey
		NULL,						// initVector
		CSSM_PADDING_NONE, 			// unwrapPad
		0,							// Params
		&ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateSymmetricContext", crtn);
		goto done;
	}
	
	/* add DL/DB to context */
	CSSM_CONTEXT_ATTRIBUTE newAttr;
	newAttr.AttributeType     = CSSM_ATTRIBUTE_DL_DB_HANDLE;
	newAttr.AttributeLength   = sizeof(CSSM_DL_DB_HANDLE);
	newAttr.Attribute.Data    = (CSSM_DATA_PTR)&dlDbHand;
	crtn = CSSM_UpdateContextAttributes(ccHand, 1, &newAttr);
	if(crtn) {
		cuPrintError("CSSM_UpdateContextAttributes", crtn);
		goto done;
	}
	
	/* do the NULL unwrap */
	labelData.Data = (uint8 *)privKeyLabel;
	labelData.Length = strlen(privKeyLabel) + 1;
	crtn = CSSM_UnwrapKey(ccHand,
		NULL,				// PublicKey
		&wrappedKey,
		CSSM_KEYUSE_ANY,
		CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT | 
			CSSM_KEYATTR_SENSITIVE |CSSM_KEYATTR_EXTRACTABLE,
		&labelData,
		NULL,				// CredAndAclEntry
		&unwrappedKey,
		&descData);		// required
	if(crtn != CSSM_OK) {
		cuPrintError("CSSM_UnwrapKey", crtn);
		goto done;
	}
	
	/* one more thing: bind this private key to its public key */
	crtn = setPubKeyHash(cspHand, dlDbHand, privKeyLabel, keyHash);
	
	/* We don't need the unwrapped key any more */
	CSSM_FreeKey(cspHand, 
		NULL,			// access cred
		&unwrappedKey,
		CSSM_FALSE);	// delete 

done:
	if(ccHand) {
		CSSM_DeleteContext(ccHand);
	}
	if(derKey) {
		free(derKey);		// mallocd by readFile() */
	}
	if(pemFormat && pemKey) {
		free(pemKey);
	}
	if(rawCspHand) {
		CSSM_ModuleDetach(rawCspHand);
	}
	return crtn;
}


CSSM_RETURN importBadCert(
	CSSM_DL_HANDLE dlHand,
	const char *dbFileName, 
	const char *certFile, 
	const char *keyFile, 
	CSSM_ALGORITHMS	keyAlg,
	CSSM_BOOL pemFormat,			// of the file
	CSSM_KEYBLOB_FORMAT	keyFormat,	// of the key blob itself, NONE means 
									//   use default
	CSSM_BOOL verbose)
{
	CSSM_DL_DB_HANDLE dlDbHand = {dlHand, 0};
	CSSM_RETURN crtn;
	CSSM_DATA keyDigest = {0, NULL};
	CSSM_DATA certData = {0, NULL};
	unsigned len;
	
	CSSM_CSP_HANDLE cspHand = cuCspStartup(CSSM_FALSE);
	if(cspHand == 0) {
		printf("***Error attaching to CSPDL. Aborting.\n");
		return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
	}
	
	/*
	 * 1. Open the (already existing) DB.
	 */
	dlDbHand.DBHandle = cuDbStartupByName(dlHand,
		(char *)dbFileName,		// bogus non-const prototype
		CSSM_FALSE,				// do NOT create it
		CSSM_FALSE);			// quiet
	if(dlDbHand.DBHandle == 0) {
		printf("Error opening %s. Aborting.\n", dbFileName);
		return CSSMERR_DL_DATASTORE_DOESNOT_EXIST;
	}
	
	/*
	 * Import key to DB, snagging its key digest along the way.
	 */
	crtn = importPrivateKey(dlDbHand, cspHand,
		keyFile, keyAlg, pemFormat, keyFormat,
		&keyDigest);
	if(crtn) {
		printf("***Error importing key %s. Aborting.\n", keyFile);
		goto errOut;
	}
	
	/* 
	 * Now the cert.
	 */
	if(readFile(certFile, &certData.Data, &len)) {
		printf("***Error reading cert from %s. Aborting.\n", certFile);
		goto errOut;
	}
	certData.Length = len;
	crtn = cuAddCertToDb(dlDbHand, &certData, 
		CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER,
		certFile,			// printName
		&keyDigest);
	if(crtn == CSSMERR_DL_INVALID_RECORDTYPE) {
		/* virgin DB, no cert schema: add schema and retry */
		crtn = tpAddCertSchema(dlDbHand);
		if(crtn == CSSM_OK) {
			crtn = cuAddCertToDb(dlDbHand, &certData, 
				CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER,
				certFile,			// printName
				&keyDigest);
		}
	}
	if(crtn) {
		printf("***Error importing cert %s. Aborting.\n", certFile);
	}
errOut:
	if(keyDigest.Data) {
		cuAppFree(keyDigest.Data, NULL);
	}
	if(dlDbHand.DBHandle) {
		CSSM_DL_DbClose(dlDbHand);
	}
	if(certData.Data) {
		free(certData.Data);
	}
	return crtn;
}