1/*
2 * pubKeyTool.cpp - calculate public key hash of arbitrary keys and certs; derive
3 *                  public key from a private key or a cert.
4 */
5
6#include <stdlib.h>
7#include <strings.h>
8#include <stdio.h>
9#include <unistd.h>
10#include <Security/Security.h>
11#include <security_cdsa_utils/cuFileIo.h>
12#include <security_cdsa_utils/cuCdsaUtils.h>
13#include "cspwrap.h"
14#include "common.h"
15
16static void usage(char **argv)
17{
18	printf("usage: %s [options]\n", argv[0]);
19	printf("Options:\n");
20	printf("   -k priv_key_file      -- private key file to read\n");
21	printf("   -b pub_key_file       -- public key file to read\n");
22	printf("   -c cert_file          -- cert file to read\n");
23	printf("   -d                    -- print public key digest\n");
24	printf("   -o out_file           -- write public key to out_file\n");
25	printf("   -f pkcs1|pkcs8|x509   -- input key format\n");
26	printf("                         -- default is PKCS8 for private key, PKCS1 for"
27											" public\n");
28	printf("   -K keychain           -- import pub key to this keychain; workaround "
29											"for Radar 4191851)\n");
30	exit(1);
31}
32
33/* Convert raw key blob into a respectable CSSM_KEY. */
34static CSSM_RETURN inferCssmKey(
35	const CSSM_DATA &keyBlob,
36	bool isPrivKey,
37	CSSM_KEYBLOB_FORMAT keyForm,
38	CSSM_CSP_HANDLE cspHand,
39	CSSM_KEY &outKey)
40{
41	memset(&outKey, 0, sizeof(CSSM_KEY));
42	outKey.KeyData = keyBlob;
43	CSSM_KEYHEADER &hdr = outKey.KeyHeader;
44	hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
45	/* CspId blank */
46	hdr.BlobType = CSSM_KEYBLOB_RAW;
47	hdr.AlgorithmId = CSSM_ALGID_RSA;
48	hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
49	hdr.Format = keyForm;
50	hdr.KeyClass = isPrivKey ? CSSM_KEYCLASS_PRIVATE_KEY : CSSM_KEYCLASS_PUBLIC_KEY;
51	hdr.KeyUsage = CSSM_KEYUSE_ANY;
52	hdr.WrapAlgorithmId = CSSM_ALGID_NONE;
53	hdr.WrapMode = CSSM_ALGMODE_NONE;
54	/*
55	 * LogicalKeySizeInBits - ask the CSP
56	 */
57	CSSM_KEY_SIZE keySize;
58	CSSM_RETURN crtn;
59	crtn = CSSM_QueryKeySizeInBits(cspHand, CSSM_INVALID_HANDLE, &outKey,
60		&keySize);
61	if(crtn) {
62		cssmPerror("CSSM_QueryKeySizeInBits", crtn);
63		return crtn;
64	}
65	hdr.LogicalKeySizeInBits = keySize.LogicalKeySizeInBits;
66	return CSSM_OK;
67}
68
69/*
70 * Given any key in either blob or reference format,
71 * obtain the associated public key's SHA-1 hash.
72 */
73static CSSM_RETURN keyDigest(
74	CSSM_CSP_HANDLE		cspHand,
75	const CSSM_KEY		*key,
76	CSSM_DATA_PTR		*hashData)	/* struct and contents cuAppMalloc'd and RETURNED */
77{
78	CSSM_CC_HANDLE		ccHand;
79	CSSM_RETURN			crtn;
80	CSSM_DATA_PTR		dp;
81
82	*hashData = NULL;
83
84	/* validate input params */
85	if((key == NULL) ||
86	   (hashData == NULL)) {
87	   	printf("keyHash: bogus args\n");
88		return CSSMERR_CSSM_INTERNAL_ERROR;
89	}
90
91	/* cook up a context for a passthrough op */
92	crtn = CSSM_CSP_CreatePassThroughContext(cspHand,
93	 	key,
94		&ccHand);
95	if(ccHand == 0) {
96		cssmPerror("CSSM_CSP_CreatePassThroughContext", crtn);
97		return crtn;
98	}
99
100	/* now it's up to the CSP */
101	crtn = CSSM_CSP_PassThrough(ccHand,
102		CSSM_APPLECSP_KEYDIGEST,
103		NULL,
104		(void **)&dp);
105	if(crtn) {
106		cssmPerror("CSSM_CSP_PassThrough(KEYDIGEST)", crtn);
107	}
108	else {
109		*hashData = dp;
110		crtn = CSSM_OK;
111	}
112	CSSM_DeleteContext(ccHand);
113	return crtn;
114}
115
116/*
117 * Here's a tricky one. Given a private key, obtain the correspoding public key.
118 * This uses a private key blob format that's used internally in the CSP
119 * to generate key digests.
120 */
121
122/*
123 * this magic const copied from BinaryKey.h
124 */
125#define CSSM_KEYBLOB_RAW_FORMAT_DIGEST	\
126	(CSSM_KEYBLOB_RAW_FORMAT_VENDOR_DEFINED + 0x12345)
127
128static CSSM_RETURN pubKeyFromPrivKey(
129	CSSM_CSP_HANDLE cspHand,
130	const CSSM_KEY *privKey,			// assumed to be raw format
131	CSSM_KEY *pubKey)
132{
133	/* first convert to reference key */
134	CSSM_KEY refKey;
135	CSSM_RETURN crtn;
136	crtn = cspRawKeyToRef(cspHand, privKey, &refKey);
137	if(crtn) {
138		return crtn;
139	}
140
141	/* now a NULL wrap with the magic format attribute */
142	CSSM_CC_HANDLE ccHand;
143	CSSM_ACCESS_CREDENTIALS	creds;
144	CSSM_DATA descData = {0, 0};
145
146	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
147			CSSM_ALGID_NONE,
148			CSSM_ALGMODE_NONE,
149			NULL,			// passPhrase,
150			NULL,			// key
151			NULL,			// initVector,
152			CSSM_PADDING_NONE,
153			NULL,			// Reserved
154			&ccHand);
155	if(crtn) {
156		cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
157		return crtn;
158	}
159	crtn = AddContextAttribute(ccHand,
160		/*
161		 * The output of the WrapKey is a private key as far as the CSP is
162		 * concerned, at the level that this attribute is used anyway....
163		 */
164		CSSM_ATTRIBUTE_PRIVATE_KEY_FORMAT,
165		sizeof(uint32),
166		CAT_Uint32,
167		NULL,
168		CSSM_KEYBLOB_RAW_FORMAT_DIGEST);
169	if(crtn) {
170		cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
171		goto errOut;
172	}
173	memset(pubKey, 0, sizeof(CSSM_KEY));
174	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
175	crtn = CSSM_WrapKey(ccHand,
176		&creds,
177		&refKey,
178		&descData,
179		pubKey);
180	if(crtn) {
181		cssmPerror("CSSM_WrapKey", crtn);
182		goto errOut;
183	}
184
185	/* now: presto chango - don't do this at home! */
186	pubKey->KeyHeader.KeyClass = CSSM_KEYCLASS_PUBLIC_KEY;
187errOut:
188	CSSM_FreeKey(cspHand, NULL, &refKey, CSSM_FALSE);
189	CSSM_DeleteContext(ccHand);
190	return crtn;
191}
192
193/*
194 * Import a key into a DLDB.
195 */
196static CSSM_RETURN importToDlDb(
197	CSSM_CSP_HANDLE	cspHand,
198	CSSM_DL_DB_HANDLE_PTR dlDbHand,
199	const CSSM_KEY *rawPubKey,
200	CSSM_DATA_PTR labelData,
201	CSSM_KEY_PTR importedKey)
202{
203	CSSM_CC_HANDLE			ccHand = 0;
204	CSSM_RETURN				crtn;
205	uint32					keyAttr;
206	CSSM_ACCESS_CREDENTIALS	creds;
207	CSSM_CONTEXT_ATTRIBUTE	newAttr;
208	CSSM_DATA				descData = {0, 0};
209
210	memset(importedKey, 0, sizeof(CSSM_KEY));
211	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
212	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
213				CSSM_ALGID_NONE,
214				CSSM_ALGMODE_NONE,
215				&creds,
216				NULL,			// unwrappingKey
217				NULL,			// initVector
218				CSSM_PADDING_NONE,
219				0,				// Params
220				&ccHand);
221	if(crtn) {
222		cssmPerror("CSSM_CSP_CreateSymmetricContext", crtn);
223		return crtn;
224	}
225	keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT;
226
227	/* Add DLDB to context */
228	newAttr.AttributeType     = CSSM_ATTRIBUTE_DL_DB_HANDLE;
229	newAttr.AttributeLength   = sizeof(CSSM_ATTRIBUTE_DL_DB_HANDLE);
230	newAttr.Attribute.Data    = (CSSM_DATA_PTR)dlDbHand;
231	crtn = CSSM_UpdateContextAttributes(ccHand, 1, &newAttr);
232	if(crtn) {
233		cssmPerror("CSSM_UpdateContextAttributes", crtn);
234		goto errOut;
235	}
236
237	/* import */
238	crtn = CSSM_UnwrapKey(ccHand,
239		NULL,				// PublicKey
240		rawPubKey,
241		CSSM_KEYUSE_ANY,
242		keyAttr,
243		labelData,
244		NULL,				// CredAndAclEntry
245		importedKey,
246		&descData);			// required
247	if(crtn) {
248		cssmPerror("CSSM_UnwrapKey", crtn);
249	}
250errOut:
251	if(ccHand) {
252		CSSM_DeleteContext(ccHand);
253	}
254	return crtn;
255}
256
257/*
258 * Free memory via specified plugin's app-level allocator
259 */
260void impExpFreeCssmMemory(
261	CSSM_HANDLE		hand,
262	void 			*p)
263{
264	CSSM_API_MEMORY_FUNCS memFuncs;
265	CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
266	if(crtn) {
267		return;
268	}
269	memFuncs.free_func(p, memFuncs.AllocRef);
270}
271
272/*
273 * Key attrribute names and values.
274 *
275 * This is where the public key hash goes.
276 */
277#define SEC_KEY_HASH_ATTR_NAME			"Label"
278
279/*
280 * This is where the publicly visible name goes.
281 */
282#define SEC_KEY_PRINT_NAME_ATTR_NAME	"PrintName"
283
284/*
285 * Look up public key by label
286 * Set label to new specified label (SHA1 digest)
287 * Set print name to new specified user-visible name
288 */
289static CSSM_RETURN setPubKeyLabel(
290	CSSM_CSP_HANDLE 	cspHand,		// where the key lives
291	CSSM_DL_DB_HANDLE 	*dlDbHand,		// ditto
292	const CSSM_DATA		*existKeyLabel,	// existing label, a random string, for lookup
293	const CSSM_DATA		*keyDigest,		// SHA1 digest, the new label
294	const CSSM_DATA		*newPrintName)	// new user-visible name
295{
296	CSSM_QUERY						query;
297	CSSM_SELECTION_PREDICATE		predicate;
298	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
299	CSSM_RETURN						crtn;
300	CSSM_HANDLE						resultHand = 0;
301
302	/*
303	 * Look up the key in the DL.
304	 */
305	query.RecordType = CSSM_DL_DB_RECORD_PUBLIC_KEY;
306	query.Conjunctive = CSSM_DB_NONE;
307	query.NumSelectionPredicates = 1;
308	predicate.DbOperator = CSSM_DB_EQUAL;
309
310	predicate.Attribute.Info.AttributeNameFormat =
311		CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
312	predicate.Attribute.Info.Label.AttributeName = (char *)"Label";
313	predicate.Attribute.Info.AttributeFormat =
314		CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
315	/* hope this cast is OK */
316	predicate.Attribute.Value = (CSSM_DATA_PTR)existKeyLabel;
317	query.SelectionPredicate = &predicate;
318
319	query.QueryLimits.TimeLimit = 0;	// FIXME - meaningful?
320	query.QueryLimits.SizeLimit = 1;	// FIXME - meaningful?
321	query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA;	// FIXME - used?
322
323	/* build Record attribute with two attrs */
324	CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
325	CSSM_DB_ATTRIBUTE_DATA attr[2];
326
327	attr[0].Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
328	attr[0].Info.Label.AttributeName = (char *)SEC_KEY_HASH_ATTR_NAME;
329	attr[0].Info.AttributeFormat     = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
330	attr[1].Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
331	attr[1].Info.Label.AttributeName = (char *)SEC_KEY_PRINT_NAME_ATTR_NAME;
332	attr[1].Info.AttributeFormat     = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
333
334	recordAttrs.DataRecordType		 = CSSM_DL_DB_RECORD_PUBLIC_KEY;
335	recordAttrs.NumberOfAttributes   = 2;
336	recordAttrs.AttributeData        = attr;
337
338	crtn = CSSM_DL_DataGetFirst(*dlDbHand,
339		&query,
340		&resultHand,
341		&recordAttrs,
342		NULL,			// theData
343		&record);
344	/* abort only on success */
345	if(crtn != CSSM_OK) {
346		cssmPerror("CSSM_DL_DataGetFirst", crtn);
347		goto errOut;
348	}
349
350	/*
351	 * Update existing attr data.
352	 * NOTE: the module which allocated this attribute data - a DL -
353	 * was loaded and attached by the keychain layer, not by us. Thus
354	 * we can't use the memory allocator functions *we* used when
355	 * attaching to the CSP - we have to use the ones
356	 * which the client registered with the DL.
357	 */
358	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[0].Value->Data);
359	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[0].Value);
360	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[1].Value->Data);
361	impExpFreeCssmMemory(dlDbHand->DLHandle, attr[1].Value);
362	attr[0].Value = const_cast<CSSM_DATA *>(keyDigest);
363	attr[1].Value = const_cast<CSSM_DATA *>(newPrintName);
364
365	crtn = CSSM_DL_DataModify(*dlDbHand,
366			CSSM_DL_DB_RECORD_PUBLIC_KEY,
367			record,
368			&recordAttrs,
369            NULL,				// DataToBeModified
370			CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
371	if(crtn) {
372		cssmPerror("CSSM_DL_DataModify", crtn);
373	}
374errOut:
375	/* free resources */
376	if(resultHand) {
377		CSSM_DL_DataAbortQuery(*dlDbHand, resultHand);
378	}
379	if(record) {
380		CSSM_DL_FreeUniqueRecord(*dlDbHand, record);
381	}
382	return crtn;
383}
384
385#define SHA1_LABEL_LEN	20
386#define IMPORTED_KEY_NAME "Imported Public Key"
387
388/*
389 * Import a public key into a keychain, with proper Label attribute setting.
390 * A workaround for Radar 4191851.
391 */
392static int pubKeyImport(
393	const char *kcName,
394	const CSSM_KEY *pubKey,
395	CSSM_CSP_HANDLE rawCspHand)		/* raw CSP handle for calculating digest */
396{
397	CSSM_CSP_HANDLE cspHand;
398	CSSM_DL_DB_HANDLE dlDbHand;
399	OSStatus ortn;
400	CSSM_RETURN crtn;
401	SecKeychainRef kcRef = NULL;
402	int ourRtn = 0;
403	CSSM_DATA_PTR digest = NULL;
404	CSSM_KEY importedKey;
405	CSSM_DATA newPrintName =
406		{ (uint32)strlen(IMPORTED_KEY_NAME), (uint8 *)IMPORTED_KEY_NAME};
407
408	/* NULL unwrap stuff */
409	uint8 tempLabel[SHA1_LABEL_LEN];
410	CSSM_DATA labelData = {SHA1_LABEL_LEN, tempLabel};
411
412	ortn = SecKeychainOpen(kcName, &kcRef);
413	if(ortn) {
414		cssmPerror("SecKeychainOpen", ortn);
415		return -1;
416	}
417	/* subsequent errors to errOut: */
418
419	/* Get CSSM handles */
420	ortn = SecKeychainGetCSPHandle(kcRef, &cspHand);
421	if(ortn) {
422		cssmPerror("SecKeychainGetCSPHandle", ortn);
423		ourRtn = -1;
424		goto errOut;
425	}
426	ortn = SecKeychainGetDLDBHandle(kcRef, &dlDbHand);
427	if(ortn) {
428		cssmPerror("SecKeychainGetCSPHandle", ortn);
429		ourRtn = -1;
430		goto errOut;
431	}
432
433	/* public key hash from raw CSP */
434	crtn = keyDigest(rawCspHand, pubKey, &digest);
435	if(crtn) {
436		ourRtn = -1;
437		goto errOut;
438	}
439
440	/* random label for initial storage and later retrieval */
441	appGetRandomBytes(tempLabel, SHA1_LABEL_LEN);
442
443	/* import the key into the keychain's DLDB */
444	memset(&importedKey, 0, sizeof(CSSM_KEY));
445	crtn = importToDlDb(cspHand, &dlDbHand, pubKey, &labelData, &importedKey);
446	if(crtn) {
447		ourRtn = -1;
448		goto errOut;
449	}
450
451	/* don't need this */
452	CSSM_FreeKey(cspHand, NULL, &importedKey, CSSM_FALSE);
453
454	/* update the label and printName attributes */
455	crtn = setPubKeyLabel(cspHand, &dlDbHand, &labelData, digest, &newPrintName);
456	if(crtn) {
457		ourRtn = -1;
458	}
459errOut:
460	CFRelease(kcRef);
461	if(digest) {
462		APP_FREE(digest->Data);
463		APP_FREE(digest);
464	}
465	return ourRtn;
466}
467
468int main(int argc, char **argv)
469{
470	char *privKeyFile = NULL;
471	char *pubKeyFile = NULL;
472	char *certFile = NULL;
473	char *outFile = NULL;
474	bool printDigest = false;
475	CSSM_KEYBLOB_FORMAT keyForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
476	char *kcName = NULL;
477
478	if(argc < 3) {
479		usage(argv);
480	}
481	extern char *optarg;
482	int arg;
483	while ((arg = getopt(argc, argv, "k:b:c:do:f:K:h")) != -1) {
484		switch (arg) {
485			case 'k':
486				privKeyFile = optarg;
487				break;
488			case 'b':
489				pubKeyFile = optarg;
490				break;
491			case 'c':
492				certFile = optarg;
493				break;
494			case 'd':
495				printDigest = true;
496				break;
497			case 'o':
498				outFile = optarg;
499				break;
500			case 'f':
501				if(!strcmp("pkcs1", optarg)) {
502					keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS1;
503				}
504				else if(!strcmp("pkcs8", optarg)) {
505					keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS8;
506				}
507				else if(!strcmp("x509", optarg)) {
508					keyForm = CSSM_KEYBLOB_RAW_FORMAT_X509;
509				}
510				break;
511			case 'K':
512				kcName = optarg;
513				break;
514			case 'h':
515				usage(argv);
516		}
517	}
518	if(optind != argc) {
519		usage(argv);
520	}
521
522	CSSM_DATA privKeyBlob = {0, NULL};
523	CSSM_DATA pubKeyBlob = {0, NULL};
524	CSSM_KEY thePrivKey;			// constructed
525	CSSM_KEY thePubKey;				// null-wrapped
526	CSSM_KEY_PTR pubKey = NULL;
527	CSSM_KEY_PTR privKey = NULL;
528	CSSM_RETURN crtn;
529	CSSM_CL_HANDLE clHand = 0;
530	CSSM_CSP_HANDLE cspHand = cuCspStartup(CSSM_TRUE);
531
532	/* gather input */
533	if(privKeyFile) {
534		/* key blob from a file ==> a private CSSM_KEY */
535
536		if(pubKeyFile || certFile) {
537			printf("****Specify exactly one of {cert_file, priv_key_file, "
538					"pub_key_file}.\n");
539			exit(1);
540		}
541		unsigned len;
542		if(readFile(privKeyFile, &privKeyBlob.Data, &len)) {
543			printf("***Error reading private key from %s. Aborting.\n", privKeyFile);
544			exit(1);
545		}
546		privKeyBlob.Length = len;
547		if(keyForm == CSSM_KEYBLOB_RAW_FORMAT_NONE) {
548			/* default for private keys */
549			keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS8;
550		}
551		crtn = inferCssmKey(privKeyBlob, true, keyForm, cspHand, thePrivKey);
552		if(crtn) {
553			goto errOut;
554		}
555		privKey = &thePrivKey;
556	}
557	if(pubKeyFile) {
558		/* key blob from a file ==> a public CSSM_KEY */
559
560		if(privKeyFile || certFile) {
561			printf("****Specify exactly one of {cert_file, priv_key_file, "
562					"pub_key_file}.\n");
563			exit(1);
564		}
565
566		unsigned len;
567		if(readFile(pubKeyFile, &pubKeyBlob.Data, &len)) {
568			printf("***Error reading public key from %s. Aborting.\n", pubKeyFile);
569			exit(1);
570		}
571		pubKeyBlob.Length = len;
572		if(keyForm == CSSM_KEYBLOB_RAW_FORMAT_NONE) {
573			/* default for public keys */
574			keyForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS1;
575		}
576		crtn = inferCssmKey(pubKeyBlob, false, keyForm, cspHand, thePubKey);
577		if(crtn) {
578			goto errOut;
579		}
580		pubKey = &thePubKey;
581	}
582	if(certFile) {
583		/* cert from a file ==> a public CSSM_KEY */
584
585		if(privKeyFile || pubKeyFile) {
586			printf("****Specify exactly one of {cert_file, priv_key_file, "
587					"pub_key_file}.\n");
588			exit(1);
589		}
590
591		CSSM_DATA certData = {0, NULL};
592		unsigned len;
593		if(readFile(certFile, &certData.Data, &len)) {
594			printf("***Error reading cert from %s. Aborting.\n", certFile);
595			exit(1);
596		}
597		certData.Length = len;
598
599		/* Extract public key - that's what we will be using later */
600		clHand = cuClStartup();
601		crtn = CSSM_CL_CertGetKeyInfo(clHand, &certData, &pubKey);
602		if(crtn) {
603			cssmPerror("CSSM_CL_CertGetKeyInfo", crtn);
604			goto errOut;
605		}
606	}
607
608	/* now do something useful */
609	if(printDigest) {
610		CSSM_KEY_PTR theKey = privKey;
611		if(theKey == NULL) {
612			/* maybe we got public key from a cert */
613			theKey = pubKey;
614		}
615		if(theKey == NULL) {
616			printf("***Can't calculate digest because I don't have a key or a clue.\n");
617			goto errOut;
618		}
619		CSSM_DATA_PTR dig = NULL;
620		crtn = keyDigest(cspHand, theKey, &dig);
621		if(crtn) {
622			printf("Sorry, can't get the digest for this key.\n");
623			goto errOut;
624		}
625		if((dig == NULL) || (dig->Length == 0)) {
626			printf("Screwup calculating digest.\n");
627			goto errOut;
628		}
629		printf("Key Digest:\n");
630		for(unsigned dex=0; dex<dig->Length; dex++) {
631			printf("%02X ", dig->Data[dex]);
632		}
633		printf("\n");
634		APP_FREE(dig->Data);
635		APP_FREE(dig);
636	}
637
638	if(outFile || kcName) {
639		/* get a public key if we don't already have one */
640		if(pubKey == NULL) {
641			if(privKey == NULL) {
642				printf("***PubKey file name specified but no privKey or cert. "
643						"Aborting.\n");
644				goto errOut;
645			}
646			crtn = pubKeyFromPrivKey(cspHand, privKey, &thePubKey);
647			if(crtn) {
648				goto errOut;
649			}
650			pubKey = &thePubKey;
651		}
652	}
653	if(outFile) {
654		if(writeFile(outFile, pubKey->KeyData.Data, pubKey->KeyData.Length)) {
655			printf("***Error writing to %s.\n", outFile);
656		}
657		else {
658			printf("...%lu bytes written to %s.\n", pubKey->KeyData.Length, outFile);
659		}
660	}
661	if(kcName) {
662		if(pubKeyImport(kcName, pubKey, cspHand) == 0) {
663			printf("....public key %s imported to %s\n", pubKeyFile, kcName);
664		}
665		else {
666			printf("***Error importing public key %s to %s\n", pubKeyFile, kcName);
667		}
668	}
669errOut:
670	/* clean up here if you must */
671	return 0;
672}
673