1/*
2 * Copyright (c) 2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_BSD_LICENSE_HEADER_START@
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1.  Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 * 2.  Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of its
16 *     contributors may be used to endorse or promote products derived from
17 *     this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * @APPLE_BSD_LICENSE_HEADER_END@
31 */
32
33#include "includes.h"
34
35#include <stdio.h>
36#include <string.h>
37
38#include "xmalloc.h"
39#include "key.h"
40#include "authfd.h"
41#include "authfile.h"
42
43#if defined(__APPLE_KEYCHAIN__)
44
45#include <CoreFoundation/CoreFoundation.h>
46#include <Security/Security.h>
47
48/* Our Security/SecPassword.h is not yet API, so I will define the constants that I am using here. */
49enum SEC_PASSWORD_OPTS {
50kSecPasswordGet     = 1<<0,  // Get password from keychain or user
51kSecPasswordSet     = 1<<1,  // Set password (passed in if kSecPasswordGet not set, otherwise from user)
52kSecPasswordFail    = 1<<2,  // Wrong password (ignore item in keychain and flag error)
53};
54
55#endif
56
57/*
58 * Platform-specific helper functions.
59 */
60
61#if defined(__APPLE_KEYCHAIN__)
62
63static int get_boolean_preference(const char *key, int default_value,
64    int foreground)
65{
66	int value = default_value;
67	CFStringRef keyRef = NULL;
68	CFPropertyListRef valueRef = NULL;
69
70	keyRef = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
71	if (keyRef != NULL)
72		valueRef = CFPreferencesCopyAppValue(keyRef,
73		    CFSTR("org.openbsd.openssh"));
74	if (valueRef != NULL)
75		if (CFGetTypeID(valueRef) == CFBooleanGetTypeID())
76			value = CFBooleanGetValue(valueRef);
77		else if (foreground)
78			fprintf(stderr, "Ignoring nonboolean %s preference.\n", key);
79
80	if (keyRef)
81		CFRelease(keyRef);
82	if (valueRef)
83		CFRelease(valueRef);
84
85	return value;
86}
87
88#endif
89
90/*
91 * Store the passphrase for a given identity in the keychain.
92 */
93void
94store_in_keychain(const char *filename, const char *passphrase)
95{
96
97#if defined(__APPLE_KEYCHAIN__)
98
99	/*
100	 * store_in_keychain
101	 * Mac OS X implementation
102	 */
103
104	CFStringRef cfstr_relative_filename = NULL;
105	CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL;
106	CFStringRef cfstr_filename = NULL;
107	CFDataRef cfdata_filename = NULL;
108	CFIndex filename_len;
109	UInt8 *label = NULL;
110	UInt8 *utf8_filename;
111	OSStatus rv;
112	SecKeychainItemRef itemRef = NULL;
113	SecTrustedApplicationRef apps[] = {NULL, NULL, NULL};
114	CFArrayRef trustedlist = NULL;
115	SecAccessRef initialAccess = NULL;
116
117	/* Bail out if KeychainIntegration preference is -bool NO */
118	if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) {
119		fprintf(stderr, "Keychain integration is disabled.\n");
120		goto err;
121	}
122
123	/* Interpret filename with the correct encoding. */
124	if ((cfstr_relative_filename =
125	    CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL)
126	    {
127		fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n");
128		goto err;
129	}
130	if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL,
131	    cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) {
132		fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n");
133		goto err;
134	}
135	if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) ==
136	    NULL) {
137		fprintf(stderr, "CFURLCopyAbsoluteURL failed\n");
138		goto err;
139	}
140	if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename,
141	    kCFURLPOSIXPathStyle)) == NULL) {
142		fprintf(stderr, "CFURLCopyFileSystemPath failed\n");
143		goto err;
144	}
145	if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL,
146	    cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) {
147		fprintf(stderr, "CFStringCreateExternalRepresentation failed\n");
148		goto err;
149	}
150	filename_len = CFDataGetLength(cfdata_filename);
151	if ((label = xmalloc(filename_len + 5)) == NULL) {
152		fprintf(stderr, "xmalloc failed\n");
153		goto err;
154	}
155	memcpy(label, "SSH: ", 5);
156	utf8_filename = label + 5;
157	CFDataGetBytes(cfdata_filename, CFRangeMake(0, filename_len),
158	    utf8_filename);
159
160	/* Check if we already have this passphrase. */
161	rv = SecKeychainFindGenericPassword(NULL, 3, "SSH", filename_len,
162	    (char *)utf8_filename, NULL, NULL, &itemRef);
163	if (rv == errSecItemNotFound) {
164		/* Add a new keychain item. */
165		SecKeychainAttribute attrs[] = {
166			{kSecLabelItemAttr, filename_len + 5, label},
167			{kSecServiceItemAttr, 3, "SSH"},
168			{kSecAccountItemAttr, filename_len, utf8_filename}
169		};
170		SecKeychainAttributeList attrList =
171		    {sizeof(attrs) / sizeof(attrs[0]), attrs};
172		if (SecTrustedApplicationCreateFromPath("/usr/bin/ssh-agent",
173		    &apps[0]) != noErr ||
174		    SecTrustedApplicationCreateFromPath("/usr/bin/ssh-add",
175		    &apps[1]) != noErr ||
176		    SecTrustedApplicationCreateFromPath("/usr/bin/ssh",
177		    &apps[2]) != noErr) {
178			fprintf(stderr, "SecTrustedApplicationCreateFromPath failed\n");
179			goto err;
180		}
181		if ((trustedlist = CFArrayCreate(NULL, (const void **)apps,
182		    sizeof(apps) / sizeof(apps[0]), &kCFTypeArrayCallBacks)) ==
183		    NULL) {
184			fprintf(stderr, "CFArrayCreate failed\n");
185			goto err;
186		}
187		if (SecAccessCreate(cfstr_filename, trustedlist,
188		    &initialAccess) != noErr) {
189			fprintf(stderr, "SecAccessCreate failed\n");
190			goto err;
191		}
192		if (SecKeychainItemCreateFromContent(
193		    kSecGenericPasswordItemClass, &attrList, strlen(passphrase),
194		    passphrase, NULL, initialAccess, NULL) == noErr)
195			fprintf(stderr, "Passphrase stored in keychain: %s\n", filename);
196		else
197			fprintf(stderr, "Could not create keychain item\n");
198	} else if (rv == noErr) {
199		/* Update an existing keychain item. */
200		if (SecKeychainItemModifyAttributesAndData(itemRef, NULL,
201		    strlen(passphrase), passphrase) == noErr)
202			fprintf(stderr, "Passphrase updated in keychain: %s\n", filename);
203		else
204			fprintf(stderr, "Could not modify keychain item\n");
205	} else
206		fprintf(stderr, "Could not access keychain\n");
207
208err:	/* Clean up. */
209	if (cfstr_relative_filename)
210		CFRelease(cfstr_relative_filename);
211	if (cfurl_relative_filename)
212		CFRelease(cfurl_relative_filename);
213	if (cfurl_filename)
214		CFRelease(cfurl_filename);
215	if (cfstr_filename)
216		CFRelease(cfstr_filename);
217	if (cfdata_filename)
218		CFRelease(cfdata_filename);
219	if (label)
220		xfree(label);
221	if (itemRef)
222		CFRelease(itemRef);
223	if (apps[0])
224		CFRelease(apps[0]);
225	if (apps[1])
226		CFRelease(apps[1]);
227	if (apps[2])
228		CFRelease(apps[2]);
229	if (trustedlist)
230		CFRelease(trustedlist);
231	if (initialAccess)
232		CFRelease(initialAccess);
233
234#else
235
236	/*
237	 * store_in_keychain
238	 * no keychain implementation
239	 */
240
241	fprintf(stderr, "Keychain is not available on this system\n");
242
243#endif
244
245}
246
247/*
248 * Remove the passphrase for a given identity from the keychain.
249 */
250void
251remove_from_keychain(const char *filename)
252{
253
254#if defined(__APPLE_KEYCHAIN__)
255
256	/*
257	 * remove_from_keychain
258	 * Mac OS X implementation
259	 */
260
261	CFStringRef cfstr_relative_filename = NULL;
262	CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL;
263	CFStringRef cfstr_filename = NULL;
264	CFDataRef cfdata_filename = NULL;
265	CFIndex filename_len;
266	const UInt8 *utf8_filename;
267	OSStatus rv;
268	SecKeychainItemRef itemRef = NULL;
269
270	/* Bail out if KeychainIntegration preference is -bool NO */
271	if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) {
272		fprintf(stderr, "Keychain integration is disabled.\n");
273		goto err;
274	}
275
276	/* Interpret filename with the correct encoding. */
277	if ((cfstr_relative_filename =
278	    CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL)
279	    {
280		fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n");
281		goto err;
282	}
283	if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL,
284	    cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) {
285		fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n");
286		goto err;
287	}
288	if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) ==
289	    NULL) {
290		fprintf(stderr, "CFURLCopyAbsoluteURL failed\n");
291		goto err;
292	}
293	if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename,
294	    kCFURLPOSIXPathStyle)) == NULL) {
295		fprintf(stderr, "CFURLCopyFileSystemPath failed\n");
296		goto err;
297	}
298	if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL,
299	    cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) {
300		fprintf(stderr, "CFStringCreateExternalRepresentation failed\n");
301		goto err;
302	}
303	filename_len = CFDataGetLength(cfdata_filename);
304	utf8_filename = CFDataGetBytePtr(cfdata_filename);
305
306	/* Check if we already have this passphrase. */
307	rv = SecKeychainFindGenericPassword(NULL, 3, "SSH", filename_len,
308	    (const char *)utf8_filename, NULL, NULL, &itemRef);
309	if (rv == noErr) {
310		/* Remove the passphrase from the keychain. */
311		if (SecKeychainItemDelete(itemRef) == noErr)
312			fprintf(stderr, "Passphrase removed from keychain: %s\n", filename);
313		else
314			fprintf(stderr, "Could not remove keychain item\n");
315	} else if (rv != errSecItemNotFound)
316		fprintf(stderr, "Could not access keychain\n");
317
318err:	/* Clean up. */
319	if (cfstr_relative_filename)
320		CFRelease(cfstr_relative_filename);
321	if (cfurl_relative_filename)
322		CFRelease(cfurl_relative_filename);
323	if (cfurl_filename)
324		CFRelease(cfurl_filename);
325	if (cfstr_filename)
326		CFRelease(cfstr_filename);
327	if (cfdata_filename)
328		CFRelease(cfdata_filename);
329	if (itemRef)
330		CFRelease(itemRef);
331
332#else
333
334	/*
335	 * remove_from_keychain
336	 * no keychain implementation
337	 */
338
339	fprintf(stderr, "Keychain is not available on this system\n");
340
341#endif
342
343}
344
345/*
346 * Add identities to ssh-agent using passphrases stored in the keychain.
347 * Returns zero on success and nonzero on failure.
348 * add_identity is a callback into ssh-agent.  It takes a filename and a
349 * passphrase, and attempts to add the identity to the agent.  It returns
350 * zero on success and nonzero on failure.
351 */
352int
353add_identities_using_keychain(int (*add_identity)(const char *, const char *))
354{
355
356#if defined(__APPLE_KEYCHAIN__)
357
358	/*
359	 * add_identities_using_keychain
360	 * Mac OS X implementation
361	 */
362
363	OSStatus rv;
364	SecKeychainSearchRef searchRef;
365	SecKeychainItemRef itemRef;
366	UInt32 length;
367	void *data;
368	CFIndex maxsize;
369
370	/* Bail out if KeychainIntegration preference is -bool NO */
371	if (get_boolean_preference("KeychainIntegration", 1, 0) == 0)
372		return 0;
373
374	/* Search for SSH passphrases in the keychain */
375	SecKeychainAttribute attrs[] = {
376		{kSecServiceItemAttr, 3, "SSH"}
377	};
378	SecKeychainAttributeList attrList =
379	    {sizeof(attrs) / sizeof(attrs[0]), attrs};
380	if ((rv = SecKeychainSearchCreateFromAttributes(NULL,
381	    kSecGenericPasswordItemClass, &attrList, &searchRef)) != noErr)
382		return 0;
383
384	/* Iterate through the search results. */
385	while ((rv = SecKeychainSearchCopyNext(searchRef, &itemRef)) == noErr) {
386		UInt32 tag = kSecAccountItemAttr;
387		UInt32 format = kSecFormatUnknown;
388		SecKeychainAttributeInfo info = {1, &tag, &format};
389		SecKeychainAttributeList *itemAttrList = NULL;
390		CFStringRef cfstr_filename = NULL;
391		char *filename = NULL;
392		char *passphrase = NULL;
393
394		/* Retrieve filename and passphrase. */
395		if ((rv = SecKeychainItemCopyAttributesAndData(itemRef, &info,
396		    NULL, &itemAttrList, &length, &data)) != noErr)
397			goto err;
398		if (itemAttrList->count != 1)
399			goto err;
400		cfstr_filename = CFStringCreateWithBytes(NULL,
401		    itemAttrList->attr->data, itemAttrList->attr->length,
402		    kCFStringEncodingUTF8, true);
403		maxsize = CFStringGetMaximumSizeOfFileSystemRepresentation(
404		    cfstr_filename);
405		if ((filename = xmalloc(maxsize)) == NULL)
406			goto err;
407		if (CFStringGetFileSystemRepresentation(cfstr_filename,
408		    filename, maxsize) == false)
409			goto err;
410		if ((passphrase = xmalloc(length + 1)) == NULL)
411			goto err;
412		memcpy(passphrase, data, length);
413		passphrase[length] = '\0';
414
415		/* Add the identity. */
416		add_identity(filename, passphrase);
417
418err:		/* Clean up. */
419		if (itemRef)
420			CFRelease(itemRef);
421		if (cfstr_filename)
422			CFRelease(cfstr_filename);
423		if (filename)
424			xfree(filename);
425		if (passphrase)
426			xfree(passphrase);
427		if (itemAttrList)
428			SecKeychainItemFreeAttributesAndData(itemAttrList,
429			    data);
430	}
431
432	CFRelease(searchRef);
433
434	return 0;
435
436#else
437
438	/*
439	 * add_identities_using_keychain
440	 * no implementation
441	 */
442
443	return 1;
444
445#endif
446
447}
448
449/*
450 * Prompt the user for a key's passphrase.  The user will be offered the option
451 * of storing the passphrase in their keychain.  Returns the passphrase
452 * (which the caller is responsible for xfreeing), or NULL if this function
453 * fails or is not implemented.  If this function is not implemented, ssh will
454 * fall back on the standard read_passphrase function, and the user will need
455 * to use ssh-add -K to add their keys to the keychain.
456 */
457char *
458keychain_read_passphrase(const char *filename, int oAskPassGUI)
459{
460
461#if defined(__APPLE_KEYCHAIN__)
462
463	/*
464	 * keychain_read_passphrase
465	 * Mac OS X implementation
466	 */
467
468	CFStringRef cfstr_relative_filename = NULL;
469	CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL;
470	CFStringRef cfstr_filename = NULL;
471	CFDataRef cfdata_filename = NULL;
472	CFIndex filename_len;
473	UInt8 *label = NULL;
474	UInt8 *utf8_filename;
475	SecPasswordRef passRef = NULL;
476	SecTrustedApplicationRef apps[] = {NULL, NULL, NULL};
477	CFArrayRef trustedlist = NULL;
478	SecAccessRef initialAccess = NULL;
479	CFURLRef path = NULL;
480	CFStringRef pathFinal = NULL;
481	CFURLRef bundle_url = NULL;
482	CFBundleRef bundle = NULL;
483	CFStringRef promptTemplate = NULL, prompt = NULL;
484	UInt32 length;
485	const void *data;
486	AuthenticationConnection *ac = NULL;
487	char *result = NULL;
488
489	/* Bail out if KeychainIntegration preference is -bool NO */
490	if (get_boolean_preference("KeychainIntegration", 1, 1) == 0)
491		goto err;
492
493	/* Bail out if the user set AskPassGUI preference to -bool NO */
494	if (get_boolean_preference("AskPassGUI", 1, 1) == 0 || oAskPassGUI == 0)
495		goto err;
496
497	/* Bail out if we can't communicate with ssh-agent */
498	if ((ac = ssh_get_authentication_connection()) == NULL)
499		goto err;
500
501	/* Interpret filename with the correct encoding. */
502	if ((cfstr_relative_filename =
503	    CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL)
504	    {
505		fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n");
506		goto err;
507	}
508	if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL,
509	    cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) {
510		fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n");
511		goto err;
512	}
513	if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) ==
514	    NULL) {
515		fprintf(stderr, "CFURLCopyAbsoluteURL failed\n");
516		goto err;
517	}
518	if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename,
519	    kCFURLPOSIXPathStyle)) == NULL) {
520		fprintf(stderr, "CFURLCopyFileSystemPath failed\n");
521		goto err;
522	}
523	if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL,
524	    cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) {
525		fprintf(stderr, "CFStringCreateExternalRepresentation failed\n");
526		goto err;
527	}
528	filename_len = CFDataGetLength(cfdata_filename);
529	if ((label = xmalloc(filename_len + 5)) == NULL) {
530		fprintf(stderr, "xmalloc failed\n");
531		goto err;
532	}
533	memcpy(label, "SSH: ", 5);
534	utf8_filename = label + 5;
535	CFDataGetBytes(cfdata_filename, CFRangeMake(0, filename_len),
536	    utf8_filename);
537
538	/* Build a SecPasswordRef. */
539	SecKeychainAttribute searchAttrs[] = {
540		{kSecServiceItemAttr, 3, "SSH"},
541		{kSecAccountItemAttr, filename_len, utf8_filename}
542	};
543	SecKeychainAttributeList searchAttrList =
544	    {sizeof(searchAttrs) / sizeof(searchAttrs[0]), searchAttrs};
545	SecKeychainAttribute attrs[] = {
546		{kSecLabelItemAttr, filename_len + 5, label},
547		{kSecServiceItemAttr, 3, "SSH"},
548		{kSecAccountItemAttr, filename_len, utf8_filename}
549	};
550	SecKeychainAttributeList attrList =
551	    {sizeof(attrs) / sizeof(attrs[0]), attrs};
552	if (SecGenericPasswordCreate(&searchAttrList, &attrList, &passRef) !=
553	    noErr) {
554		fprintf(stderr, "SecGenericPasswordCreate failed\n");
555		goto err;
556	}
557	if (SecTrustedApplicationCreateFromPath("/usr/bin/ssh-agent", &apps[0])
558	    != noErr ||
559	    SecTrustedApplicationCreateFromPath("/usr/bin/ssh-add", &apps[1])
560	    != noErr ||
561	    SecTrustedApplicationCreateFromPath("/usr/bin/ssh", &apps[2])
562	    != noErr) {
563		fprintf(stderr, "SecTrustedApplicationCreateFromPath failed\n");
564		goto err;
565	}
566	if ((trustedlist = CFArrayCreate(NULL, (const void **)apps,
567	    sizeof(apps) / sizeof(apps[0]), &kCFTypeArrayCallBacks)) == NULL) {
568		fprintf(stderr, "CFArrayCreate failed\n");
569		goto err;
570	}
571	if (SecAccessCreate(cfstr_filename, trustedlist, &initialAccess)
572	    != noErr) {
573		fprintf(stderr, "SecAccessCreate failed\n");
574		goto err;
575	}
576	if (SecPasswordSetInitialAccess(passRef, initialAccess) != noErr) {
577		fprintf(stderr, "SecPasswordSetInitialAccess failed\n");
578		goto err;
579	}
580
581	/* Request the passphrase from the user. */
582	if ((path = CFURLCreateFromFileSystemRepresentation(NULL,
583	    (UInt8 *)filename, strlen(filename), false)) == NULL) {
584		fprintf(stderr, "CFURLCreateFromFileSystemRepresentation failed\n");
585		goto err;
586	}
587	if ((pathFinal = CFURLCopyLastPathComponent(path)) == NULL) {
588		fprintf(stderr, "CFURLCopyLastPathComponent failed\n");
589		goto err;
590	}
591	if (!((bundle_url = CFURLCreateWithFileSystemPath(NULL,
592	    CFSTR("/System/Library/CoreServices/"), kCFURLPOSIXPathStyle, true))
593	    != NULL && (bundle = CFBundleCreate(NULL, bundle_url)) != NULL &&
594	    (promptTemplate = CFCopyLocalizedStringFromTableInBundle(
595	    CFSTR("Enter your password for the SSH key \"%@\"."),
596	    CFSTR("OpenSSH"), bundle, "Text of the dialog asking the user for"
597	    "their passphrase.  The %@ will be replaced with the filename of a"
598	    "specific key.")) != NULL) &&
599	    (promptTemplate = CFStringCreateCopy(NULL,
600	    CFSTR("Enter your password for the SSH key \"%@\"."))) == NULL) {
601		fprintf(stderr, "CFStringCreateCopy failed\n");
602		goto err;
603	}
604	if ((prompt = CFStringCreateWithFormat(NULL, NULL, promptTemplate,
605	    pathFinal)) == NULL) {
606		fprintf(stderr, "CFStringCreateWithFormat failed\n");
607		goto err;
608	}
609	switch (SecPasswordAction(passRef, prompt,
610	    kSecPasswordGet|kSecPasswordFail, &length, &data)) {
611	case noErr:
612		result = xmalloc(length + 1);
613		memcpy(result, data, length);
614		result[length] = '\0';
615
616		/* Save password in keychain if requested. */
617		if (noErr != SecPasswordAction(passRef, CFSTR(""), kSecPasswordSet, &length, &data))
618			fprintf(stderr, "Saving password to keychain failed\n");
619
620		/* Add password to agent. */
621		char *comment = NULL;
622		Key *private = key_load_private(filename, result, &comment);
623		if (NULL == private)
624			break;
625		if (ssh_add_identity_constrained(ac, private, comment, 0, 0))
626			fprintf(stderr, "Identity added: %s (%s)\n", filename, comment);
627		else
628			fprintf(stderr, "Could not add identity: %s\n", filename);
629		xfree(comment);
630		key_free(private);
631		break;
632	case errAuthorizationCanceled:
633		result = xmalloc(1);
634		*result = '\0';
635		break;
636	default:
637		goto err;
638	}
639
640err:	/* Clean up. */
641	if (cfstr_relative_filename)
642		CFRelease(cfstr_relative_filename);
643	if (cfurl_relative_filename)
644		CFRelease(cfurl_relative_filename);
645	if (cfurl_filename)
646		CFRelease(cfurl_filename);
647	if (cfstr_filename)
648		CFRelease(cfstr_filename);
649	if (cfdata_filename)
650		CFRelease(cfdata_filename);
651	if (label)
652		xfree(label);
653	if (passRef)
654		CFRelease(passRef);
655	if (apps[0])
656		CFRelease(apps[0]);
657	if (apps[1])
658		CFRelease(apps[1]);
659	if (apps[2])
660		CFRelease(apps[2]);
661	if (trustedlist)
662		CFRelease(trustedlist);
663	if (initialAccess)
664		CFRelease(initialAccess);
665	if (path)
666		CFRelease(path);
667	if (pathFinal)
668		CFRelease(pathFinal);
669	if (bundle_url)
670		CFRelease(bundle_url);
671	if (bundle)
672		CFRelease(bundle);
673	if (promptTemplate)
674		CFRelease(promptTemplate);
675	if (prompt)
676		CFRelease(prompt);
677	if (ac)
678		ssh_close_authentication_connection(ac);
679
680	return result;
681
682#else
683
684	/*
685	 * keychain_read_passphrase
686	 * no implementation
687	 */
688
689	return NULL;
690
691#endif
692
693}
694
695#if defined(__APPLE_KEYCHAIN__)
696volatile sig_atomic_t keychain_thread_active = 0;
697
698OSStatus
699keychain_lock_callback(SecKeychainEvent event, SecKeychainCallbackInfo *info, void *context)
700{
701	SecKeychainRef login_keychain = NULL;
702	OSStatus retval = noErr;
703
704	/* Only care about login keychain */
705	retval = SecKeychainCopyDefault(&login_keychain);
706	if (retval != noErr) {
707		debug("keychain_lock_callback: Unable to get login keychain, doing nothing.");
708		goto cleanup;
709	}
710	if (!CFEqual(info->keychain, login_keychain)) {
711		goto cleanup;
712	}
713
714	AuthenticationConnection *ac = ssh_get_authentication_connection();
715	if (NULL == ac) {
716		error("keychain_lock_callback: Unable to get authentication connection.");
717		goto cleanup;
718	}
719
720	/* Silently remove all identitites */
721	debug("keychain_lock_callback: Removing all identities.");
722	if (0 != ssh_remove_all_identities(ac, 1))
723		debug("keychain_lock_callback: Failed to remove all v1 identities.");
724
725	if (0 != ssh_remove_all_identities(ac, 2))
726		debug("keychain_lock_callback: Failed to remove all v2 identities.");
727
728	ssh_close_authentication_connection(ac);
729
730cleanup:
731	if (login_keychain)
732		CFRelease(login_keychain);
733
734	return errSecSuccess;
735}
736
737OSStatus
738keychain_unlock_callback(SecKeychainEvent event, SecKeychainCallbackInfo *info, void *context)
739{
740	OSStatus ret = errSecSuccess;
741	Boolean state = false;
742	SecKeychainRef login_keychain = NULL;
743
744	/* Only care about login keychain */
745	ret = SecKeychainCopyDefault(&login_keychain);
746	if (ret != noErr) {
747		debug("keychain_lock_callback: Unable to get login keychain.");
748		goto cleanup;
749	}
750	if (!CFEqual(info->keychain, login_keychain)) {
751		goto cleanup;
752	}
753
754	/* No user interaction for keychain actions */
755	ret = SecKeychainGetUserInteractionAllowed(&state);
756	if (errSecSuccess != ret)
757		debug("keychain_unlock_callback: Unable to determine if user interaction is allowed.");
758
759	if (state) {
760		debug("keychain_unlock_callback: Temporarily denying user interaction.");
761		ret = SecKeychainSetUserInteractionAllowed(false);
762		if (errSecSuccess != ret)
763			error("Keychain unlocked callback: Unable deny user interaction.");
764	}
765
766	/* Silently add all identities from keychain */
767	debug("keychain_unlock_callback: Adding all identities from keychain, no user interaction.");
768	AuthenticationConnection *ac = ssh_get_authentication_connection();
769	if (NULL == ac) {
770		error("keychain_unlock_callback: Unable to get authentication connection.");
771		goto cleanup;
772	}
773	ssh_add_from_keychain(ac);
774	ssh_close_authentication_connection(ac);
775
776	/* Set user interaction state back */
777	if (state) {
778		debug("keychain_unlock_callback: Restoring user interaction.");
779		ret = SecKeychainSetUserInteractionAllowed(state);
780		if (errSecSuccess != ret)
781			error("keychain_unlock_callback:  Unable to restore user interaction.");
782	}
783
784cleanup:
785	if (login_keychain)
786		CFRelease(login_keychain);
787
788	return errSecSuccess;
789}
790
791void
792keychain_thread_timer_callback(CFRunLoopTimerRef timer, void *info)
793{
794	/* Will get here every kCFAbsoluteTimeIntervalSince1904 seconds. */
795}
796
797void*
798keychain_thread_main(void *msg)
799{
800	OSStatus ret;
801
802	CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
803					CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1904,
804					kCFAbsoluteTimeIntervalSince1904,
805					0, 0, keychain_thread_timer_callback, NULL);
806	if (NULL == timer)
807		error("keychain_thread_main: Cannot create timer for runloop.");
808
809	CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
810
811	ret = SecKeychainAddCallback(&keychain_lock_callback, kSecLockEventMask, NULL);
812	if (errSecSuccess != ret)
813		error("keychain_thread_main: Unable to add keychain lock callback.");
814
815	SecKeychainAddCallback(&keychain_unlock_callback, kSecUnlockEventMask, NULL);
816	if (errSecSuccess != ret)
817		error("keychain_thread_main: Unable to add keychain unlock callback.");
818
819	CFRunLoopRun();
820	/* NEVER REACHED */
821
822	return NULL;
823}
824
825/* Start the keychain thread. */
826void
827keychain_thread_init()
828{
829	if (!keychain_thread_active) {
830		int ret;
831		pthread_t thread;
832
833		keychain_thread_active = 1;
834		ret = pthread_create(&thread, NULL, &keychain_thread_main, (void*)"keychain-notification-thread");
835		if (0 != ret)
836			error("keychain_thread_init: pthread_create failed for keychain notification thread.");
837	}
838}
839
840#endif
841