Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
openssh-{7.7,7.8,7.9,8.0,8.1,8.2}p1 patches for storing SSH passphrases in the OS X keychain. https://github.com/leonklingele/homebrew-dupes
From eefbdc8d0c72b6608d9f265294e6d8e56a5ed849 Mon Sep 17 00:00:00 2001
From: Leon Klingele <git@leonklingele.de>
Date: Sat, 15 Feb 2020 10:23:58 +0100
Subject: [PATCH 1/2] apple-keychain-integration-other-changes
---
Makefile.in | 18 +-
audit-bsm.c | 7 +-
auth.c | 2 +-
authfd.c | 25 ++
authfd.h | 6 +
configure.ac | 30 +++
groupaccess.c | 50 +++-
groupaccess.h | 2 +-
keychain.c | 695 ++++++++++++++++++++++++++++++++++++++++++++++++++
keychain.h | 45 ++++
readconf.c | 19 ++
readconf.h | 4 +
servconf.c | 2 +-
session.c | 4 +
ssh-add.1 | 12 +-
ssh-add.c | 62 ++++-
ssh-agent.c | 139 ++++++++++
ssh-keysign.8 | 3 +
sshconnect2.c | 5 +
sshd.c | 11 +-
20 files changed, 1111 insertions(+), 30 deletions(-)
create mode 100644 keychain.c
create mode 100644 keychain.h
diff --git a/Makefile.in b/Makefile.in
index e754947..ef9cca7 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -62,6 +62,7 @@ ENT=@ENT@
XAUTH_PATH=@XAUTH_PATH@
LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@
LDFLAGS_NOPIE=-L. -Lopenbsd-compat/ @LDFLAGS_NOPIE@
+KEYCHAIN_LDFLAGS=@KEYCHAIN_LDFLAGS@
EXEEXT=@EXEEXT@
MANFMT=@MANFMT@
MKDIR_P=@MKDIR_P@
@@ -152,6 +153,8 @@ SFTPSERVER_OBJS=sftp-common.o sftp-server.o sftp-server-main.o
SFTP_OBJS= sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o
+KEYCHAINOBJS=keychain.o
+
MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-keysign.8.out ssh-pkcs11-helper.8.out ssh-sk-helper.8.out sshd_config.5.out ssh_config.5.out
MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-keysign.8 ssh-pkcs11-helper.8 ssh-sk-helper.8 sshd_config.5 ssh_config.5
MANTYPE = @MANTYPE@
@@ -187,6 +190,7 @@ all: configure-check $(CONFIGFILES) $(MANPAGES) $(TARGETS)
$(LIBSSH_OBJS): Makefile.in config.h
$(SSHOBJS): Makefile.in config.h
$(SSHDOBJS): Makefile.in config.h
+$(KEYCHAINOBJS): Makefile.in config.h
configure-check: $(srcdir)/configure
$(srcdir)/configure: configure.ac aclocal.m4
@@ -205,8 +209,8 @@ libssh.a: $(LIBSSH_OBJS)
$(AR) rv $@ $(LIBSSH_OBJS)
$(RANLIB) $@
-ssh$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHOBJS)
- $(LD) -o $@ $(SSHOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHLIBS) $(LIBS) $(GSSLIBS)
+ssh$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHOBJS) $(KEYCHAINOBJS)
+ $(LD) -o $@ $(SSHOBJS) $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(SSHLIBS) $(LIBS) $(GSSLIBS)
sshd$(EXEEXT): libssh.a $(LIBCOMPAT) $(SSHDOBJS)
$(LD) -o $@ $(SSHDOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHDLIBS) $(LIBS) $(GSSLIBS) $(K5LIBS)
@@ -214,11 +218,11 @@ sshd$(EXEEXT): libssh.a $(LIBCOMPAT) $(SSHDOBJS)
scp$(EXEEXT): $(LIBCOMPAT) libssh.a $(SCP_OBJS)
$(LD) -o $@ $(SCP_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
-ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHADD_OBJS)
- $(LD) -o $@ $(SSHADD_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHADD_OBJS) $(KEYCHAINOBJS)
+ $(LD) -o $@ $(SSHADD_OBJS) $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
-ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHAGENT_OBJS)
- $(LD) -o $@ $(SSHAGENT_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHAGENT_OBJS) $(KEYCHAINOBJS)
+ $(LD) -o $@ $(SSHAGENT_OBJS) $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYGEN_OBJS)
$(LD) -o $@ $(SSHKEYGEN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
@@ -386,7 +390,7 @@ install-files:
$(INSTALL) -m 0755 $(STRIP_OPT) ssh-keygen$(EXEEXT) $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) ssh-keyscan$(EXEEXT) $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) sshd$(EXEEXT) $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
- $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT)
+ $(INSTALL) -m 0711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) ssh-sk-helper$(EXEEXT) $(DESTDIR)$(SSH_SK_HELPER)$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
diff --git a/audit-bsm.c b/audit-bsm.c
index 0ba16c7..9953625 100644
--- a/audit-bsm.c
+++ b/audit-bsm.c
@@ -260,7 +260,12 @@ bsm_audit_record(int typ, char *string, au_event_t event_no)
pid_t pid = getpid();
AuditInfoTermID tid = ssh_bsm_tid;
- if (the_authctxt != NULL && the_authctxt->valid) {
+ if (the_authctxt == NULL) {
+ error("BSM audit: audit record internal error (NULL ctxt)");
+ abort();
+ }
+
+ if (the_authctxt->valid) {
uid = the_authctxt->pw->pw_uid;
gid = the_authctxt->pw->pw_gid;
}
diff --git a/auth.c b/auth.c
index 086b8eb..9460472 100644
--- a/auth.c
+++ b/auth.c
@@ -226,7 +226,7 @@ allowed_user(struct ssh *ssh, struct passwd * pw)
}
if (options.num_deny_groups > 0 || options.num_allow_groups > 0) {
/* Get the user's group access list (primary and supplementary) */
- if (ga_init(pw->pw_name, pw->pw_gid) == 0) {
+ if (ga_init(pw) == 0) {
logit("User %.100s from %.100s not allowed because "
"not in any group", pw->pw_name, hostname);
return 0;
diff --git a/authfd.c b/authfd.c
index 05fd454..c9c2471 100644
--- a/authfd.c
+++ b/authfd.c
@@ -177,6 +177,31 @@ ssh_request_reply(int sock, struct sshbuf *request, struct sshbuf *reply)
return 0;
}
+/*
+ * Adds identities using passphrases stored in the keychain. This call is not
+ * meant to be used by normal applications.
+ */
+int
+ssh_add_from_keychain(int sock)
+{
+ int r;
+ struct sshbuf *msg;
+ int type;
+
+ if ((msg = sshbuf_new()) == NULL)
+ return SSH_ERR_ALLOC_FAIL;
+ if ((r = sshbuf_put_u8(msg, SSH_AGENTC_ADD_FROM_KEYCHAIN)) != 0)
+ goto out;
+ if ((r = ssh_request_reply(sock, msg, msg)) != 0)
+ goto out;
+ if ((r = sshbuf_get_u8(msg, &type)) != 0)
+ goto out;
+ r = decode_reply(type);
+ out:
+ sshbuf_free(msg);
+ return r;
+}
+
/*
* Closes the agent socket if it should be closed (depends on how it was
* obtained). The argument must have been returned by
diff --git a/authfd.h b/authfd.h
index c3bf625..4342ba1 100644
--- a/authfd.h
+++ b/authfd.h
@@ -43,6 +43,9 @@ int ssh_agent_sign(int sock, const struct sshkey *key,
u_char **sigp, size_t *lenp,
const u_char *data, size_t datalen, const char *alg, u_int compat);
+int
+ssh_add_from_keychain(int sock);
+
/* Messages for the authentication agent connection. */
#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1
#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2
@@ -76,6 +79,9 @@ int ssh_agent_sign(int sock, const struct sshkey *key,
#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25
#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26
+/* keychain */
+#define SSH_AGENTC_ADD_FROM_KEYCHAIN 27
+
#define SSH_AGENT_CONSTRAIN_LIFETIME 1
#define SSH_AGENT_CONSTRAIN_CONFIRM 2
#define SSH_AGENT_CONSTRAIN_MAXSIGN 3
diff --git a/configure.ac b/configure.ac
index b689db4..74d9a69 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5317,10 +5317,40 @@ AC_CHECK_MEMBER([struct utmp.ut_line], [], [
#endif
])
+dnl Keychain support
+AC_ARG_WITH(keychain,
+ [ --with-keychain=apple Use Mac OS X Keychain],
+ [
+ case "$withval" in
+ apple|no)
+ KEYCHAIN=$withval
+ ;;
+ *)
+ AC_MSG_ERROR(invalid keychain type: $withval)
+ ;;
+ esac
+ ]
+)
+if test ! -z "$KEYCHAIN" -a "$KEYCHAIN" != "no"; then
+ case "$KEYCHAIN" in
+ apple)
+ AC_CHECK_HEADERS(Security/Security.h, [
+ CPPFLAGS="$CPPFLAGS -D__APPLE_KEYCHAIN__"
+ KEYCHAIN_LDFLAGS="-framework Security -framework CoreFoundation"
+ AC_SUBST(KEYCHAIN_LDFLAGS)
+ ],
+ AC_MSG_WARN([Security framework not found. Disabling Mac OS X Keychain support.]))
+ ;;
+ esac
+fi
+
dnl Adding -Werror to CFLAGS early prevents configure tests from running.
dnl Add now.
CFLAGS="$CFLAGS $werror_flags"
+AC_CHECK_FUNCS(copyfile)
+AC_CHECK_HEADERS(copyfile.h)
+
if test "x$ac_cv_func_getaddrinfo" != "xyes" ; then
TEST_SSH_IPV6=no
else
diff --git a/groupaccess.c b/groupaccess.c
index 80d3019..de7f57d 100644
--- a/groupaccess.c
+++ b/groupaccess.c
@@ -34,40 +34,68 @@
#include <string.h>
#include <limits.h>
+#ifdef __APPLE_MEMBERSHIP__
+#include <membership.h>
+#endif
+
#include "xmalloc.h"
#include "groupaccess.h"
#include "match.h"
#include "log.h"
+#ifdef __APPLE_MEMBERSHIP__
+// SPI for 5235093
+int32_t getgrouplist_2(const char *, gid_t, gid_t **);
+int32_t getgroupcount(const char *, gid_t);
+#endif
+
static int ngroups;
static char **groups_byname;
+#ifdef __APPLE_MEMBERSHIP__
+uuid_t u_uuid;
+#endif
/*
* Initialize group access list for user with primary (base) and
* supplementary groups. Return the number of groups in the list.
*/
int
-ga_init(const char *user, gid_t base)
+ga_init(struct passwd *pw)
{
- gid_t *groups_bygid;
+ gid_t *groups_bygid = NULL;
int i, j, retry = 0;
struct group *gr;
+#ifdef __APPLE_MEMBERSHIP__
+ if (0 != mbr_uid_to_uuid(pw->pw_uid, u_uuid))
+ return 0;
+#endif
+
if (ngroups > 0)
ga_free();
+#ifndef __APPLE_MEMBERSHIP__
ngroups = NGROUPS_MAX;
#if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX)
ngroups = MAX(NGROUPS_MAX, sysconf(_SC_NGROUPS_MAX));
#endif
groups_bygid = xcalloc(ngroups, sizeof(*groups_bygid));
- while (getgrouplist(user, base, groups_bygid, &ngroups) == -1) {
+#else
+ if (-1 == (ngroups = getgrouplist_2(pw->pw_name, pw->pw_gid,
+ &groups_bygid))) {
+ logit("getgrouplist_2 failed");
+ return 0;
+ }
+#endif
+#ifndef __APPLE_MEMBERSHIP__
+ while (getgrouplist(pw->pw_name, pw->pw_gid, groups_bygid, &ngroups) == -1) {
if (retry++ > 0)
fatal("getgrouplist: groups list too small");
groups_bygid = xreallocarray(groups_bygid, ngroups,
sizeof(*groups_bygid));
}
+#endif
groups_byname = xcalloc(ngroups, sizeof(*groups_byname));
for (i = 0, j = 0; i < ngroups; i++)
@@ -80,16 +108,32 @@ ga_init(const char *user, gid_t base)
/*
* Return 1 if one of user's groups is contained in groups.
* Return 0 otherwise. Use match_pattern() for string comparison.
+ * Use mbr_check_membership() for membership checking on Mac OS X.
*/
int
ga_match(char * const *groups, int n)
{
+#ifdef __APPLE_MEMBERSHIP__
+ int i, ismember = 0;
+ uuid_t g_uuid;
+ struct group *grp;
+
+ for (i = 0; i < n; i++) {
+ if ((grp = getgrnam(groups[i])) == NULL ||
+ (mbr_gid_to_uuid(grp->gr_gid, g_uuid) != 0) ||
+ (mbr_check_membership(u_uuid, g_uuid, &ismember) != 0))
+ return 0;
+ if (ismember)
+ return 1;
+ }
+#else
int i, j;
for (i = 0; i < ngroups; i++)
for (j = 0; j < n; j++)
if (match_pattern(groups_byname[i], groups[j]))
return 1;
+#endif
return 0;
}
diff --git a/groupaccess.h b/groupaccess.h
index 000578e..ddea117 100644
--- a/groupaccess.h
+++ b/groupaccess.h
@@ -27,7 +27,7 @@
#ifndef GROUPACCESS_H
#define GROUPACCESS_H
-int ga_init(const char *, gid_t);
+int ga_init(struct passwd *);
int ga_match(char * const *, int);
int ga_match_pattern_list(const char *);
void ga_free(void);
diff --git a/keychain.c b/keychain.c
new file mode 100644
index 0000000..5968c93
--- /dev/null
+++ b/keychain.c
@@ -0,0 +1,695 @@
+/*
+ * Copyright (c) 2007 Apple Inc. All rights reserved.
+ *
+ * @APPLE_BSD_LICENSE_HEADER_START@
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Inc. ("Apple") nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @APPLE_BSD_LICENSE_HEADER_END@
+ */
+
+#include "includes.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "xmalloc.h"
+#include "sshkey.h"
+#include "authfd.h"
+#include "authfile.h"
+
+#if defined(__APPLE_KEYCHAIN__)
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+
+/* Our Security/SecPassword.h is not yet API, so I will define the constants that I am using here. */
+int kSecPasswordGet = 1<<0; // Get password from keychain or user
+int kSecPasswordSet = 1<<1; // Set password (passed in if kSecPasswordGet not set, otherwise from user)
+int kSecPasswordFail = 1<<2; // Wrong password (ignore item in keychain and flag error)
+OSStatus SecGenericPasswordCreate(SecKeychainAttributeList *searchAttrList, SecKeychainAttributeList *itemAttrList, SecPasswordRef *itemRef);
+OSStatus SecPasswordAction(SecPasswordRef itemRef, CFTypeRef message, UInt32 flags, UInt32 *length, const void **data);
+OSStatus SecPasswordSetInitialAccess(SecPasswordRef itemRef, SecAccessRef accessRef);
+
+#endif
+
+/*
+ * Platform-specific helper functions.
+ */
+
+#if defined(__APPLE_KEYCHAIN__)
+
+static int get_boolean_preference(const char *key, int default_value,
+ int foreground)
+{
+ int value = default_value;
+ CFStringRef keyRef = NULL;
+ CFPropertyListRef valueRef = NULL;
+
+ keyRef = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8);
+ if (keyRef != NULL)
+ valueRef = CFPreferencesCopyAppValue(keyRef,
+ CFSTR("org.openbsd.openssh"));
+ if (valueRef != NULL)
+ if (CFGetTypeID(valueRef) == CFBooleanGetTypeID())
+ value = CFBooleanGetValue(valueRef);
+ else if (foreground)
+ fprintf(stderr, "Ignoring nonboolean %s preference.\n", key);
+
+ if (keyRef)
+ CFRelease(keyRef);
+ if (valueRef)
+ CFRelease(valueRef);
+
+ return value;
+}
+
+#endif
+
+/*
+ * Store the passphrase for a given identity in the keychain.
+ */
+void
+store_in_keychain(const char *filename, const char *passphrase)
+{
+
+#if defined(__APPLE_KEYCHAIN__)
+
+ /*
+ * store_in_keychain
+ * Mac OS X implementation
+ */
+
+ CFStringRef cfstr_relative_filename = NULL;
+ CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL;
+ CFStringRef cfstr_filename = NULL;
+ CFDataRef cfdata_filename = NULL;
+ CFIndex filename_len;
+ UInt8 *label = NULL;
+ UInt8 *utf8_filename;
+ OSStatus rv;
+ SecKeychainItemRef itemRef = NULL;
+ SecTrustedApplicationRef apps[] = {NULL, NULL, NULL};
+ CFArrayRef trustedlist = NULL;
+ SecAccessRef initialAccess = NULL;
+
+ /* Bail out if KeychainIntegration preference is -bool NO */
+ if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) {
+ fprintf(stderr, "Keychain integration is disabled.\n");
+ goto err;
+ }
+
+ /* Interpret filename with the correct encoding. */
+ if ((cfstr_relative_filename =
+ CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL)
+ {
+ fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n");
+ goto err;
+ }
+ if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL,
+ cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) {
+ fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n");
+ goto err;
+ }
+ if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) ==
+ NULL) {
+ fprintf(stderr, "CFURLCopyAbsoluteURL failed\n");
+ goto err;
+ }
+ if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename,
+ kCFURLPOSIXPathStyle)) == NULL) {
+ fprintf(stderr, "CFURLCopyFileSystemPath failed\n");
+ goto err;
+ }
+ if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL,
+ cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) {
+ fprintf(stderr, "CFStringCreateExternalRepresentation failed\n");
+ goto err;
+ }
+ filename_len = CFDataGetLength(cfdata_filename);
+ if ((label = xmalloc(filename_len + 5)) == NULL) {
+ fprintf(stderr, "xmalloc failed\n");
+ goto err;
+ }
+ memcpy(label, "SSH: ", 5);
+ utf8_filename = label + 5;
+ CFDataGetBytes(cfdata_filename, CFRangeMake(0, filename_len),
+ utf8_filename);
+
+ /* Check if we already have this passphrase. */
+ rv = SecKeychainFindGenericPassword(NULL, 3, "SSH", filename_len,
+ (char *)utf8_filename, NULL, NULL, &itemRef);
+ if (rv == errSecItemNotFound) {
+ /* Add a new keychain item. */
+ SecKeychainAttribute attrs[] = {
+ {kSecLabelItemAttr, filename_len + 5, label},
+ {kSecServiceItemAttr, 3, "SSH"},
+ {kSecAccountItemAttr, filename_len, utf8_filename}
+ };
+ SecKeychainAttributeList attrList =
+ {sizeof(attrs) / sizeof(attrs[0]), attrs};
+ if (SecTrustedApplicationCreateFromPath("/usr/bin/ssh-agent",
+ &apps[0]) != noErr ||
+ SecTrustedApplicationCreateFromPath("/usr/bin/ssh-add",
+ &apps[1]) != noErr ||
+ SecTrustedApplicationCreateFromPath("/usr/bin/ssh",
+ &apps[2]) != noErr) {
+ fprintf(stderr, "SecTrustedApplicationCreateFromPath failed\n");
+ goto err;
+ }
+ if ((trustedlist = CFArrayCreate(NULL, (const void **)apps,
+ sizeof(apps) / sizeof(apps[0]), &kCFTypeArrayCallBacks)) ==
+ NULL) {
+ fprintf(stderr, "CFArrayCreate failed\n");
+ goto err;
+ }
+ if (SecAccessCreate(cfstr_filename, trustedlist,
+ &initialAccess) != noErr) {
+ fprintf(stderr, "SecAccessCreate failed\n");
+ goto err;
+ }
+ if (SecKeychainItemCreateFromContent(
+ kSecGenericPasswordItemClass, &attrList, strlen(passphrase),
+ passphrase, NULL, initialAccess, NULL) == noErr)
+ fprintf(stderr, "Passphrase stored in keychain: %s\n", filename);
+ else
+ fprintf(stderr, "Could not create keychain item\n");
+ } else if (rv == noErr) {
+ /* Update an existing keychain item. */
+ if (SecKeychainItemModifyAttributesAndData(itemRef, NULL,
+ strlen(passphrase), passphrase) == noErr)
+ fprintf(stderr, "Passphrase updated in keychain: %s\n", filename);
+ else
+ fprintf(stderr, "Could not modify keychain item\n");
+ } else
+ fprintf(stderr, "Could not access keychain\n");
+
+err: /* Clean up. */
+ if (cfstr_relative_filename)
+ CFRelease(cfstr_relative_filename);
+ if (cfurl_relative_filename)
+ CFRelease(cfurl_relative_filename);
+ if (cfurl_filename)
+ CFRelease(cfurl_filename);
+ if (cfstr_filename)
+ CFRelease(cfstr_filename);
+ if (cfdata_filename)
+ CFRelease(cfdata_filename);
+ if (label)
+ free(label);
+ if (itemRef)
+ CFRelease(itemRef);
+ if (apps[0])
+ CFRelease(apps[0]);
+ if (apps[1])
+ CFRelease(apps[1]);
+ if (apps[2])
+ CFRelease(apps[2]);
+ if (trustedlist)
+ CFRelease(trustedlist);
+ if (initialAccess)
+ CFRelease(initialAccess);
+
+#else
+
+ /*
+ * store_in_keychain
+ * no keychain implementation
+ */
+
+ fprintf(stderr, "Keychain is not available on this system\n");
+
+#endif
+
+}
+
+/*
+ * Remove the passphrase for a given identity from the keychain.
+ */
+void
+remove_from_keychain(const char *filename)
+{
+
+#if defined(__APPLE_KEYCHAIN__)
+
+ /*
+ * remove_from_keychain
+ * Mac OS X implementation
+ */
+
+ CFStringRef cfstr_relative_filename = NULL;
+ CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL;
+ CFStringRef cfstr_filename = NULL;
+ CFDataRef cfdata_filename = NULL;
+ CFIndex filename_len;
+ const UInt8 *utf8_filename;
+ OSStatus rv;
+ SecKeychainItemRef itemRef = NULL;
+
+ /* Bail out if KeychainIntegration preference is -bool NO */
+ if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) {
+ fprintf(stderr, "Keychain integration is disabled.\n");
+ goto err;
+ }
+
+ /* Interpret filename with the correct encoding. */
+ if ((cfstr_relative_filename =
+ CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL)
+ {
+ fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n");
+ goto err;
+ }
+ if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL,
+ cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) {
+ fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n");
+ goto err;
+ }
+ if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) ==
+ NULL) {
+ fprintf(stderr, "CFURLCopyAbsoluteURL failed\n");
+ goto err;
+ }
+ if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename,
+ kCFURLPOSIXPathStyle)) == NULL) {
+ fprintf(stderr, "CFURLCopyFileSystemPath failed\n");
+ goto err;
+ }
+ if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL,
+ cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) {
+ fprintf(stderr, "CFStringCreateExternalRepresentation failed\n");
+ goto err;
+ }
+ filename_len = CFDataGetLength(cfdata_filename);
+ utf8_filename = CFDataGetBytePtr(cfdata_filename);
+
+ /* Check if we already have this passphrase. */
+ rv = SecKeychainFindGenericPassword(NULL, 3, "SSH", filename_len,
+ (const char *)utf8_filename, NULL, NULL, &itemRef);
+ if (rv == noErr) {
+ /* Remove the passphrase from the keychain. */
+ if (SecKeychainItemDelete(itemRef) == noErr)
+ fprintf(stderr, "Passphrase removed from keychain: %s\n", filename);
+ else
+ fprintf(stderr, "Could not remove keychain item\n");
+ } else if (rv != errSecItemNotFound)
+ fprintf(stderr, "Could not access keychain\n");
+
+err: /* Clean up. */
+ if (cfstr_relative_filename)
+ CFRelease(cfstr_relative_filename);
+ if (cfurl_relative_filename)
+ CFRelease(cfurl_relative_filename);
+ if (cfurl_filename)
+ CFRelease(cfurl_filename);
+ if (cfstr_filename)
+ CFRelease(cfstr_filename);
+ if (cfdata_filename)
+ CFRelease(cfdata_filename);
+ if (itemRef)
+ CFRelease(itemRef);
+
+#else
+
+ /*
+ * remove_from_keychain
+ * no keychain implementation
+ */
+
+ fprintf(stderr, "Keychain is not available on this system\n");
+
+#endif
+
+}
+
+/*
+ * Add identities to ssh-agent using passphrases stored in the keychain.
+ * Returns zero on success and nonzero on failure.
+ * add_identity is a callback into ssh-agent. It takes a filename and a
+ * passphrase, and attempts to add the identity to the agent. It returns
+ * zero on success and nonzero on failure.
+ */
+int
+add_identities_using_keychain(int (*add_identity)(const char *, const char *))
+{
+
+#if defined(__APPLE_KEYCHAIN__)
+
+ /*
+ * add_identities_using_keychain
+ * Mac OS X implementation
+ */
+
+ OSStatus rv;
+ SecKeychainSearchRef searchRef;
+ SecKeychainItemRef itemRef;
+ UInt32 length;
+ void *data;
+ CFIndex maxsize;
+
+ /* Bail out if KeychainIntegration preference is -bool NO */
+ if (get_boolean_preference("KeychainIntegration", 1, 0) == 0)
+ return 0;
+
+ /* Search for SSH passphrases in the keychain */
+ SecKeychainAttribute attrs[] = {
+ {kSecServiceItemAttr, 3, "SSH"}
+ };
+ SecKeychainAttributeList attrList =
+ {sizeof(attrs) / sizeof(attrs[0]), attrs};
+ if ((rv = SecKeychainSearchCreateFromAttributes(NULL,
+ kSecGenericPasswordItemClass, &attrList, &searchRef)) != noErr)
+ return 0;
+
+ /* Iterate through the search results. */
+ while ((rv = SecKeychainSearchCopyNext(searchRef, &itemRef)) == noErr) {
+ UInt32 tag = kSecAccountItemAttr;
+ UInt32 format = kSecFormatUnknown;
+ SecKeychainAttributeInfo info = {1, &tag, &format};
+ SecKeychainAttributeList *itemAttrList = NULL;
+ CFStringRef cfstr_filename = NULL;
+ char *filename = NULL;
+ char *passphrase = NULL;
+
+ /* Retrieve filename and passphrase. */
+ if ((rv = SecKeychainItemCopyAttributesAndData(itemRef, &info,
+ NULL, &itemAttrList, &length, &data)) != noErr)
+ goto err;
+ if (itemAttrList->count != 1)
+ goto err;
+ cfstr_filename = CFStringCreateWithBytes(NULL,
+ itemAttrList->attr->data, itemAttrList->attr->length,
+ kCFStringEncodingUTF8, true);
+ maxsize = CFStringGetMaximumSizeOfFileSystemRepresentation(
+ cfstr_filename);
+ if ((filename = xmalloc(maxsize)) == NULL)
+ goto err;
+ if (CFStringGetFileSystemRepresentation(cfstr_filename,
+ filename, maxsize) == false)
+ goto err;
+ if ((passphrase = xmalloc(length + 1)) == NULL)
+ goto err;
+ memcpy(passphrase, data, length);
+ passphrase[length] = '\0';
+
+ /* Add the identity. */
+ add_identity(filename, passphrase);
+
+err: /* Clean up. */
+ if (itemRef)
+ CFRelease(itemRef);
+ if (cfstr_filename)
+ CFRelease(cfstr_filename);
+ if (filename)
+ free(filename);
+ if (passphrase)
+ free(passphrase);
+ if (itemAttrList)
+ SecKeychainItemFreeAttributesAndData(itemAttrList,
+ data);
+ }
+
+ CFRelease(searchRef);
+
+ return 0;
+
+#else
+
+ /*
+ * add_identities_using_keychain
+ * no implementation
+ */
+
+ return 1;
+
+#endif
+
+}
+
+/*
+ * Prompt the user for a key's passphrase. The user will be offered the option
+ * of storing the passphrase in their keychain. Returns the passphrase
+ * (which the caller is responsible for freeing), or NULL if this function
+ * fails or is not implemented. If this function is not implemented, ssh will
+ * fall back on the standard read_passphrase function, and the user will need
+ * to use ssh-add -K to add their keys to the keychain.
+ */
+char *
+keychain_read_passphrase(const char *filename, int oAskPassGUI)
+{
+
+#if defined(__APPLE_KEYCHAIN__)
+
+ /*
+ * keychain_read_passphrase
+ * Mac OS X implementation
+ */
+
+ CFStringRef cfstr_relative_filename = NULL;
+ CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL;
+ CFStringRef cfstr_filename = NULL;
+ CFDataRef cfdata_filename = NULL;
+ CFIndex filename_len;
+ UInt8 *label = NULL;
+ UInt8 *utf8_filename;
+ SecPasswordRef passRef = NULL;
+ SecTrustedApplicationRef apps[] = {NULL, NULL, NULL};
+ CFArrayRef trustedlist = NULL;
+ SecAccessRef initialAccess = NULL;
+ CFURLRef path = NULL;
+ CFStringRef pathFinal = NULL;
+ CFURLRef bundle_url = NULL;
+ CFBundleRef bundle = NULL;
+ CFStringRef promptTemplate = NULL, prompt = NULL;
+ UInt32 length;
+ const void *data;
+ int sock = -1;
+ char *result = NULL;
+
+ /* Bail out if KeychainIntegration preference is -bool NO */
+ if (get_boolean_preference("KeychainIntegration", 1, 1) == 0)
+ goto err;
+
+ /* Bail out if the user set AskPassGUI preference to -bool NO */
+ if (get_boolean_preference("AskPassGUI", 1, 1) == 0 || oAskPassGUI == 0)
+ goto err;
+
+ /* Bail out if we can't communicate with ssh-agent */
+ if ((ssh_get_authentication_socket(&sock)) != 0)
+ goto err;
+
+ /* Interpret filename with the correct encoding. */
+ if ((cfstr_relative_filename =
+ CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL)
+ {
+ fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n");
+ goto err;
+ }
+ if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL,
+ cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) {
+ fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n");
+ goto err;
+ }
+ if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) ==
+ NULL) {
+ fprintf(stderr, "CFURLCopyAbsoluteURL failed\n");
+ goto err;
+ }
+ if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename,
+ kCFURLPOSIXPathStyle)) == NULL) {
+ fprintf(stderr, "CFURLCopyFileSystemPath failed\n");
+ goto err;
+ }
+ if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL,
+ cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) {
+ fprintf(stderr, "CFStringCreateExternalRepresentation failed\n");
+ goto err;
+ }
+ filename_len = CFDataGetLength(cfdata_filename);
+ if ((label = xmalloc(filename_len + 5)) == NULL) {
+ fprintf(stderr, "xmalloc failed\n");
+ goto err;
+ }
+ memcpy(label, "SSH: ", 5);
+ utf8_filename = label + 5;
+ CFDataGetBytes(cfdata_filename, CFRangeMake(0, filename_len),
+ utf8_filename);
+
+ /* Build a SecPasswordRef. */
+ SecKeychainAttribute searchAttrs[] = {
+ {kSecServiceItemAttr, 3, "SSH"},
+ {kSecAccountItemAttr, filename_len, utf8_filename}
+ };
+ SecKeychainAttributeList searchAttrList =
+ {sizeof(searchAttrs) / sizeof(searchAttrs[0]), searchAttrs};
+ SecKeychainAttribute attrs[] = {
+ {kSecLabelItemAttr, filename_len + 5, label},
+ {kSecServiceItemAttr, 3, "SSH"},
+ {kSecAccountItemAttr, filename_len, utf8_filename}
+ };
+ SecKeychainAttributeList attrList =
+ {sizeof(attrs) / sizeof(attrs[0]), attrs};
+ if (SecGenericPasswordCreate(&searchAttrList, &attrList, &passRef) !=
+ noErr) {
+ fprintf(stderr, "SecGenericPasswordCreate failed\n");
+ goto err;
+ }
+ if (SecTrustedApplicationCreateFromPath("/usr/bin/ssh-agent", &apps[0])
+ != noErr ||
+ SecTrustedApplicationCreateFromPath("/usr/bin/ssh-add", &apps[1])
+ != noErr ||
+ SecTrustedApplicationCreateFromPath("/usr/bin/ssh", &apps[2])
+ != noErr) {
+ fprintf(stderr, "SecTrustedApplicationCreateFromPath failed\n");
+ goto err;
+ }
+ if ((trustedlist = CFArrayCreate(NULL, (const void **)apps,
+ sizeof(apps) / sizeof(apps[0]), &kCFTypeArrayCallBacks)) == NULL) {
+ fprintf(stderr, "CFArrayCreate failed\n");
+ goto err;
+ }
+ if (SecAccessCreate(cfstr_filename, trustedlist, &initialAccess)
+ != noErr) {
+ fprintf(stderr, "SecAccessCreate failed\n");
+ goto err;
+ }
+ if (SecPasswordSetInitialAccess(passRef, initialAccess) != noErr) {
+ fprintf(stderr, "SecPasswordSetInitialAccess failed\n");
+ goto err;
+ }
+
+ /* Request the passphrase from the user. */
+ if ((path = CFURLCreateFromFileSystemRepresentation(NULL,
+ (UInt8 *)filename, strlen(filename), false)) == NULL) {
+ fprintf(stderr, "CFURLCreateFromFileSystemRepresentation failed\n");
+ goto err;
+ }
+ if ((pathFinal = CFURLCopyLastPathComponent(path)) == NULL) {
+ fprintf(stderr, "CFURLCopyLastPathComponent failed\n");
+ goto err;
+ }
+ if (!((bundle_url = CFURLCreateWithFileSystemPath(NULL,
+ CFSTR("/System/Library/CoreServices/"), kCFURLPOSIXPathStyle, true))
+ != NULL && (bundle = CFBundleCreate(NULL, bundle_url)) != NULL &&
+ (promptTemplate = CFCopyLocalizedStringFromTableInBundle(
+ CFSTR("Enter your password for the SSH key \"%@\"."),
+ CFSTR("OpenSSH"), bundle, "Text of the dialog asking the user for"
+ "their passphrase. The %@ will be replaced with the filename of a"
+ "specific key.")) != NULL) &&
+ (promptTemplate = CFStringCreateCopy(NULL,
+ CFSTR("Enter your password for the SSH key \"%@\"."))) == NULL) {
+ fprintf(stderr, "CFStringCreateCopy failed\n");
+ goto err;
+ }
+ if ((prompt = CFStringCreateWithFormat(NULL, NULL, promptTemplate,
+ pathFinal)) == NULL) {
+ fprintf(stderr, "CFStringCreateWithFormat failed\n");
+ goto err;
+ }
+ switch (SecPasswordAction(passRef, prompt,
+ kSecPasswordGet|kSecPasswordFail, &length, &data)) {
+ case noErr:
+ result = xmalloc(length + 1);
+ memcpy(result, data, length);
+ result[length] = '\0';
+
+ /* Save password in keychain if requested. */
+ if (noErr != SecPasswordAction(passRef, CFSTR(""), kSecPasswordSet, &length, &data))
+ fprintf(stderr, "Saving password to keychain failed\n");
+
+ /* Add password to agent. */
+ char *comment = NULL;
+ struct sshkey *private;
+
+ if (sshkey_load_private(filename, result, &private, &comment) != 0)
+ break;
+ if (ssh_add_identity_constrained(sock, private, comment, 0, 0, 0, NULL))
+ fprintf(stderr, "Identity added: %s (%s)\n", filename, comment);
+ else
+ fprintf(stderr, "Could not add identity: %s\n", filename);
+ free(comment);
+ sshkey_free(private);
+ break;
+ case errAuthorizationCanceled:
+ result = xmalloc(1);
+ *result = '\0';
+ break;
+ default:
+ goto err;
+ }
+
+err: /* Clean up. */
+ if (cfstr_relative_filename)
+ CFRelease(cfstr_relative_filename);
+ if (cfurl_relative_filename)
+ CFRelease(cfurl_relative_filename);
+ if (cfurl_filename)
+ CFRelease(cfurl_filename);
+ if (cfstr_filename)
+ CFRelease(cfstr_filename);
+ if (cfdata_filename)
+ CFRelease(cfdata_filename);
+ if (label)
+ free(label);
+ if (passRef)
+ CFRelease(passRef);
+ if (apps[0])
+ CFRelease(apps[0]);
+ if (apps[1])
+ CFRelease(apps[1]);
+ if (apps[2])
+ CFRelease(apps[2]);
+ if (trustedlist)
+ CFRelease(trustedlist);
+ if (initialAccess)
+ CFRelease(initialAccess);
+ if (path)
+ CFRelease(path);
+ if (pathFinal)
+ CFRelease(pathFinal);
+ if (bundle_url)
+ CFRelease(bundle_url);
+ if (bundle)
+ CFRelease(bundle);
+ if (promptTemplate)
+ CFRelease(promptTemplate);
+ if (prompt)
+ CFRelease(prompt);
+ if (sock != -1)
+ ssh_close_authentication_socket(sock);
+
+ return result;
+
+#else
+
+ /*
+ * keychain_read_passphrase
+ * no implementation
+ */
+
+ return NULL;
+
+#endif
+
+}
diff --git a/keychain.h b/keychain.h
new file mode 100644
index 0000000..9bb8c22
--- /dev/null
+++ b/keychain.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 Apple Inc. All rights reserved.
+ *
+ * @APPLE_BSD_LICENSE_HEADER_START@
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Inc. ("Apple") nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @APPLE_BSD_LICENSE_HEADER_END@
+ */
+
+/*
+ * KEYCHAIN indicates that keychain functionality is present.
+ * KEYCHAIN_* indicates the implementation to use, and implies KEYCHAIN.
+ */
+#if defined(__APPLE_KEYCHAIN__)
+#define KEYCHAIN
+#endif
+
+void store_in_keychain(const char *filename, const char *passphrase);
+void remove_from_keychain(const char *filename);
+int add_identities_using_keychain(
+ int (*add_identity)(const char *, const char *));
+char *keychain_read_passphrase(const char *filename, int oAskPassGUI);
diff --git a/readconf.c b/readconf.c
index f3cac6b..710232b 100644
--- a/readconf.c
+++ b/readconf.c
@@ -169,6 +169,9 @@ typedef enum {
oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass,
oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
+#ifdef __APPLE_KEYCHAIN__
+ oAskPassGUI,
+#endif
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
oPubkeyAcceptedKeyTypes, oCASignatureAlgorithms, oProxyJump,
@@ -310,6 +313,9 @@ static struct {
{ "ignoreunknown", oIgnoreUnknown },
{ "proxyjump", oProxyJump },
{ "securitykeyprovider", oSecurityKeyProvider },
+#ifdef __APPLE_KEYCHAIN__
+ { "askpassgui", oAskPassGUI },
+#endif
{ NULL, oBadOption }
};
@@ -1659,6 +1665,12 @@ parse_keytypes:
charptr = &options->ignored_unknown;
goto parse_string;
+#ifdef __APPLE_KEYCHAIN__
+ case oAskPassGUI:
+ intptr = &options->ask_pass_gui;
+ goto parse_flag;
+#endif
+
case oProxyUseFdpass:
intptr = &options->proxy_use_fdpass;
goto parse_flag;
@@ -1986,6 +1998,9 @@ initialize_options(Options * options)
options->request_tty = -1;
options->proxy_use_fdpass = -1;
options->ignored_unknown = NULL;
+#ifdef __APPLE_KEYCHAIN__
+ options->ask_pass_gui = -1;
+#endif
options->num_canonical_domains = 0;
options->num_permitted_cnames = 0;
options->canonicalize_max_dots = -1;
@@ -2162,6 +2177,10 @@ fill_default_options(Options * options)
options->ip_qos_bulk = IPTOS_DSCP_CS1;
if (options->request_tty == -1)
options->request_tty = REQUEST_TTY_AUTO;
+#ifdef __APPLE_KEYCHAIN__
+ if (options->ask_pass_gui == -1)
+ options->ask_pass_gui = 1;
+#endif
if (options->proxy_use_fdpass == -1)
options->proxy_use_fdpass = 0;
if (options->canonicalize_max_dots == -1)
diff --git a/readconf.h b/readconf.h
index feedb3d..42c1a18 100644
--- a/readconf.h
+++ b/readconf.h
@@ -168,6 +168,10 @@ typedef struct {
char *jump_extra;
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
+
+#ifdef __APPLE_KEYCHAIN__
+ int ask_pass_gui;
+#endif
} Options;
#define SSH_CANONICALISE_NO 0
diff --git a/servconf.c b/servconf.c
index 70f5f73..b6cc5fc 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1019,7 +1019,7 @@ match_cfg_line_group(const char *grps, int line, const char *user)
if ((pw = getpwnam(user)) == NULL) {
debug("Can't match group at line %d because user %.100s does "
"not exist", line, user);
- } else if (ga_init(pw->pw_name, pw->pw_gid) == 0) {
+ } else if (ga_init(pw) == 0) {
debug("Can't Match group because user %.100s not in any group "
"at line %d", user, line);
} else if (ga_match_pattern_list(grps) != 1) {
diff --git a/session.c b/session.c
index 8c0e54f..96f9877 100644
--- a/session.c
+++ b/session.c
@@ -1945,8 +1945,10 @@ session_pty_req(struct ssh *ssh, Session *s)
if ((r = sshpkt_get_end(ssh)) != 0)
sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
+#ifndef __APPLE_PRIVPTY__
if (!use_privsep)
pty_setowner(s->pw, s->tty);
+#endif
/* Set window size from the packet. */
pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel);
@@ -2281,9 +2283,11 @@ session_pty_cleanup2(Session *s)
if (s->pid != 0)
record_logout(s->pid, s->tty, s->pw->pw_name);
+#ifndef __APPLE_PRIVPTY__
/* Release the pseudo-tty. */
if (getuid() == 0)
pty_release(s->tty);
+#endif
/*
* Close the server side of the socket pairs. We must do this after
diff --git a/ssh-add.1 b/ssh-add.1
index 58d4213..b9ba6d9 100644
--- a/ssh-add.1
+++ b/ssh-add.1
@@ -43,7 +43,7 @@
.Nd adds private key identities to the OpenSSH authentication agent
.Sh SYNOPSIS
.Nm ssh-add
-.Op Fl cDdKkLlqvXx
+.Op Fl cDdkKALlqvXx
.Op Fl E Ar fingerprint_hash
.Op Fl S Ar provider
.Op Fl t Ar life
@@ -124,7 +124,7 @@ The default is
.It Fl e Ar pkcs11
Remove keys provided by the PKCS#11 shared library
.Ar pkcs11 .
-.It Fl K
+.It Fl F
Load resident keys from a FIDO authenticator.
.It Fl k
When loading keys into or deleting keys from the agent, process plain private
@@ -134,6 +134,14 @@ Lists public key parameters of all identities currently represented
by the agent.
.It Fl l
Lists fingerprints of all identities currently represented by the agent.
+.It Fl K
+When adding identities, each passphrase will also be stored in the user's keychain. When removing identities
+with
+.Fl d ,
+each passphrase will be removed from it.
+.It Fl A
+Add identities to the agent using any passphrase stored in the user's keychain.
+X keychain.
.It Fl q
Be quiet after a successful operation.
.It Fl S Ar provider
diff --git a/ssh-add.c b/ssh-add.c
index 8057eb1..e977b43 100644
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -67,6 +67,7 @@
#include "ssherr.h"
#include "digest.h"
#include "ssh-sk.h"
+#include "keychain.h"
/* argv0 */
extern char *__progname;
@@ -112,12 +113,25 @@ clear_pass(void)
}
static int
-delete_file(int agent_fd, const char *filename, int key_only, int qflag)
+add_from_keychain(int sock)
+{
+ if (ssh_add_from_keychain(sock) == 0)
+ return -1;
+
+ fprintf(stderr, "Added keychain identities.\n");
+ return 0;
+}
+
+static int
+delete_file(int agent_fd, int keychain, const char *filename, int key_only, int qflag)
{
struct sshkey *public, *cert = NULL;
char *certpath = NULL, *comment = NULL;
int r, ret = -1;
+ if (keychain)
+ remove_from_keychain(filename);
+
if ((r = sshkey_load_public(filename, &public, &comment)) != 0) {
printf("Bad key file %s: %s\n", filename, ssh_err(r));
return -1;
@@ -194,7 +208,7 @@ delete_all(int agent_fd, int qflag)
}
static int
-add_file(int agent_fd, const char *filename, int key_only, int qflag,
+add_file(int agent_fd, int keychain, const char *filename, int key_only, int qflag,
const char *skprovider)
{
struct sshkey *private, *cert;
@@ -240,6 +254,10 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
filename, ssh_err(r));
goto fail_load;
}
+
+ if (keychain && private != NULL)
+ store_in_keychain(filename, "");
+
/* try last */
if (private == NULL && pass != NULL) {
if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private,
@@ -248,6 +266,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
filename, ssh_err(r));
goto fail_load;
}
+ if (keychain && private != NULL)
+ store_in_keychain(filename, pass);
}
if (private == NULL) {
/* clear passphrase since it did not work */
@@ -259,8 +279,13 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
if (strcmp(pass, "") == 0)
goto fail_load;
if ((r = sshkey_parse_private_fileblob(keyblob, pass,
- &private, &comment)) == 0)
+ &private, &comment)) == 0) {
+ if (private != NULL) {
+ if (keychain)
+ store_in_keychain(filename, pass);
+ }
break;
+ }
else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
fprintf(stderr,
"Error loading key \"%s\": %s\n",
@@ -588,14 +613,14 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
}
static int
-do_file(int agent_fd, int deleting, int key_only, char *file, int qflag,
+do_file(int agent_fd, int deleting, int keychain, int key_only, char *file, int qflag,
const char *skprovider)
{
if (deleting) {
- if (delete_file(agent_fd, file, key_only, qflag) == -1)
+ if (delete_file(agent_fd, keychain, file, key_only, qflag) == -1)
return -1;
} else {
- if (add_file(agent_fd, file, key_only, qflag, skprovider) == -1)
+ if (add_file(agent_fd, keychain, file, key_only, qflag, skprovider) == -1)
return -1;
}
return 0;
@@ -614,6 +639,11 @@ usage(void)
" ssh-add -e pkcs11\n"
" ssh-add -T pubkey ...\n"
);
+#ifdef __APPLE_KEYCHAIN__
+ fprintf(stderr, " -A Add all identities stored in your keychain.\n");
+ fprintf(stderr, " -K Store passphrases in your keychain.\n");
+ fprintf(stderr, " With -d, remove passphrases from your keychain.\n");
+#endif
}
int
@@ -625,6 +655,7 @@ main(int argc, char **argv)
char *pkcs11provider = NULL, *skprovider = NULL;
int r, i, ch, deleting = 0, ret = 0, key_only = 0, do_download = 0;
int xflag = 0, lflag = 0, Dflag = 0, qflag = 0, Tflag = 0;
+ int keychain = 0;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
LogLevel log_level = SYSLOG_LEVEL_INFO;
@@ -653,7 +684,11 @@ main(int argc, char **argv)
skprovider = getenv("SSH_SK_PROVIDER");
- while ((ch = getopt(argc, argv, "vkKlLcdDTxXE:e:M:m:qs:S:t:")) != -1) {
+#ifdef __APPLE_KEYCHAIN__
+ while ((ch = getopt(argc, argv, "vkKFAlLcdDTxXE:e:M:m:qs:S:t:")) != -1) {
+#else
+ while ((ch = getopt(argc, argv, "vkFlLcdDTxXE:e:M:m:qs:S:t:")) != -1) {
+#endif
switch (ch) {
case 'v':
if (log_level == SYSLOG_LEVEL_INFO)
@@ -669,7 +704,16 @@ main(int argc, char **argv)
case 'k':
key_only = 1;
break;
+#ifdef __APPLE_KEYCHAIN__
case 'K':
+ keychain = 1;
+ break;
+ case 'A':
+ if (add_from_keychain(agent_fd) == -1)
+ ret = 1;
+ goto done;
+#endif
+ case 'F':
do_download = 1;
break;
case 'l':
@@ -802,7 +846,7 @@ main(int argc, char **argv)
default_files[i]);
if (stat(buf, &st) == -1)
continue;
- if (do_file(agent_fd, deleting, key_only, buf,
+ if (do_file(agent_fd, deleting, keychain, key_only, buf,
qflag, skprovider) == -1)
ret = 1;
else
@@ -812,7 +856,7 @@ main(int argc, char **argv)
ret = 1;
} else {
for (i = 0; i < argc; i++) {
- if (do_file(agent_fd, deleting, key_only,
+ if (do_file(agent_fd, deleting, keychain, key_only,
argv[i], qflag, skprovider) == -1)
ret = 1;
}
diff --git a/ssh-agent.c b/ssh-agent.c
index 7eb6f0d..67b8dfb 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -74,12 +74,16 @@
#ifdef HAVE_UTIL_H
# include <util.h>
#endif
+#ifdef __APPLE_LAUNCHD__
+#include <launch.h>
+#endif
#include "xmalloc.h"
#include "ssh.h"
#include "sshbuf.h"
#include "sshkey.h"
#include "authfd.h"
+#include "authfile.h"
#include "compat.h"
#include "log.h"
#include "misc.h"
@@ -91,6 +95,7 @@
#include "pathnames.h"
#include "ssh-pkcs11.h"
#include "sk-api.h"
+#include "keychain.h"
#ifndef DEFAULT_PROVIDER_WHITELIST
# define DEFAULT_PROVIDER_WHITELIST "/usr/lib*/*,/usr/local/lib*/*"
@@ -768,6 +773,60 @@ send:
}
#endif /* ENABLE_PKCS11 */
+static int
+add_identity_callback(const char *filename, const char *passphrase)
+{
+ struct sshkey *k;
+ int r;
+
+ if ((r = sshkey_load_private(filename, passphrase, &k, NULL)) != NULL)
+ return r;
+ switch (k->type) {
+ case KEY_RSA:
+ if (RSA_blinding_on(k->rsa, NULL) != 1) {
+ sshkey_free(k);
+ return 1;
+ }
+ break;
+ }
+ if (lookup_identity(k) == NULL) {
+ Identity *id = xmalloc(sizeof(Identity));
+ id->key = k;
+ id->comment = xstrdup(filename);
+ if (id->comment == NULL) {
+ sshkey_free(k);
+ return 1;
+ }
+ id->death = 0;
+ id->confirm = 0;
+ TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
+ idtab->nentries++;
+ } else {
+ sshkey_free(k);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+process_add_from_keychain(SocketEntry *e)
+{
+ int success;
+
+ success = add_identities_using_keychain(&add_identity_callback);
+
+ /* e will be NULL when ssh-agent adds keys on its own at startup */
+ if (e) {
+ int r;
+
+ if ((r = sshbuf_put_u32(e->output, 1)) != 0 ||
+ (r = sshbuf_put_u8(e->output, success ?
+ SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE)) != 0)
+ fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ }
+}
+
/* dispatch incoming messages */
static int
@@ -860,6 +919,9 @@ process_message(u_int socknum)
process_remove_smartcard_key(e);
break;
#endif /* ENABLE_PKCS11 */
+ case SSH_AGENTC_ADD_FROM_KEYCHAIN:
+ process_add_from_keychain(e);
+ break;
default:
/* Unknown message. Respond with failure. */
error("Unknown message %d", type);
@@ -1179,7 +1241,11 @@ usage(void)
int
main(int ac, char **av)
{
+#ifdef __APPLE_LAUNCHD__
+ int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0, l_flag = 0;
+#else
int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0;
+#endif
int sock, fd, ch, result, saved_errno;
char *shell, *format, *pidstr, *agentsocket = NULL;
#ifdef HAVE_SETRLIMIT
@@ -1213,7 +1279,11 @@ main(int ac, char **av)
__progname = ssh_get_progname(av[0]);
seed_rng();
+#ifdef __APPLE_LAUNCHD__
+ while ((ch = getopt(ac, av, "cDdklsE:a:P:t:")) != -1) {
+#else
while ((ch = getopt(ac, av, "cDdksE:a:P:t:")) != -1) {
+#endif
switch (ch) {
case 'E':
fingerprint_hash = ssh_digest_alg_by_name(optarg);
@@ -1233,6 +1303,11 @@ main(int ac, char **av)
fatal("-P option already specified");
provider_whitelist = xstrdup(optarg);
break;
+#ifdef __APPLE_LAUNCHD__
+ case 'l':
+ l_flag++;
+ break;
+#endif
case 's':
if (c_flag)
usage();
@@ -1264,7 +1339,11 @@ main(int ac, char **av)
ac -= optind;
av += optind;
+#ifdef __APPLE_LAUNCHD__
+ if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag || l_flag))
+#else
if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag))
+#endif
usage();
if (provider_whitelist == NULL)
@@ -1335,6 +1414,53 @@ main(int ac, char **av)
* Create socket early so it will exist before command gets run from
* the parent.
*/
+#ifdef __APPLE_LAUNCHD__
+ if (l_flag) {
+ launch_data_t resp, msg, tmp;
+ size_t listeners_i;
+
+ msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
+
+ resp = launch_msg(msg);
+
+ if (NULL == resp) {
+ perror("launch_msg");
+ exit(1);
+ }
+ launch_data_free(msg);
+ switch (launch_data_get_type(resp)) {
+ case LAUNCH_DATA_ERRNO:
+ errno = launch_data_get_errno(resp);
+ perror("launch_msg response");
+ exit(1);
+ case LAUNCH_DATA_DICTIONARY:
+ break;
+ default:
+ fprintf(stderr, "launch_msg unknown response");
+ exit(1);
+ }
+ tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS);
+
+ if (NULL == tmp) {
+ fprintf(stderr, "no sockets\n");
+ exit(1);
+ }
+
+ tmp = launch_data_dict_lookup(tmp, "Listeners");
+
+ if (NULL == tmp) {
+ fprintf(stderr, "no known listeners\n");
+ exit(1);
+ }
+
+ for (listeners_i = 0; listeners_i < launch_data_array_get_count(tmp); listeners_i++) {
+ launch_data_t obj_at_ind = launch_data_array_get_index(tmp, listeners_i);
+ new_socket(AUTH_SOCKET, launch_data_get_fd(obj_at_ind));
+ }
+
+ launch_data_free(resp);
+ } else {
+#endif
prev_mask = umask(0177);
sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0);
if (sock < 0) {
@@ -1343,6 +1469,14 @@ main(int ac, char **av)
cleanup_exit(1);
}
umask(prev_mask);
+#ifdef __APPLE_LAUNCHD__
+ }
+#endif
+
+#ifdef __APPLE_LAUNCHD__
+ if (l_flag)
+ goto skip2;
+#endif
/*
* Fork, and have the parent execute the command, if any, or present
@@ -1420,6 +1554,7 @@ skip:
pkcs11_init(0);
#endif
new_socket(AUTH_SOCKET, sock);
+skip2:
if (ac > 0)
parent_alive_interval = 10;
idtab_init();
@@ -1432,6 +1567,10 @@ skip:
fatal("%s: pledge: %s", __progname, strerror(errno));
platform_pledge_agent();
+#ifdef KEYCHAIN
+ process_add_from_keychain(NULL);
+#endif
+
while (1) {
prepare_poll(&pfd, &npfd, &timeout, maxfds);
result = poll(pfd, npfd, timeout);
diff --git a/ssh-keysign.8 b/ssh-keysign.8
index 73b6239..0406ba0 100644
--- a/ssh-keysign.8
+++ b/ssh-keysign.8
@@ -72,6 +72,9 @@ accessible to others.
Since they are readable only by root,
.Nm
must be set-uid root if host-based authentication is used.
+Note that
+.Nm
+is not set-uid by default on Mac OS X.
.Pp
.It Pa /etc/ssh/ssh_host_dsa_key-cert.pub
.It Pa /etc/ssh/ssh_host_ecdsa_key-cert.pub
diff --git a/sshconnect2.c b/sshconnect2.c
index af00fb3..aded8b8 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -72,6 +72,7 @@
#include "hostfile.h"
#include "ssherr.h"
#include "utf8.h"
+#include "keychain.h"
#include "ssh-sk.h"
#include "sk-api.h"
@@ -1464,6 +1465,10 @@ load_identity_file(Identity *id)
if (i == 0)
passphrase = "";
else {
+#ifdef __APPLE_KEYCHAIN__
+ passphrase = keychain_read_passphrase(id->filename, options.ask_pass_gui);
+ if (passphrase == NULL)
+#endif
passphrase = read_passphrase(prompt, 0);
if (*passphrase == '\0') {
debug2("no passphrase given, try next key");
diff --git a/sshd.c b/sshd.c
index 60b2aaf..9263aac 100644
--- a/sshd.c
+++ b/sshd.c
@@ -2226,6 +2226,12 @@ main(int ac, char **av)
audit_event(ssh, SSH_AUTH_SUCCESS);
#endif
+#ifdef USE_PAM
+ if (options.use_pam) {
+ do_pam_setcred(1);
+ do_pam_session(ssh);
+ }
+#endif
#ifdef GSSAPI
if (options.gss_authentication) {
temporarily_use_uid(authctxt->pw);
@@ -2233,12 +2239,7 @@ main(int ac, char **av)
restore_uid();
}
#endif
-#ifdef USE_PAM
- if (options.use_pam) {
- do_pam_setcred(1);
do_pam_session(ssh);
- }
-#endif
/*
* In privilege separation, we fork another child and prepare
--
2.25.0
From 8bf8ddf9f2ef37ed8fb8cc565023a800576cb683 Mon Sep 17 00:00:00 2001
From: Leon Klingele <git@leonklingele.de>
Date: Thu, 18 Apr 2019 01:52:05 +0200
Subject: [PATCH 2/2] apple-sandbox-named-external
---
sandbox-darwin.c | 8 ++++++++
sshd.c | 7 +++++++
2 files changed, 15 insertions(+)
diff --git a/sandbox-darwin.c b/sandbox-darwin.c
index 59b4d28..0639f63 100644
--- a/sandbox-darwin.c
+++ b/sandbox-darwin.c
@@ -63,8 +63,16 @@ ssh_sandbox_child(struct ssh_sandbox *box)
struct rlimit rl_zero;
debug3("%s: starting Darwin sandbox", __func__);
+#ifdef __APPLE_SANDBOX_NAMED_EXTERNAL__
+#ifndef SANDBOX_NAMED_EXTERNAL
+#define SANDBOX_NAMED_EXTERNAL (0x3)
+#endif
+ if (sandbox_init("@PREFIX@/share/openssh/org.openssh.sshd.sb",
+ SANDBOX_NAMED_EXTERNAL, &errmsg) == -1)
+#else
if (sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED,
&errmsg) == -1)
+#endif
fatal("%s: sandbox_init: %s", __func__, errmsg);
/*
diff --git a/sshd.c b/sshd.c
index 9263aac..ed66e49 100644
--- a/sshd.c
+++ b/sshd.c
@@ -539,10 +539,17 @@ privsep_preauth(struct ssh *ssh)
/* Arrange for logging to be sent to the monitor */
set_log_handler(mm_log_handler, pmonitor);
+#ifdef __APPLE_SANDBOX_NAMED_EXTERNAL__
+ /* We need to do this before we chroot() so we can read sshd.sb */
+ if (box != NULL)
+ ssh_sandbox_child(box);
+#endif
privsep_preauth_child();
setproctitle("%s", "[net]");
+#ifndef __APPLE_SANDBOX_NAMED_EXTERNAL__
if (box != NULL)
ssh_sandbox_child(box);
+#endif
return 0;
}
--
2.25.0
;; Copyright (c) 2008 Apple Inc. All Rights reserved.
;;
;; sshd - profile for privilege separated children
;;
;; WARNING: The sandbox rules in this file currently constitute
;; Apple System Private Interface and are subject to change at any time and
;; without notice.
;;
(version 1)
(deny default)
(allow file-chroot)
(allow file-read-metadata (literal "/var"))
(allow sysctl-read)
(allow mach-per-user-lookup)
(allow mach-lookup
(global-name "com.apple.system.notification_center")
(global-name "com.apple.system.opendirectoryd.libinfo")
(global-name "com.apple.system.opendirectoryd.libinfo") ;; duplicate name as a work-around for 19978803
(global-name "com.apple.system.logger"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.