1/*
2 * kcExport.cpp - export keychain items using SecKeychainItemExport
3 */
4
5#include <Security/Security.h>
6#include <Security/SecImportExport.h>
7#include <security_cdsa_utils/cuFileIo.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12#include <utilLib/common.h>
13
14static void usage(char **argv)
15{
16    printf("Usage: %s keychain [option ...]\n", argv[0]);
17	printf("Options:\n");
18	printf("   -t <type>       itemType = certs|allKeys|pubKeys|privKeys|identities|all\n");
19	printf("                   ...default itemType=all\n");
20	printf("   -f <format>     format = openssl|openssh1|openssh2|bsafe|pkcs7|pkcs8|pkcs12|pemseq\n"
21	       "                            ...default itemType is pemseq for aggregate, openssl\n"
22		   "                            for single \n");
23	printf("   -p              PEM encode\n");
24	printf("   -w              Private keys are wrapped\n");
25	printf("   -o outFileName  (default is stdout)\n");
26	printf("   -z passphrase   (for PKCS12 and wrapped keys only)\n");
27	printf("   -Z              Use secure passphrase\n");
28	printf("   -q              Quiet\n");
29	printf("   -h              help\n");
30	exit(1);
31}
32
33typedef enum {
34	IS_Certs,
35	IS_AllKeys,
36	IS_PubKeys,
37	IS_PrivKeys,
38	IS_Identities,
39	IS_All
40} ItemSpec;
41
42/*
43 * Add all itmes of specified class from a keychain to an array.
44 * Item class are things like kSecCertificateItemClass, and
45 * CSSM_DL_DB_RECORD_PRIVATE_KEY. Identities are searched separately.
46 */
47static OSStatus addKcItems(
48	SecKeychainRef kcRef,
49	SecItemClass itemClass,		// kSecCertificateItemClass
50	CFMutableArrayRef outArray)
51{
52	OSStatus ortn;
53	SecKeychainSearchRef srchRef;
54
55	ortn = SecKeychainSearchCreateFromAttributes(kcRef,
56		itemClass,
57		NULL,		// no attrs
58		&srchRef);
59	if(ortn) {
60		cssmPerror("SecKeychainSearchCreateFromAttributes", ortn);
61		return ortn;
62	}
63	for(;;) {
64		SecKeychainItemRef itemRef;
65		ortn = SecKeychainSearchCopyNext(srchRef, &itemRef);
66		if(ortn) {
67			if(ortn == errSecItemNotFound) {
68				/* normal search end */
69				ortn = noErr;
70			}
71			else {
72				cssmPerror("SecKeychainSearchCopyNext", ortn);
73			}
74			break;
75		}
76		CFArrayAppendValue(outArray, itemRef);
77		CFRelease(itemRef);		// array owns it
78	}
79	CFRelease(srchRef);
80	return ortn;
81}
82
83/*
84 * Add all SecIdentityRefs from a keychain into an array.
85 */
86static OSStatus addIdentities(
87	SecKeychainRef kcRef,
88	CFMutableArrayRef outArray)
89{
90	/* Search for all identities */
91	SecIdentitySearchRef srchRef;
92	OSStatus ortn = SecIdentitySearchCreate(kcRef,
93		0,				// keyUsage - any
94		&srchRef);
95	if(ortn) {
96		cssmPerror("SecIdentitySearchCreate", ortn);
97		return ortn;
98	}
99
100	do {
101		SecIdentityRef identity;
102		ortn = SecIdentitySearchCopyNext(srchRef, &identity);
103		if(ortn) {
104			if(ortn == errSecItemNotFound) {
105				/* normal search end */
106				ortn = noErr;
107			}
108			else {
109				cssmPerror("SecIdentitySearchCopyNext", ortn);
110			}
111			break;
112		}
113		CFArrayAppendValue(outArray, identity);
114
115		/* the array has the retain count we need */
116		CFRelease(identity);
117	} while(ortn == noErr);
118	CFRelease(srchRef);
119	return ortn;
120}
121
122int main(int argc, char **argv)
123{
124	if(argc < 4) {
125		usage(argv);
126	}
127
128	const char *kcName = argv[1];
129	SecKeychainRef kcRef = NULL;
130	OSStatus ortn = SecKeychainOpen(kcName, &kcRef);
131	if(ortn) {
132		cssmPerror("SecKeychainOpen", ortn);
133		exit(1);
134	}
135
136	/* user specified options */
137	ItemSpec itemSpec = IS_All;		// default
138	SecExternalFormat exportForm = kSecFormatUnknown;
139	bool pemEncode = false;
140	const char *outFile = NULL;
141	CFStringRef passphrase = NULL;
142	bool securePassphrase = false;
143	bool quiet = false;
144	bool wrapPrivKeys = false;
145
146	extern int optind;
147	extern char *optarg;
148	int arg;
149	optind = 2;
150
151	while ((arg = getopt(argc, argv, "t:f:po:z:Zhqw")) != -1) {
152		switch (arg) {
153			case 't':
154				if(!strcmp("certs", optarg)) {
155					itemSpec = IS_Certs;
156				}
157				else if(!strcmp("allKeys", optarg)) {
158					itemSpec = IS_AllKeys;
159				}
160				else if(!strcmp("pubKeys", optarg)) {
161					itemSpec = IS_PubKeys;
162				}
163				else if(!strcmp("privKeys", optarg)) {
164					itemSpec = IS_PrivKeys;
165				}
166				else if(!strcmp("identities", optarg)) {
167					itemSpec = IS_Identities;
168				}
169				else if(!strcmp("all", optarg)) {
170					itemSpec = IS_All;
171				}
172				else {
173					usage(argv);
174				}
175				break;
176			case 'f':
177				if(!strcmp("openssl", optarg)) {
178					exportForm = kSecFormatOpenSSL;
179				}
180				else if(!strcmp("openssh1", optarg)) {
181					exportForm = kSecFormatSSH;
182				}
183				else if(!strcmp("openssh2", optarg)) {
184					exportForm = kSecFormatSSHv2;
185				}
186				else if(!strcmp("bsafe", optarg)) {
187					exportForm = kSecFormatBSAFE;
188				}
189				else if(!strcmp("pkcs7", optarg)) {
190					exportForm = kSecFormatPKCS7;
191				}
192				else if(!strcmp("pkcs8", optarg)) {
193					exportForm = kSecFormatWrappedPKCS8;
194				}
195				else if(!strcmp("pkcs12", optarg)) {
196					exportForm = kSecFormatPKCS12;
197				}
198				else if(!strcmp("pemseq", optarg)) {
199					exportForm = kSecFormatPEMSequence;
200				}
201				else {
202					usage(argv);
203				}
204				break;
205			case 'p':
206				pemEncode = true;
207				break;
208			case 'o':
209				outFile = optarg;
210				break;
211			case 'z':
212				passphrase = CFStringCreateWithCString(NULL, optarg,
213					kCFStringEncodingASCII);
214				break;
215			case 'Z':
216				securePassphrase = true;
217				break;
218			case 'w':
219				wrapPrivKeys = true;
220				break;
221			case 'q':
222				quiet = true;
223				break;
224			case '?':
225			case 'h':
226				default:
227					usage(argv);
228		}
229
230	}
231	if(optind != argc) {
232		/* getopt does not return '?' */
233		usage(argv);
234	}
235	if(wrapPrivKeys) {
236		switch(exportForm) {
237			case kSecFormatOpenSSL:
238			case kSecFormatUnknown:		// i.e., use default
239				exportForm = kSecFormatWrappedOpenSSL;
240				break;
241			case kSecFormatSSH:
242				exportForm = kSecFormatWrappedSSH;
243				break;
244			case kSecFormatSSHv2:
245				/* there is no wrappedSSHv2 */
246				exportForm = kSecFormatWrappedOpenSSL;
247				break;
248			case kSecFormatWrappedPKCS8:
249				/* proceed */
250				break;
251			default:
252				printf("Don't know how to wrap in specified format/type.\n");
253				exit(1);
254		}
255	}
256
257	/* gather items */
258	CFMutableArrayRef exportItems = CFArrayCreateMutable(NULL, 0,
259		&kCFTypeArrayCallBacks);
260	switch(itemSpec) {
261		case IS_Certs:
262			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems);
263			if(ortn) {
264				exit(1);
265			}
266			break;
267
268		case IS_PrivKeys:
269			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems);
270			if(ortn) {
271				exit(1);
272			}
273			break;
274
275		case IS_PubKeys:
276			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems);
277			if(ortn) {
278				exit(1);
279			}
280			break;
281
282		case IS_AllKeys:
283			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems);
284			if(ortn) {
285				exit(1);
286			}
287			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems);
288			if(ortn) {
289				exit(1);
290			}
291			break;
292
293		case IS_All:
294			/* No public keys here - PKCS12 doesn't support them */
295			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems);
296			if(ortn) {
297				exit(1);
298			}
299			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems);
300			if(ortn) {
301				exit(1);
302			}
303			break;
304
305		case IS_Identities:
306			ortn = addIdentities(kcRef, exportItems);
307			if(ortn) {
308				exit(1);
309			}
310			break;
311		default:
312			printf("Huh? Bogus itemSpec!\n");
313			exit(1);
314	}
315
316	CFIndex numItems = CFArrayGetCount(exportItems);
317
318	if(exportForm == kSecFormatUnknown) {
319		/* Use default export format per set of items */
320		if(numItems > 1) {
321			exportForm = kSecFormatPEMSequence;
322		}
323		else {
324			exportForm = kSecFormatOpenSSL;
325		}
326	}
327	uint32 expFlags = 0;		// SecItemImportExportFlags
328	if(pemEncode) {
329		expFlags |= kSecItemPemArmour;
330	}
331
332	/* optional key related arguments */
333	SecKeyImportExportParameters keyParams;
334	SecKeyImportExportParameters *keyParamPtr = NULL;
335	if((passphrase != NULL) || securePassphrase) {
336		memset(&keyParams, 0, sizeof(keyParams));
337		keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
338		if(securePassphrase) {
339			/* give this precedence */
340			keyParams.flags |= kSecKeySecurePassphrase;
341		}
342		else {
343			keyParams.passphrase = passphrase;		// may be NULL
344		}
345		keyParamPtr = &keyParams;
346	}
347
348	/* GO */
349	CFDataRef outData = NULL;
350	ortn = SecKeychainItemExport(exportItems, exportForm, expFlags, keyParamPtr,
351		&outData);
352	if(ortn) {
353		cssmPerror("SecKeychainItemExport", ortn);
354		exit(1);
355	}
356
357	unsigned len = CFDataGetLength(outData);
358	if(outFile) {
359		int rtn = writeFile(outFile, CFDataGetBytePtr(outData), len);
360		if(rtn == 0) {
361			if(!quiet) {
362				printf("...%u bytes written to %s\n", len, outFile);
363			}
364		}
365		else {
366			printf("***Error writing to %s\n", outFile);
367		}
368	}
369	else {
370		int irtn = write(STDOUT_FILENO, CFDataGetBytePtr(outData), len);
371		if(irtn != (int)len) {
372			perror("write");
373		}
374	}
375	if(!quiet) {
376		fprintf(stderr, "\n%u items exported.\n", (unsigned)numItems);
377	}
378	if(exportItems) {
379		/* FIXME this in conjunction with the release of the KC crashes */
380		CFRelease(exportItems);
381	}
382	if(passphrase) {
383		CFRelease(passphrase);
384	}
385	if(outData) {
386		CFRelease(outData);
387	}
388	if(kcRef) {
389		CFRelease(kcRef);
390	}
391	return 0;
392}
393