1/*
2 * Copyright (c) 2003-2009 Apple 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_import.c
24 */
25
26#include "keychain_import.h"
27#include "keychain_utilities.h"
28#include "access_utils.h"
29#include "security.h"
30
31#include <errno.h>
32#include <unistd.h>
33#include <stdio.h>
34#include <Security/SecImportExport.h>
35#include <Security/SecIdentity.h>
36#include <Security/SecKey.h>
37#include <Security/SecCertificate.h>
38#include <Security/SecKeychainItemExtendedAttributes.h>
39#include <security_cdsa_utils/cuFileIo.h>
40#include <CoreFoundation/CoreFoundation.h>
41
42// SecTrustedApplicationCreateApplicationGroup
43#include <Security/SecTrustedApplicationPriv.h>
44
45#define KC_IMPORT_KEY_PASSWORD_MESSAGE      CFSTR("Enter the password for \"%1$@\":")
46#define KC_IMPORT_KEY_PASSWORD_RETRYMESSAGE CFSTR("Sorry, you entered an invalid password.\n\nEnter the password for \"%1$@\":")
47
48static int do_keychain_import(
49	SecKeychainRef		kcRef,
50	CFDataRef			inData,
51	SecExternalFormat   externFormat,
52	SecExternalItemType itemType,
53	SecAccessRef		access,
54	Boolean				nonExtractable,
55	const char			*passphrase,
56	const char			*fileName,
57	char				**attrNames,
58	char				**attrValues,
59	unsigned			numExtendedAttributes)
60{
61	SecKeyImportExportParameters	keyParams;
62	OSStatus		ortn;
63	CFStringRef		fileStr;
64	CFArrayRef		outArray = NULL;
65	int				result = 0;
66	int				numCerts = 0;
67	int				numKeys = 0;
68	int				numIdentities = 0;
69	int				tryCount = 0;
70	CFIndex			dex;
71	CFIndex			numItems = 0;
72	CFStringRef		passStr = NULL;
73	CFStringRef		promptStr = NULL;
74	CFStringRef		retryStr = NULL;
75
76	/*
77	 * Specify some kind of passphrase in case caller doesn't know this
78	 * is a wrapped object
79	 */
80	memset(&keyParams, 0, sizeof(SecKeyImportExportParameters));
81	keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
82	if(passphrase != NULL) {
83		passStr = CFStringCreateWithCString(NULL, passphrase, kCFStringEncodingASCII);
84		keyParams.passphrase = passStr;
85	}
86	else {
87		keyParams.flags = kSecKeySecurePassphrase;
88	}
89	if(nonExtractable) {
90        // explicitly set the key attributes, omitting the CSSM_KEYATTR_EXTRACTABLE bit
91        keyParams.keyAttributes = CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE;
92    }
93	keyParams.accessRef = access;
94
95	fileStr = CFStringCreateWithCString(NULL, fileName, kCFStringEncodingUTF8);
96	if (fileStr) {
97		CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, fileStr, kCFURLPOSIXPathStyle, FALSE);
98		if (fileURL) {
99			CFStringRef nameStr = CFURLCopyLastPathComponent(fileURL);
100			if (nameStr) {
101				safe_CFRelease(&fileStr);
102				fileStr = nameStr;
103			}
104			safe_CFRelease(&fileURL);
105		}
106	}
107	promptStr = CFStringCreateWithFormat(NULL, NULL, KC_IMPORT_KEY_PASSWORD_MESSAGE, fileStr);
108	retryStr = CFStringCreateWithFormat(NULL, NULL, KC_IMPORT_KEY_PASSWORD_RETRYMESSAGE, fileStr);
109
110	while (TRUE)
111	{
112		keyParams.alertPrompt = (tryCount == 0) ? promptStr : retryStr;
113
114		ortn = SecKeychainItemImport(inData,
115									 fileStr,
116									 &externFormat,
117									 &itemType,
118									 0,		/* flags not used (yet) */
119									 &keyParams,
120									 kcRef,
121									 &outArray);
122
123		if(ortn) {
124			if (ortn == errSecPkcs12VerifyFailure && ++tryCount < 3) {
125				continue;
126			}
127			sec_perror("SecKeychainItemImport", ortn);
128			result = 1;
129			goto cleanup;
130		}
131		break;
132	}
133
134	/*
135	 * Parse returned items & report to user
136	 */
137	if(outArray == NULL) {
138		sec_error("No keychain items found");
139		result = 1;
140		goto cleanup;
141	}
142	numItems = CFArrayGetCount(outArray);
143	for(dex=0; dex<numItems; dex++) {
144		CFTypeRef item = CFArrayGetValueAtIndex(outArray, dex);
145		CFTypeID itemType = CFGetTypeID(item);
146		if(itemType == SecIdentityGetTypeID()) {
147			numIdentities++;
148		}
149		else if(itemType == SecCertificateGetTypeID()) {
150			numCerts++;
151		}
152		else if(itemType == SecKeyGetTypeID()) {
153			numKeys++;
154		}
155		else {
156			sec_error("Unexpected item type returned from SecKeychainItemImport");
157			result = 1;
158			goto cleanup;
159		}
160	}
161	if(numIdentities) {
162		char *str;
163		if(numIdentities > 1) {
164			str = "identities";
165		}
166		else {
167			str = "identity";
168		}
169		fprintf(stdout, "%d %s imported.\n", numIdentities, str);
170	}
171	if(numKeys) {
172		char *str;
173		if(numKeys > 1) {
174			str = "keys";
175		}
176		else {
177			str = "key";
178		}
179		fprintf(stdout, "%d %s imported.\n", numKeys, str);
180	}
181	if(numCerts) {
182		char *str;
183		if(numCerts > 1) {
184			str = "certificates";
185		}
186		else {
187			str = "certificate";
188		}
189		fprintf(stdout, "%d %s imported.\n", numCerts, str);
190	}
191
192	/* optionally apply extended attributes */
193	if(numExtendedAttributes) {
194		unsigned attrDex;
195		for(attrDex=0; attrDex<numExtendedAttributes; attrDex++) {
196			CFStringRef attrNameStr = CFStringCreateWithCString(NULL, attrNames[attrDex],
197				kCFStringEncodingASCII);
198			CFDataRef attrValueData = CFDataCreate(NULL, (const UInt8 *)attrValues[attrDex],
199				strlen(attrValues[attrDex]));
200			for(dex=0; dex<numItems; dex++) {
201				SecKeychainItemRef itemRef =
202					(SecKeychainItemRef)CFArrayGetValueAtIndex(outArray, dex);
203				ortn = SecKeychainItemSetExtendedAttribute(itemRef, attrNameStr, attrValueData);
204				if(ortn) {
205					cssmPerror("SecKeychainItemSetExtendedAttribute", ortn);
206					result = 1;
207					break;
208				}
209			}	/* for each imported item */
210			CFRelease(attrNameStr);
211			CFRelease(attrValueData);
212			if(result) {
213				break;
214			}
215		}	/* for each extended attribute */
216	}
217
218cleanup:
219	safe_CFRelease(&fileStr);
220	safe_CFRelease(&outArray);
221	safe_CFRelease(&passStr);
222	safe_CFRelease(&promptStr);
223	safe_CFRelease(&retryStr);
224
225	return result;
226}
227
228int
229keychain_import(int argc, char * const *argv)
230{
231	int ch, result = 0;
232
233	char *inFile = NULL;
234	char *kcName = NULL;
235	SecKeychainRef kcRef = NULL;
236	SecExternalFormat externFormat = kSecFormatUnknown;
237	SecExternalItemType itemType = kSecItemTypeUnknown;
238	Boolean wrapped = FALSE;
239	Boolean nonExtractable = FALSE;
240	const char *passphrase = NULL;
241	unsigned char *inFileData = NULL;
242	unsigned inFileLen = 0;
243	CFDataRef inData = NULL;
244	unsigned numExtendedAttributes = 0;
245	char **attrNames = NULL;
246	char **attrValues = NULL;
247	Boolean access_specified = FALSE;
248	Boolean always_allow = FALSE;
249	SecAccessRef access = NULL;
250	CFMutableArrayRef trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
251
252	if(argc < 2) {
253		result = 2; /* @@@ Return 2 triggers usage message. */
254		goto cleanup;
255	}
256	inFile = argv[1];
257	if((argc == 2) && (inFile[0] == '-')) {
258		result = 2;
259		goto cleanup;
260	}
261	optind = 2;
262
263    while ((ch = getopt(argc, argv, "k:t:f:P:wxa:hAT:")) != -1)
264	{
265		switch  (ch)
266		{
267		case 'k':
268			kcName = optarg;
269			break;
270		case 't':
271			if(!strcmp("pub", optarg)) {
272				itemType = kSecItemTypePublicKey;
273			}
274			else if(!strcmp("priv", optarg)) {
275				itemType = kSecItemTypePrivateKey;
276			}
277			else if(!strcmp("session", optarg)) {
278				itemType = kSecItemTypeSessionKey;
279			}
280			else if(!strcmp("cert", optarg)) {
281				itemType = kSecItemTypeCertificate;
282			}
283			else if(!strcmp("agg", optarg)) {
284				itemType = kSecItemTypeAggregate;
285			}
286			else {
287				result = 2; /* @@@ Return 2 triggers usage message. */
288				goto cleanup;
289			}
290			break;
291		case 'f':
292			if(!strcmp("openssl", optarg)) {
293				externFormat = kSecFormatOpenSSL;
294			}
295			else if(!strcmp("openssh1", optarg)) {
296				externFormat = kSecFormatSSH;
297			}
298			else if(!strcmp("openssh2", optarg)) {
299				externFormat = kSecFormatSSHv2;
300			}
301			else if(!strcmp("bsafe", optarg)) {
302				externFormat = kSecFormatBSAFE;
303			}
304			else if(!strcmp("raw", optarg)) {
305				externFormat = kSecFormatRawKey;
306			}
307			else if(!strcmp("pkcs7", optarg)) {
308				externFormat = kSecFormatPKCS7;
309			}
310			else if(!strcmp("pkcs8", optarg)) {
311				externFormat = kSecFormatWrappedPKCS8;
312			}
313			else if(!strcmp("pkcs12", optarg)) {
314				externFormat = kSecFormatPKCS12;
315			}
316			else if(!strcmp("netscape", optarg)) {
317				externFormat = kSecFormatNetscapeCertSequence;
318			}
319			else if(!strcmp("x509", optarg)) {
320				externFormat = kSecFormatX509Cert;
321			}
322			else if(!strcmp("pemseq", optarg)) {
323				externFormat = kSecFormatPEMSequence;
324			}
325			else {
326				result = 2; /* @@@ Return 2 triggers usage message. */
327				goto cleanup;
328			}
329			break;
330		case 'w':
331			wrapped = TRUE;
332			break;
333		case 'x':
334			nonExtractable = TRUE;
335			break;
336		case 'P':
337			passphrase = optarg;
338			break;
339		case 'a':
340			/* this takes an additional argument */
341			if(optind > (argc - 1)) {
342				result = 2; /* @@@ Return 2 triggers usage message. */
343				goto cleanup;
344			}
345			attrNames  = (char **)realloc(attrNames, numExtendedAttributes * sizeof(char *));
346			attrValues = (char **)realloc(attrValues, numExtendedAttributes * sizeof(char *));
347			attrNames[numExtendedAttributes]  = optarg;
348			attrValues[numExtendedAttributes] = argv[optind];
349			numExtendedAttributes++;
350			optind++;
351			break;
352		case 'A':
353			always_allow = TRUE;
354			access_specified = TRUE;
355			break;
356		case 'T':
357			if (optarg[0])
358			{
359				SecTrustedApplicationRef app = NULL;
360				OSStatus status = noErr;
361				/* check whether the argument specifies an application group */
362				const char *groupPrefix = "group://";
363				size_t prefixLen = strlen(groupPrefix);
364				if (strlen(optarg) > prefixLen && !memcmp(optarg, groupPrefix, prefixLen)) {
365					const char *groupName = &optarg[prefixLen];
366					if ((status = SecTrustedApplicationCreateApplicationGroup(groupName, NULL, &app)) != noErr) {
367						sec_error("SecTrustedApplicationCreateApplicationGroup %s: %s", optarg, sec_errstr(status));
368					}
369				} else {
370					if ((status = SecTrustedApplicationCreateFromPath(optarg, &app)) != noErr) {
371						sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status));
372					}
373				}
374
375				if (status) {
376					result = 1;
377					goto cleanup;
378				}
379
380				CFArrayAppendValue(trusted_list, app);
381				CFRelease(app);
382			}
383			access_specified = TRUE;
384			break;
385		case '?':
386		default:
387			result = 2; /* @@@ Return 2 triggers usage message. */
388			goto cleanup;
389		}
390	}
391
392	if(wrapped) {
393		switch(externFormat) {
394			case kSecFormatOpenSSL:
395			case kSecFormatUnknown:		// i.e., use default
396				externFormat = kSecFormatWrappedOpenSSL;
397				break;
398			case kSecFormatSSH:
399				externFormat = kSecFormatWrappedSSH;
400				break;
401			case kSecFormatSSHv2:
402				/* there is no wrappedSSHv2 */
403				externFormat = kSecFormatWrappedOpenSSL;
404				break;
405			case kSecFormatWrappedPKCS8:
406				/* proceed */
407				break;
408			default:
409				fprintf(stderr, "Don't know how to wrap in specified format/type\n");
410				result = 2; /* @@@ Return 2 triggers usage message. */
411				goto cleanup;
412		}
413	}
414
415	if(kcName) {
416		kcRef = keychain_open(kcName);
417		if(kcRef == NULL) {
418			return 1;
419		}
420	}
421	if(readFile(inFile, &inFileData, &inFileLen)) {
422		sec_error("Error reading infile %s: %s", inFile, strerror(errno));
423		result = 1;
424		goto cleanup;
425	}
426	inData = CFDataCreate(NULL, inFileData, inFileLen);
427	if(inData == NULL) {
428		result = 1;
429		goto cleanup;
430	}
431	free(inFileData);
432
433	if(access_specified)
434	{
435		char *accessName = NULL;
436		CFStringRef fileStr = CFStringCreateWithCString(NULL, inFile, kCFStringEncodingUTF8);
437		if (fileStr) {
438			CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, fileStr, kCFURLPOSIXPathStyle, FALSE);
439			if (fileURL) {
440				CFStringRef nameStr = CFURLCopyLastPathComponent(fileURL);
441				if (nameStr) {
442					CFIndex nameLen = CFStringGetLength(nameStr);
443					CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8);
444					accessName = (char *)malloc(bufLen);
445					if (!CFStringGetCString(nameStr, accessName, bufLen-1, kCFStringEncodingUTF8))
446						accessName[0]=0;
447					nameLen = strlen(accessName);
448					char *p = &accessName[nameLen]; // initially points to terminating null
449					while (--nameLen > 0) {
450						if (*p == '.') { // strip extension, if any
451							*p = '\0';
452							break;
453						}
454						p--;
455					}
456					safe_CFRelease(&nameStr);
457				}
458				safe_CFRelease(&fileURL);
459			}
460			safe_CFRelease(&fileStr);
461		}
462
463		result = create_access(accessName, always_allow, trusted_list, &access);
464
465		if (accessName) {
466			free(accessName);
467		}
468		if (result != 0) {
469			goto cleanup;
470		}
471	}
472
473	result = do_keychain_import(kcRef, inData, externFormat, itemType, access,
474								nonExtractable, passphrase, inFile, attrNames,
475								attrValues, numExtendedAttributes);
476
477cleanup:
478	safe_CFRelease(&trusted_list);
479	safe_CFRelease(&access);
480	safe_CFRelease(&kcRef);
481	safe_CFRelease(&inData);
482
483	return result;
484}
485