1/*
2 * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * keychain_export.c
24 */
25
26#include "keychain_export.h"
27#include "keychain_utilities.h"
28#include "security.h"
29
30#include <errno.h>
31#include <string.h>
32#include <unistd.h>
33#include <Security/SecImportExport.h>
34#include <Security/SecKeychainItem.h>
35#include <Security/SecKeychainSearch.h>
36#include <Security/SecIdentitySearch.h>
37#include <Security/SecKey.h>
38#include <Security/SecCertificate.h>
39#include <security_cdsa_utils/cuFileIo.h>
40#include <CoreFoundation/CoreFoundation.h>
41#include <stdio.h>
42
43typedef enum {
44	IS_Certs,
45	IS_AllKeys,
46	IS_PubKeys,
47	IS_PrivKeys,
48	IS_Identities,
49	IS_All
50} ItemSpec;
51
52/*
53 * Add all itmes of specified class from a keychain to an array.
54 * Item class are things like kSecCertificateItemClass, and
55 * CSSM_DL_DB_RECORD_PRIVATE_KEY. Identities are searched separately.
56 */
57static OSStatus addKcItems(
58	SecKeychainRef kcRef,
59	SecItemClass itemClass,		// kSecCertificateItemClass
60	CFMutableArrayRef outArray,
61	unsigned *numItems)			// UPDATED on return
62{
63	OSStatus ortn;
64	SecKeychainSearchRef srchRef;
65
66	ortn = SecKeychainSearchCreateFromAttributes(kcRef,
67		itemClass,
68		NULL,		// no attrs
69		&srchRef);
70	if(ortn) {
71		sec_perror("SecKeychainSearchCreateFromAttributes", ortn);
72		return ortn;
73	}
74	for(;;) {
75		SecKeychainItemRef itemRef;
76		ortn = SecKeychainSearchCopyNext(srchRef, &itemRef);
77		if(ortn) {
78			if(ortn == errSecItemNotFound) {
79				/* normal search end */
80				ortn = noErr;
81			}
82			else {
83				sec_perror("SecIdentitySearchCopyNext", ortn);
84			}
85			break;
86		}
87		CFArrayAppendValue(outArray, itemRef);
88		CFRelease(itemRef);		// array owns the item
89		(*numItems)++;
90	}
91	CFRelease(srchRef);
92	return ortn;
93}
94
95/*
96 * Add all SecIdentityRefs from a keychain into an array.
97 */
98static OSStatus addIdentities(
99	SecKeychainRef kcRef,
100	CFMutableArrayRef outArray,
101	unsigned *numItems)			// UPDATED on return
102{
103	/* Search for all identities */
104	SecIdentitySearchRef srchRef;
105	OSStatus ortn = SecIdentitySearchCreate(kcRef,
106		0,				// keyUsage - any
107		&srchRef);
108	if(ortn) {
109		sec_perror("SecIdentitySearchCreate", ortn);
110		return ortn;
111	}
112
113	do {
114		SecIdentityRef identity;
115		ortn = SecIdentitySearchCopyNext(srchRef, &identity);
116		if(ortn) {
117			if(ortn == errSecItemNotFound) {
118				/* normal search end */
119				ortn = noErr;
120			}
121			else {
122				sec_perror("SecIdentitySearchCopyNext", ortn);
123			}
124			break;
125		}
126		CFArrayAppendValue(outArray, identity);
127
128		/* the array has the retain count we need */
129		CFRelease(identity);
130		(*numItems)++;
131	} while(ortn == noErr);
132	CFRelease(srchRef);
133	return ortn;
134}
135
136static int do_keychain_export(
137	SecKeychainRef		kcRef,
138	SecExternalFormat   externFormat,
139	ItemSpec			itemSpec,
140	const char			*passphrase,
141	int					doPem,
142	const char			*fileName)
143{
144	int result = 0;
145	CFIndex numItems;
146	unsigned numPrivKeys = 0;
147	unsigned numPubKeys = 0;
148	unsigned numCerts = 0;
149	unsigned numIdents = 0;
150	OSStatus ortn;
151	uint32 expFlags = 0;		// SecItemImportExportFlags
152	SecKeyImportExportParameters keyParams;
153	CFStringRef	passStr = NULL;
154	CFDataRef outData = NULL;
155	unsigned len;
156
157	/* gather items */
158	CFMutableArrayRef exportItems = CFArrayCreateMutable(NULL, 0,
159		&kCFTypeArrayCallBacks);
160	switch(itemSpec) {
161		case IS_Certs:
162			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems, &numCerts);
163			if(ortn) {
164				result = 1;
165				goto loser;
166			}
167			break;
168
169		case IS_PrivKeys:
170			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems,
171				&numPrivKeys);
172			if(ortn) {
173				result = 1;
174				goto loser;
175			}
176			break;
177
178		case IS_PubKeys:
179			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems,
180				&numPubKeys);
181			if(ortn) {
182				result = 1;
183				goto loser;
184			}
185			break;
186
187		case IS_AllKeys:
188			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems,
189				&numPrivKeys);
190			if(ortn) {
191				result = 1;
192				goto loser;
193			}
194			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems,
195				&numPubKeys);
196			if(ortn) {
197				result = 1;
198				goto loser;
199			}
200			break;
201
202		case IS_All:
203			/* No public keys here - PKCS12 doesn't support them */
204			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems, &numCerts);
205			if(ortn) {
206				result = 1;
207				goto loser;
208			}
209			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems,
210				&numPrivKeys);
211			if(ortn) {
212				result = 1;
213				goto loser;
214			}
215			break;
216
217		case IS_Identities:
218			ortn = addIdentities(kcRef, exportItems, &numIdents);
219			if(ortn) {
220				result = 1;
221				goto loser;
222			}
223			if(numIdents) {
224				numPrivKeys += numIdents;
225				numCerts    += numIdents;
226			}
227			break;
228		default:
229			sec_error("Internal error parsing item_spec");
230			result = 1;
231			goto loser;
232	}
233
234	numItems = CFArrayGetCount(exportItems);
235	if(externFormat == kSecFormatUnknown) {
236		/* Use default export format per set of items */
237		if(numItems > 1) {
238			externFormat = kSecFormatPEMSequence;
239		}
240		else if(numCerts) {
241			externFormat = kSecFormatX509Cert;
242		}
243		else {
244			externFormat = kSecFormatOpenSSL;
245		}
246	}
247	if(doPem) {
248		expFlags |= kSecItemPemArmour;
249	}
250
251	/*
252	 * Key related arguments, ignored if we're not exporting keys.
253	 * Always specify some kind of passphrase - default is secure passkey.
254	 */
255	memset(&keyParams, 0, sizeof(keyParams));
256	keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
257	if(passphrase != NULL) {
258		passStr = CFStringCreateWithCString(NULL, passphrase, kCFStringEncodingASCII);
259		keyParams.passphrase = passStr;
260	}
261	else {
262		keyParams.flags = kSecKeySecurePassphrase;
263	}
264
265	/* Go */
266	ortn = SecKeychainItemExport(exportItems, externFormat, expFlags, &keyParams,
267		&outData);
268	if(ortn) {
269		sec_perror("SecKeychainItemExport", ortn);
270		result = 1;
271		goto loser;
272	}
273
274	len = CFDataGetLength(outData);
275	if(fileName) {
276		int rtn = writeFile(fileName, CFDataGetBytePtr(outData), len);
277		if(rtn == 0) {
278			if(!do_quiet) {
279				fprintf(stderr, "...%u bytes written to %s\n", len, fileName);
280			}
281		}
282		else {
283			sec_error("Error writing to %s: %s", fileName, strerror(errno));
284			result = 1;
285		}
286	}
287	else {
288		int irtn = write(STDOUT_FILENO, CFDataGetBytePtr(outData), len);
289		if(irtn != (int)len) {
290			perror("write");
291		}
292	}
293loser:
294	if(exportItems) {
295		CFRelease(exportItems);
296	}
297	if(passStr) {
298		CFRelease(passStr);
299	}
300	if(outData) {
301		CFRelease(outData);
302	}
303	return result;
304}
305
306int
307keychain_export(int argc, char * const *argv)
308{
309	int ch, result = 0;
310
311	char *outFile = NULL;
312	char *kcName = NULL;
313	SecKeychainRef kcRef = NULL;
314	SecExternalFormat externFormat = kSecFormatUnknown;
315	ItemSpec itemSpec = IS_All;
316	int wrapped = 0;
317	int doPem = 0;
318	const char *passphrase = NULL;
319
320    while ((ch = getopt(argc, argv, "k:o:t:f:P:wph")) != -1)
321	{
322		switch  (ch)
323		{
324		case 'k':
325			kcName = optarg;
326			break;
327		case 'o':
328			outFile = optarg;
329			break;
330		case 't':
331			if(!strcmp("certs", optarg)) {
332				itemSpec = IS_Certs;
333			}
334			else if(!strcmp("allKeys", optarg)) {
335				itemSpec = IS_AllKeys;
336			}
337			else if(!strcmp("pubKeys", optarg)) {
338				itemSpec = IS_PubKeys;
339			}
340			else if(!strcmp("privKeys", optarg)) {
341				itemSpec = IS_PrivKeys;
342			}
343			else if(!strcmp("identities", optarg)) {
344				itemSpec = IS_Identities;
345			}
346			else if(!strcmp("all", optarg)) {
347				itemSpec = IS_All;
348			}
349			else {
350				return 2; /* @@@ Return 2 triggers usage message. */
351			}
352			break;
353		case 'f':
354			if(!strcmp("openssl", optarg)) {
355				externFormat = kSecFormatOpenSSL;
356			}
357			else if(!strcmp("openssh1", optarg)) {
358				externFormat = kSecFormatSSH;
359			}
360			else if(!strcmp("openssh2", optarg)) {
361				externFormat = kSecFormatSSHv2;
362			}
363			else if(!strcmp("bsafe", optarg)) {
364				externFormat = kSecFormatBSAFE;
365			}
366			else if(!strcmp("raw", optarg)) {
367				externFormat = kSecFormatRawKey;
368			}
369			else if(!strcmp("pkcs7", optarg)) {
370				externFormat = kSecFormatPKCS7;
371			}
372			else if(!strcmp("pkcs8", optarg)) {
373				externFormat = kSecFormatWrappedPKCS8;
374			}
375			else if(!strcmp("pkcs12", optarg)) {
376				externFormat = kSecFormatPKCS12;
377			}
378			else if(!strcmp("netscape", optarg)) {
379				externFormat = kSecFormatNetscapeCertSequence;
380			}
381			else if(!strcmp("x509", optarg)) {
382				externFormat = kSecFormatX509Cert;
383			}
384			else if(!strcmp("pemseq", optarg)) {
385				externFormat = kSecFormatPEMSequence;
386			}
387			else {
388				return 2; /* @@@ Return 2 triggers usage message. */
389			}
390			break;
391		case 'w':
392			wrapped = 1;
393			break;
394		case 'p':
395			doPem = 1;
396			break;
397		case 'P':
398			passphrase = optarg;
399			break;
400		case '?':
401		default:
402			return 2; /* @@@ Return 2 triggers usage message. */
403		}
404	}
405
406	if(wrapped) {
407		switch(externFormat) {
408			case kSecFormatOpenSSL:
409			case kSecFormatUnknown:		// i.e., use default
410				externFormat = kSecFormatWrappedOpenSSL;
411				break;
412			case kSecFormatSSH:
413				externFormat = kSecFormatWrappedSSH;
414				break;
415			case kSecFormatSSHv2:
416				/* there is no wrappedSSHv2 */
417				externFormat = kSecFormatWrappedOpenSSL;
418				break;
419			case kSecFormatWrappedPKCS8:
420				/* proceed */
421				break;
422			default:
423				sec_error("Don't know how to wrap in specified format/type");
424				return 2; /* @@@ Return 2 triggers usage message. */
425		}
426	}
427
428	if(kcName) {
429		kcRef = keychain_open(kcName);
430		if(kcRef == NULL) {
431			return 1;
432		}
433	}
434	result = do_keychain_export(kcRef, externFormat, itemSpec,
435		passphrase, doPem, outFile);
436
437	if(kcRef) {
438		CFRelease(kcRef);
439	}
440	return result;
441}
442