pubKeyTool.cpp   [plain text]


/*
 * pubKeyTool.cpp - calculate public key hash of arbitrary keys and certs; derive
 *                  public key from a private key or a cert. 
 */
 
#include <stdlib.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <Security/Security.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include "cspwrap.h"
#include "common.h"

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("Options:\n");
	printf("   -k priv_key_file      -- private key file to read\n");
	printf("   -b pub_key_file       -- public key file to read\n");
	printf("   -c cert_file          -- cert file to read\n");
	printf("   -d                    -- print public key digest\n");
	printf("   -o out_file           -- write public key to out_file\n");
	printf("   -f pkcs1|pkcs8|x509   -- input key format\n");
	printf("                         -- default is PKCS8 for private key, PKCS1 for"
											" public\n");
	printf("   -K keychain           -- import pub key to this keychain; workaround "
											"for Radar 4191851)\n");
	exit(1);
}

/* Convert raw key blob into a respectable CSSM_KEY. */
static CSSM_RETURN inferCssmKey(
	const CSSM_DATA &keyBlob,
	bool isPrivKey,
	CSSM_KEYBLOB_FORMAT keyForm,
	CSSM_CSP_HANDLE cspHand,
	CSSM_KEY &outKey)
{
	memset(&outKey, 0, sizeof(CSSM_KEY));
	outKey.KeyData = keyBlob;
	CSSM_KEYHEADER &hdr = outKey.KeyHeader;
	hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
	/* CspId blank */
	hdr.BlobType = CSSM_KEYBLOB_RAW;
	hdr.AlgorithmId = CSSM_ALGID_RSA;
	hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
	hdr.Format = keyForm;
	hdr.KeyClass = isPrivKey ? CSSM_KEYCLASS_PRIVATE_KEY : CSSM_KEYCLASS_PUBLIC_KEY;
	hdr.KeyUsage = CSSM_KEYUSE_ANY;
	hdr.WrapAlgorithmId = CSSM_ALGID_NONE;
	hdr.WrapMode = CSSM_ALGMODE_NONE;
	/*
	 * LogicalKeySizeInBits - ask the CSP
	 */
	CSSM_KEY_SIZE keySize;
	CSSM_RETURN crtn;
	crtn = CSSM_QueryKeySizeInBits(cspHand, CSSM_INVALID_HANDLE, &outKey,
		&keySize);
	if(crtn) {
		cssmPerror("CSSM_QueryKeySizeInBits", crtn);
		return crtn;
	}
	hdr.LogicalKeySizeInBits = keySize.LogicalKeySizeInBits;
	return CSSM_OK;
}

/*
 * Given any key in either blob or reference format,
 * obtain the associated public key's SHA-1 hash. 
 */
static CSSM_RETURN keyDigest(
	CSSM_CSP_HANDLE		cspHand,	
	const CSSM_KEY		*key,		
	CSSM_DATA_PTR		*hashData)	/* struct and contents cuAppMalloc'd and RETURNED */
{
	CSSM_CC_HANDLE		ccHand;
	CSSM_RETURN			crtn;
	CSSM_DATA_PTR		dp;
	
	*hashData = NULL;
	
	/* validate input params */
	if((key == NULL) ||
	   (hashData == NULL)) {
	   	printf("keyHash: bogus args\n");
		return CSSMERR_CSSM_INTERNAL_ERROR;				
	}
	
	/* cook up a context for a passthrough op */
	crtn = CSSM_CSP_CreatePassThroughContext(cspHand,
	 	key,
		&ccHand);
	if(ccHand == 0) {
		cssmPerror("CSSM_CSP_CreatePassThroughContext", crtn);
		return crtn;
	}
	
	/* now it's up to the CSP */
	crtn = CSSM_CSP_PassThrough(ccHand,
		CSSM_APPLECSP_KEYDIGEST,
		NULL,
		(void **)&dp);
	if(crtn) {
		cssmPerror("CSSM_CSP_PassThrough(KEYDIGEST)", crtn);
	}
	else {
		*hashData = dp;
		crtn = CSSM_OK;
	}
	CSSM_DeleteContext(ccHand);
	return crtn;
}

/* 
 * Here's a tricky one. Given a private key, obtain the correspoding public key. 
 * This uses a private key blob format that's used internally in the CSP
 * to generate key digests. 
 */
 
/* 
 * this magic const copied from BinaryKey.h 
 */
#define CSSM_KEYBLOB_RAW_FORMAT_DIGEST	\
	(CSSM_KEYBLOB_RAW_FORMAT_VENDOR_DEFINED + 0x12345)

static CSSM_RETURN pubKeyFromPrivKey(
	CSSM_CSP_HANDLE cspHand, 
	const CSSM_KEY *privKey,			// assumed to be raw format
	CSSM_KEY *pubKey)			
{
	/* first convert to reference key */
	CSSM_KEY refKey;
	CSSM_RETURN crtn;
	crtn = cspRawKeyToRef(cspHand, privKey, &refKey);
	if(crtn) {
		return crtn;
	}
	
	/* now a NULL wrap with the magic format attribute */
	CSSM_CC_HANDLE ccHand;
	CSSM_ACCESS_CREDENTIALS	creds;
	CSSM_DATA descData = {0, 0};
	
	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
			CSSM_ALGID_NONE,
			CSSM_ALGMODE_NONE,
			NULL,			// passPhrase,
			NULL,			// key
			NULL,			// initVector,
			CSSM_PADDING_NONE,	
			NULL,			// Reserved
			&ccHand);
	if(crtn) {
		cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
		return crtn;
	}
	crtn = AddContextAttribute(ccHand,
		/* 
		 * The output of the WrapKey is a private key as far as the CSP is 
		 * concerned, at the level that this attribute is used anyway.... 
		 */
		CSSM_ATTRIBUTE_PRIVATE_KEY_FORMAT,
		sizeof(uint32),
		CAT_Uint32,
		NULL,
		CSSM_KEYBLOB_RAW_FORMAT_DIGEST);
	if(crtn) {
		cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
		goto errOut;
	}
	memset(pubKey, 0, sizeof(CSSM_KEY));
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
	crtn = CSSM_WrapKey(ccHand,
		&creds,
		&refKey,
		&descData,	
		pubKey);
	if(crtn) {
		cssmPerror("CSSM_WrapKey", crtn);
		goto errOut;
	}
	
	/* now: presto chango - don't do this at home! */
	pubKey->KeyHeader.KeyClass = CSSM_KEYCLASS_PUBLIC_KEY;
errOut:
	CSSM_FreeKey(cspHand, NULL, &refKey, CSSM_FALSE);
	CSSM_DeleteContext(ccHand);
	return crtn;
}

/* 
 * Import a key into a DLDB.
 */
static CSSM_RETURN importToDlDb(
	CSSM_CSP_HANDLE	cspHand,
	CSSM_DL_DB_HANDLE_PTR dlDbHand,
	const CSSM_KEY *rawPubKey,
	CSSM_DATA_PTR labelData,
	CSSM_KEY_PTR importedKey)
{
	CSSM_CC_HANDLE			ccHand = 0;
	CSSM_RETURN				crtn;
	uint32					keyAttr;
	CSSM_ACCESS_CREDENTIALS	creds;
	CSSM_CONTEXT_ATTRIBUTE	newAttr;	
	CSSM_DATA				descData = {0, 0};
	
	memset(importedKey, 0, sizeof(CSSM_KEY));
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
				CSSM_ALGID_NONE,
				CSSM_ALGMODE_NONE,
				&creds,
				NULL,			// unwrappingKey
				NULL,			// initVector
				CSSM_PADDING_NONE,
				0,				// Params
				&ccHand);
	if(crtn) {
		cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
		return crtn;
	}
	keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT;
	
	/* Add DLDB to context */
	newAttr.AttributeType     = CSSM_ATTRIBUTE_DL_DB_HANDLE;
	newAttr.AttributeLength   = sizeof(CSSM_ATTRIBUTE_DL_DB_HANDLE);
	newAttr.Attribute.Data    = (CSSM_DATA_PTR)dlDbHand;
	crtn = CSSM_UpdateContextAttributes(ccHand, 1, &newAttr);
	if(crtn) {
		cssmPerror("CSSM_UpdateContextAttributes", crtn);
		goto errOut;
	}
	
	/* import */
	crtn = CSSM_UnwrapKey(ccHand,
		NULL,				// PublicKey
		rawPubKey,
		CSSM_KEYUSE_ANY,
		keyAttr,
		labelData,
		NULL,				// CredAndAclEntry
		importedKey,
		&descData);			// required
	if(crtn) {
		cssmPerror("CSSM_UnwrapKey", crtn);
	}
errOut:
	if(ccHand) {
		CSSM_DeleteContext(ccHand);
	}
	return crtn;
}

/*
 * Free memory via specified plugin's app-level allocator
 */
void impExpFreeCssmMemory(
	CSSM_HANDLE		hand,
	void 			*p)
{
	CSSM_API_MEMORY_FUNCS memFuncs;
	CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
	if(crtn) {
		return;
	}
	memFuncs.free_func(p, memFuncs.AllocRef);
}

/*
 * Key attrribute names and values.
 *
 * This is where the public key hash goes.
 */
#define SEC_KEY_HASH_ATTR_NAME			"Label"

/*
 * This is where the publicly visible name goes.
 */
#define SEC_KEY_PRINT_NAME_ATTR_NAME	"PrintName"

/* 
 * Look up public key by label 
 * Set label to new specified label (SHA1 digest)
 * Set print name to new specified user-visible name
 */
static CSSM_RETURN setPubKeyLabel(
	CSSM_CSP_HANDLE 	cspHand,		// where the key lives
	CSSM_DL_DB_HANDLE 	*dlDbHand,		// ditto
	const CSSM_DATA		*existKeyLabel,	// existing label, a random string, for lookup
	const CSSM_DATA		*keyDigest,		// SHA1 digest, the new label
	const CSSM_DATA		*newPrintName)	// new user-visible name
{
	CSSM_QUERY						query;
	CSSM_SELECTION_PREDICATE		predicate;
	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
	CSSM_RETURN						crtn;
	CSSM_HANDLE						resultHand = 0;
	
	/*
	 * Look up the key in the DL.
	 */
	query.RecordType = CSSM_DL_DB_RECORD_PUBLIC_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 = (CSSM_DATA_PTR)existKeyLabel;
	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 two attrs */
	CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
	CSSM_DB_ATTRIBUTE_DATA attr[2];
	
	attr[0].Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr[0].Info.Label.AttributeName = (char *)SEC_KEY_HASH_ATTR_NAME;
	attr[0].Info.AttributeFormat     = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr[1].Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr[1].Info.Label.AttributeName = (char *)SEC_KEY_PRINT_NAME_ATTR_NAME;
	attr[1].Info.AttributeFormat     = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	
	recordAttrs.DataRecordType		 = CSSM_DL_DB_RECORD_PUBLIC_KEY;
	recordAttrs.NumberOfAttributes   = 2;
	recordAttrs.AttributeData        = attr;
	
	crtn = CSSM_DL_DataGetFirst(*dlDbHand,
		&query,
		&resultHand,
		&recordAttrs,
		NULL,			// theData
		&record);
	/* abort only on success */
	if(crtn != CSSM_OK) {
		cssmPerror("CSSM_DL_DataGetFirst", crtn);
		goto errOut;
	}
	
	/* 
	 * Update existing attr data.
	 * NOTE: the module which allocated this attribute data - a DL -
	 * was loaded and attached by the keychain layer, not by us. Thus 
	 * we can't use the memory allocator functions *we* used when 
	 * attaching to the CSP - we have to use the ones
	 * which the client registered with the DL.
	 */
	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[0].Value->Data);
	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[0].Value);
	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[1].Value->Data);
	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[1].Value);
	attr[0].Value = const_cast<CSSM_DATA *>(keyDigest);
	attr[1].Value = const_cast<CSSM_DATA *>(newPrintName);
	
	crtn = CSSM_DL_DataModify(*dlDbHand,
			CSSM_DL_DB_RECORD_PUBLIC_KEY,
			record,
			&recordAttrs,
            NULL,				// DataToBeModified
			CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
	if(crtn) {
		cssmPerror("CSSM_DL_DataModify", crtn);
	}
errOut:
	/* free resources */
	if(resultHand) {
		CSSM_DL_DataAbortQuery(*dlDbHand, resultHand);
	}
	if(record) {
		CSSM_DL_FreeUniqueRecord(*dlDbHand, record);
	}
	return crtn;
}

#define SHA1_LABEL_LEN	20
#define IMPORTED_KEY_NAME "Imported Public Key"

/* 
 * Import a public key into a keychain, with proper Label attribute setting. 
 * A workaround for Radar 4191851.
 */
static int pubKeyImport(
	const char *kcName, 
	const CSSM_KEY *pubKey,
	CSSM_CSP_HANDLE rawCspHand)		/* raw CSP handle for calculating digest */
{
	CSSM_CSP_HANDLE cspHand;
	CSSM_DL_DB_HANDLE dlDbHand;
	OSStatus ortn;
	CSSM_RETURN crtn;
	SecKeychainRef kcRef = NULL;
	int ourRtn = 0;
	CSSM_DATA_PTR digest = NULL;
	CSSM_KEY importedKey;
	CSSM_DATA newPrintName = 
		{ (uint32)strlen(IMPORTED_KEY_NAME), (uint8 *)IMPORTED_KEY_NAME};
		
	/* NULL unwrap stuff */
	uint8 tempLabel[SHA1_LABEL_LEN];
	CSSM_DATA labelData = {SHA1_LABEL_LEN, tempLabel};
	
	ortn = SecKeychainOpen(kcName, &kcRef);
	if(ortn) {
		cssmPerror("SecKeychainOpen", ortn);
		return -1;
	}
	/* subsequent errors to errOut: */
	
	/* Get CSSM handles */
	ortn = SecKeychainGetCSPHandle(kcRef, &cspHand);
	if(ortn) {
		cssmPerror("SecKeychainGetCSPHandle", ortn);
		ourRtn = -1;
		goto errOut;
	}
	ortn = SecKeychainGetDLDBHandle(kcRef, &dlDbHand);
	if(ortn) {
		cssmPerror("SecKeychainGetCSPHandle", ortn);
		ourRtn = -1;
		goto errOut;
	}
	
	/* public key hash from raw CSP */
	crtn = keyDigest(rawCspHand, pubKey, &digest);
	if(crtn) {
		ourRtn = -1;
		goto errOut;
	}
	
	/* random label for initial storage and later retrieval */
	appGetRandomBytes(tempLabel, SHA1_LABEL_LEN);
	
	/* import the key into the keychain's DLDB */
	memset(&importedKey, 0, sizeof(CSSM_KEY));
	crtn = importToDlDb(cspHand, &dlDbHand, pubKey, &labelData, &importedKey);
	if(crtn) {
		ourRtn = -1;
		goto errOut;
	}
	
	/* don't need this */
	CSSM_FreeKey(cspHand, NULL, &importedKey, CSSM_FALSE);
	
	/* update the label and printName attributes */
	crtn = setPubKeyLabel(cspHand, &dlDbHand, &labelData, digest, &newPrintName);
	if(crtn) {
		ourRtn = -1;
	}
errOut:
	CFRelease(kcRef);
	if(digest) {
		APP_FREE(digest->Data);
		APP_FREE(digest);
	}
	return ourRtn;
}

int main(int argc, char **argv)
{
	char *privKeyFile = NULL;
	char *pubKeyFile = NULL;
	char *certFile = NULL;
	char *outFile = NULL;
	bool printDigest = false;
	CSSM_KEYBLOB_FORMAT keyForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
	char *kcName = NULL;
	
	if(argc < 3) {
		usage(argv);
	}
	extern char *optarg;
	int arg;
	while ((arg = getopt(argc, argv, "k:b:c:do:f:K:h")) != -1) {
		switch (arg) {
			case 'k':
				privKeyFile = optarg;
				break;
			case 'b':
				pubKeyFile = optarg;
				break;
			case 'c':
				certFile = optarg;
				break;
			case 'd':
				printDigest = true;
				break;
			case 'o':
				outFile = optarg;
				break;
			case 'f':
				if(!strcmp("pkcs1", optarg)) {
					keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS1;
				}
				else if(!strcmp("pkcs8", optarg)) {
					keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS8;
				}
				else if(!strcmp("x509", optarg)) {
					keyForm = CSSM_KEYBLOB_RAW_FORMAT_X509;
				}
				break;
			case 'K':
				kcName = optarg;
				break;
			case 'h':
				usage(argv);
		}
	}
	if(optind != argc) {
		usage(argv);
	}
	
	CSSM_DATA privKeyBlob = {0, NULL};
	CSSM_DATA pubKeyBlob = {0, NULL};
	CSSM_KEY thePrivKey;			// constructed
	CSSM_KEY thePubKey;				// null-wrapped
	CSSM_KEY_PTR pubKey = NULL;
	CSSM_KEY_PTR privKey = NULL;
	CSSM_RETURN crtn;
	CSSM_CL_HANDLE clHand = 0;
	CSSM_CSP_HANDLE cspHand = cuCspStartup(CSSM_TRUE);

	/* gather input */
	if(privKeyFile) {
		/* key blob from a file ==> a private CSSM_KEY */

		if(pubKeyFile || certFile) {
			printf("****Specify exactly one of {cert_file, priv_key_file, "
					"pub_key_file}.\n");
			exit(1);
		}
		unsigned len;
		if(readFile(privKeyFile, &privKeyBlob.Data, &len)) {
			printf("***Error reading private key from %s. Aborting.\n", privKeyFile);
			exit(1);
		}
		privKeyBlob.Length = len;
		if(keyForm == CSSM_KEYBLOB_RAW_FORMAT_NONE) {
			/* default for private keys */
			keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS8;
		}
		crtn = inferCssmKey(privKeyBlob, true, keyForm, cspHand, thePrivKey);
		if(crtn) {
			goto errOut;
		}
		privKey = &thePrivKey;
	}
	if(pubKeyFile) {
		/* key blob from a file ==> a public CSSM_KEY */

		if(privKeyFile || certFile) {
			printf("****Specify exactly one of {cert_file, priv_key_file, "
					"pub_key_file}.\n");
			exit(1);
		}
		
		unsigned len;
		if(readFile(pubKeyFile, &pubKeyBlob.Data, &len)) {
			printf("***Error reading public key from %s. Aborting.\n", pubKeyFile);
			exit(1);
		}
		pubKeyBlob.Length = len;
		if(keyForm == CSSM_KEYBLOB_RAW_FORMAT_NONE) {
			/* default for public keys */
			keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS1;
		}
		crtn = inferCssmKey(pubKeyBlob, false, keyForm, cspHand, thePubKey);
		if(crtn) {
			goto errOut;
		}
		pubKey = &thePubKey;
	}
	if(certFile) {
		/* cert from a file ==> a public CSSM_KEY */

		if(privKeyFile || pubKeyFile) {
			printf("****Specify exactly one of {cert_file, priv_key_file, "
					"pub_key_file}.\n");
			exit(1);
		}
		
		CSSM_DATA certData = {0, NULL};
		unsigned len;
		if(readFile(certFile, &certData.Data, &len)) {
			printf("***Error reading cert from %s. Aborting.\n", certFile);
			exit(1);
		}
		certData.Length = len;
		
		/* Extract public key - that's what we will be using later */
		clHand = cuClStartup();
		crtn = CSSM_CL_CertGetKeyInfo(clHand, &certData, &pubKey);
		if(crtn) {
			cssmPerror("CSSM_CL_CertGetKeyInfo", crtn);
			goto errOut;
		}
	}
	
	/* now do something useful */
	if(printDigest) {
		CSSM_KEY_PTR theKey = privKey;
		if(theKey == NULL) {
			/* maybe we got public key from a cert */
			theKey = pubKey;
		}
		if(theKey == NULL) {
			printf("***Can't calculate digest because I don't have a key or a clue.\n");
			goto errOut;
		}
		CSSM_DATA_PTR dig = NULL;
		crtn = keyDigest(cspHand, theKey, &dig);
		if(crtn) {
			printf("Sorry, can't get the digest for this key.\n");
			goto errOut;
		}
		if((dig == NULL) || (dig->Length == 0)) {
			printf("Screwup calculating digest.\n");
			goto errOut;
		}
		printf("Key Digest:\n");
		for(unsigned dex=0; dex<dig->Length; dex++) {
			printf("%02X ", dig->Data[dex]);
		}
		printf("\n");
		APP_FREE(dig->Data);
		APP_FREE(dig);
	}
	
	if(outFile || kcName) {
		/* get a public key if we don't already have one */
		if(pubKey == NULL) {
			if(privKey == NULL) {
				printf("***PubKey file name specified but no privKey or cert. "	
						"Aborting.\n");
				goto errOut;
			}
			crtn = pubKeyFromPrivKey(cspHand, privKey, &thePubKey);
			if(crtn) {
				goto errOut;
			}
			pubKey = &thePubKey;
		}
	}
	if(outFile) {
		if(writeFile(outFile, pubKey->KeyData.Data, pubKey->KeyData.Length)) {
			printf("***Error writing to %s.\n", outFile);
		}
		else {
			printf("...%lu bytes written to %s.\n", pubKey->KeyData.Length, outFile);
		}
	}
	if(kcName) {
		if(pubKeyImport(kcName, pubKey, cspHand) == 0) {
			printf("....public key %s imported to %s\n", pubKeyFile, kcName);
		}
		else {
			printf("***Error importing public key %s to %s\n", pubKeyFile, kcName);
		}
	}
errOut:
	/* clean up here if you must */
	return 0;
}