--- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ b/keychain.m 2019-10-24 18:24:42.000000000 +0200 @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2007-2016 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 + +#if ((!(defined (MAC_OS_X_VERSION_10_6))) || (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6)) +/* + * Older versions of sys/acl.h are (arguably) buggy in that the + * __APPLE_API_STRICT_CONFORMANCE macro - that we need for proper sandbox code + * compilation on older systems (10.7-) - affects (or rather restricts) the + * inclusion of macros in sys/kauth.h. + * + * These macros are then used unconditionally and everything blows up if the + * former is set. + * + * This is not a problem with newer sys/acl.h versions, since these define + * internal macros and hence don't rely on the KAUTH_* macros any longer (even + * though they still include sys/kauth.h, which strictly speaking is redundant + * in such a case). + * + * Since we don't need to work around any sandbox header bugs in the keychain + * integration, let's just drop the former macro. + */ +# ifdef __APPLE_API_STRICT_CONFORMANCE +# undef __APPLE_API_STRICT_CONFORMANCE +# endif /* defined (__APPLE_API_STRICT_CONFORMANCE) */ +#endif /* ((!(defined (MAC_OS_X_VERSION_10_6))) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)) */ + +#import +#import +#import "SecItemPriv-shim.h" +#include +#include + +#include "xmalloc.h" +#include "sshkey.h" +#include "ssherr.h" +#include "authfile.h" +#include "openbsd-compat/openbsd-compat.h" +#include "log.h" + +char *keychain_read_passphrase(const char *filename) +{ + OSStatus ret = errSecSuccess; + NSString *accountString = [NSString stringWithUTF8String: filename]; + NSData *passphraseData = NULL; + + if (accountString == nil) { + debug2("Cannot retrieve identity passphrase from the keychain since the path is not UTF8."); + return NULL; + } + + NSDictionary *searchQuery = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrAccount: accountString, + (id)kSecAttrLabel: [NSString stringWithFormat: @"SSH: %@", accountString], + (id)kSecAttrService: @"OpenSSH", +#if ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) + (id)kSecAttrNoLegacy: @YES, + (id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIFail, +#endif /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + (id)kSecAttrAccessGroup: @"com.apple.ssh.passphrases", + (id)kSecReturnData: @YES}; + debug3("Search for item with query: %s", [[searchQuery description] UTF8String]); + ret = SecItemCopyMatching((CFDictionaryRef)searchQuery, (CFTypeRef *)&passphraseData); + if (ret == errSecItemNotFound) { + debug2("Passphrase not found in the keychain."); + return NULL; + } else if (ret != errSecSuccess) { + NSString *errorString = (NSString *)SecCopyErrorMessageString(ret, NULL); + debug2("Unexpected keychain error while searching for an item: %s", [errorString UTF8String]); + [errorString release]; + [passphraseData release]; + return NULL; + } + + if (![passphraseData isKindOfClass: [NSData class]]) { + debug2("Malformed result returned from the keychain"); + [passphraseData release]; + return NULL; + } + + char *passphrase = xcalloc([passphraseData length] + 1, sizeof(char)); + [passphraseData getBytes: passphrase length: [passphraseData length]]; + [passphraseData release]; + + // Try to load the key first and only return the passphrase if we know it's the right one + struct sshkey *private = NULL; + int r = sshkey_load_private_type(KEY_UNSPEC, filename, passphrase, &private, NULL); + if (r != SSH_ERR_SUCCESS) { + debug2("Could not unlock key with the passphrase retrieved from the keychain."); + freezero(passphrase, strlen(passphrase)); + return NULL; + } + sshkey_free(private); + + return passphrase; +} + +void store_in_keychain(const char *filename, const char *passphrase) +{ + OSStatus ret = errSecSuccess; + BOOL updateExistingItem = NO; + NSString *accountString = [NSString stringWithUTF8String: filename]; + + if (accountString == nil) { + debug2("Cannot store identity passphrase into the keychain since the path is not UTF8."); + return; + } + + NSDictionary *defaultAttributes = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrAccount: accountString, + (id)kSecAttrLabel: [NSString stringWithFormat: @"SSH: %@", accountString], + (id)kSecAttrService: @"OpenSSH", +#if ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) + (id)kSecAttrNoLegacy: @YES, + (id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIFail, +#endif /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + (id)kSecAttrAccessGroup: @"com.apple.ssh.passphrases"}; + + CFTypeRef searchResults = NULL; + NSMutableDictionary *searchQuery = [@{(id)kSecReturnRef: @YES} mutableCopy]; + [searchQuery addEntriesFromDictionary: defaultAttributes]; + + debug3("Search for existing item with query: %s", [[searchQuery description] UTF8String]); + ret = SecItemCopyMatching((CFDictionaryRef)searchQuery, &searchResults); + [searchQuery release]; + if (ret == errSecSuccess) { + debug3("Item already exists in the keychain, updating."); + updateExistingItem = YES; + + } else if (ret == errSecItemNotFound) { + debug3("Item does not exist in the keychain, adding."); + } else { + NSString *errorString = (NSString *)SecCopyErrorMessageString(ret, NULL); + debug3("Unexpected keychain error while searching for an item: %s", [errorString UTF8String]); + [errorString release]; + } + + if (updateExistingItem) { + NSDictionary *updateQuery = defaultAttributes; + NSDictionary *changes = @{(id)kSecValueData: [NSData dataWithBytesNoCopy: (void *)passphrase length: strlen(passphrase) freeWhenDone: NO]}; + + ret = SecItemUpdate((CFDictionaryRef)updateQuery, (CFDictionaryRef)changes); + if (ret != errSecSuccess) { + NSString *errorString = (NSString *)SecCopyErrorMessageString(ret, NULL); + debug3("Unexpected keychain error while updating the item: %s", [errorString UTF8String]); + [errorString release]; + } + } else { + NSMutableDictionary *addQuery = [@{(id)kSecValueData: [NSData dataWithBytesNoCopy: (void *)passphrase length: strlen(passphrase) freeWhenDone: NO]} mutableCopy]; + + [addQuery addEntriesFromDictionary: defaultAttributes]; + ret = SecItemAdd((CFDictionaryRef)addQuery, NULL); + [addQuery release]; + if (ret != errSecSuccess) { + NSString *errorString = (NSString *)SecCopyErrorMessageString(ret, NULL); + debug3("Unexpected keychain error while inserting the item: %s", [errorString UTF8String]); + [errorString release]; + } + } +} + +/* + * Remove the passphrase for a given identity from the keychain. + */ +void +remove_from_keychain(const char *filename) +{ + OSStatus ret = errSecSuccess; + NSString *accountString = [NSString stringWithUTF8String: filename]; + + if (accountString == nil) { + debug2("Cannot delete identity passphrase from the keychain since the path is not UTF8."); + return; + } + + NSDictionary *searchQuery = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrAccount: accountString, + (id)kSecAttrService: @"OpenSSH", +#if ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) + (id)kSecAttrNoLegacy: @YES, + (id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIFail, +#endif /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + (id)kSecAttrAccessGroup: @"com.apple.ssh.passphrases"}; + + ret = SecItemDelete((CFDictionaryRef)searchQuery); + if (ret == errSecSuccess) { + NSString *errorString = (NSString *)SecCopyErrorMessageString(ret, NULL); + debug3("Unexpected keychain error while deleting the item: %s", [errorString UTF8String]); + [errorString release]; + } +} + + +int +load_identities_from_keychain(int (^add_identity)(const char *identity)) +{ + int ret = 0; + OSStatus err = errSecSuccess; + + NSArray *searchResults = nil; + NSDictionary *searchQuery = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrService: @"OpenSSH", +#if ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) + (id)kSecAttrNoLegacy: @YES, + (id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIFail, +#endif /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + (id)kSecAttrAccessGroup: @"com.apple.ssh.passphrases", + (id)kSecReturnAttributes: @YES, + (id)kSecMatchLimit: (id)kSecMatchLimitAll}; + + err = SecItemCopyMatching((CFDictionaryRef)searchQuery, (CFTypeRef *)&searchResults); + if (err == errSecItemNotFound) { + fprintf(stderr, "No identity found in the keychain.\n"); + [searchResults release]; + return 0; + } else if (err != errSecSuccess || ![searchResults isKindOfClass: [NSArray class]]) { + return 1; + } + + for (NSDictionary *itemAttributes in searchResults) { + NSString *accountString = itemAttributes[(id)kSecAttrAccount]; + struct stat st; + + if (stat([accountString UTF8String], &st) < 0) + continue; + if (add_identity([accountString UTF8String])) + ret = 1; + } + [searchResults release]; + + return ret; +} --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ b/keychain.h 2019-10-24 18:24:42.000000000 +0200 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2007-2016 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@ + */ + +void store_in_keychain(const char *filename, const char *passphrase); +void remove_from_keychain(const char *filename); +char *keychain_read_passphrase(const char *filename); +int load_identities_from_keychain(int (^add_identity)(const char *identity)); --- a/Makefile.in 2019-10-09 02:31:03.000000000 +0200 +++ b/Makefile.in 2019-10-24 18:24:42.000000000 +0200 @@ -41,6 +41,7 @@ PATHS= -DSSHDIR=\"$(sysconfdir)\" \ CC=@CC@ LD=@LD@ CFLAGS=@CFLAGS@ +OBJCFLAGS=@OBJCFLAGS@ CPPFLAGS=-I. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ LIBS=@LIBS@ K5LIBS=@K5LIBS@ @@ -56,6 +57,7 @@ SED=@SED@ ENT=@ENT@ XAUTH_PATH=@XAUTH_PATH@ LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@ +KEYCHAIN_LDFLAGS=@KEYCHAIN_LDFLAGS@ EXEEXT=@EXEEXT@ MANFMT=@MANFMT@ MKDIR_P=@MKDIR_P@ @@ -121,6 +123,8 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passw sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \ sandbox-solaris.o uidswap.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 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 sshd_config.5 ssh_config.5 MANTYPE = @MANTYPE@ @@ -156,6 +160,7 @@ all: configure-check $(CONFIGFILES) $(MA $(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 @@ -164,6 +169,8 @@ $(srcdir)/configure: configure.ac acloca .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ +.m.o: + $(CC) $(OBJCFLAGS) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ LIBCOMPAT=openbsd-compat/libopenbsd-compat.a $(LIBCOMPAT): always @@ -174,8 +181,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) @@ -183,11 +190,11 @@ sshd$(EXEEXT): libssh.a $(LIBCOMPAT) $(S scp$(EXEEXT): $(LIBCOMPAT) libssh.a scp.o progressmeter.o $(LD) -o $@ scp.o progressmeter.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) -ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o - $(LD) -o $@ ssh-add.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) +ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o $(KEYCHAINOBJS) + $(LD) -o $@ ssh-add.o $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(LIBS) -ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o - $(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) +ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o $(KEYCHAINOBJS) + $(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(LIBS) ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o sshsig.o $(LD) -o $@ ssh-keygen.o sshsig.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) --- a/audit-bsm.c 2019-10-09 02:31:03.000000000 +0200 +++ b/audit-bsm.c 2019-10-24 18:24:42.000000000 +0200 @@ -62,6 +62,18 @@ #include #include +#ifdef __APPLE__ +#include +#if ((defined (MAC_OS_X_VERSION_10_7)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) +#include +#endif /* ((defined (MAC_OS_X_VERSION_10_7)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) */ +#include "auth-options.h" +#include "misc.h" +#include "servconf.h" +extern ServerOptions options; +extern struct sshauthopt *auth_opts; +#endif + #if defined(HAVE_GETAUDIT_ADDR) #define AuditInfoStruct auditinfo_addr #define AuditInfoTermID au_tid_addr_t @@ -305,6 +317,19 @@ bsm_audit_session_setup(void) return; } +#ifdef __APPLE__ + bzero(&info, sizeof (info)); +#if ((defined (MAC_OS_X_VERSION_10_7)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) + info.ai_flags = AU_SESSION_FLAG_IS_REMOTE; + if (the_authctxt->valid) { + info.ai_flags |= AU_SESSION_FLAG_HAS_AUTHENTICATED; + } + if (auth_opts->permit_pty_flag && options.permit_tty) { + info.ai_flags |= AU_SESSION_FLAG_HAS_TTY; + } +#endif /* ((defined (MAC_OS_X_VERSION_10_7)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) */ +#endif + if (the_authctxt->valid) info.ai_auid = the_authctxt->pw->pw_uid; else --- a/configure.ac 2019-10-09 02:31:03.000000000 +0200 +++ b/configure.ac 2019-10-24 18:24:42.000000000 +0200 @@ -20,6 +20,7 @@ AC_LANG([C]) AC_CONFIG_HEADER([config.h]) AC_PROG_CC([cc gcc]) +AC_PROG_OBJC([cc clang gcc]) AC_CANONICAL_HOST AC_C_BIGENDIAN @@ -667,11 +668,11 @@ main() { if (NSVersionOfRunTimeLibrary(" AC_DEFINE([SSH_TUN_PREPEND_AF], [1], [Prepend the address family to IP tunnel traffic]) m4_pattern_allow([AU_IPv]) - AC_CHECK_DECL([AU_IPv4], [], - AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) - [#include ] - AC_DEFINE([LASTLOG_WRITE_PUTUTXLINE], [1], - [Define if pututxline updates lastlog too]) + AC_CHECK_DECL([AU_IPv4], + AC_DEFINE([LASTLOG_WRITE_PUTUTXLINE], [1], + [Define if pututxline updates lastlog too]), + AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]), + [[#include ]] ) AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV], [Define to a Set Process Title type if your system is @@ -5152,6 +5153,34 @@ AC_CHECK_MEMBER([struct utmp.ut_line], [ #endif ]) +dnl Keychain support +AC_ARG_WITH(keychain, + [ --with-keychain=apple Use macOS 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__ -D__APPLE_MEMBERSHIP__ -D__APPLE_TMPDIR__ -D__APPLE_LAUNCHD__" + OBJCFLAGS="$OBJCFLAGS -F/System/Library/Frameworks/Security.framework -F/System/Library/Frameworks/DirectoryService.framework -F/System/Library/Frameworks/CoreFoundation.framework" + KEYCHAIN_LDFLAGS="-framework Security -framework CoreFoundation -framework Foundation -lobjc" + AC_SUBST(KEYCHAIN_LDFLAGS) + ], + AC_MSG_WARN([Security framework not found. Disabling macOS Keychain support.])) + ;; + esac +fi + dnl Adding -Werror to CFLAGS early prevents configure tests from running. dnl Add now. CFLAGS="$CFLAGS $werror_flags" --- a/groupaccess.c 2019-10-09 02:31:03.000000000 +0200 +++ b/groupaccess.c 2019-10-24 18:24:42.000000000 +0200 @@ -39,6 +39,10 @@ #include "match.h" #include "log.h" +#ifdef __APPLE_MEMBERSHIP__ +int32_t getgrouplist_2(const char *, gid_t, gid_t **); +#endif + static int ngroups; static char **groups_byname; @@ -56,6 +60,18 @@ ga_init(const char *user, gid_t base) if (ngroups > 0) ga_free(); +#ifdef __APPLE_MEMBERSHIP__ + (void)retry; + if ((ngroups = getgrouplist_2(user, base, &groups_bygid)) == -1) { + logit("getgrouplist_2 failed"); + /* + * getgrouplist_2 only fails on memory error; in which case + * groups_bygid will be left NULL so no need to free. + */ + return 0; + } + groups_byname = xcalloc(ngroups, sizeof(*groups_byname)); +#else ngroups = NGROUPS_MAX; #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) ngroups = MAX(NGROUPS_MAX, sysconf(_SC_NGROUPS_MAX)); @@ -69,6 +85,7 @@ ga_init(const char *user, gid_t base) sizeof(*groups_bygid)); } groups_byname = xcalloc(ngroups, sizeof(*groups_byname)); +#endif /* __APPLE_MEMBERSHIP__ */ for (i = 0, j = 0; i < ngroups; i++) if ((gr = getgrgid(groups_bygid[i])) != NULL) --- a/readconf.c 2019-10-09 02:31:03.000000000 +0200 +++ b/readconf.c 2019-10-24 18:24:42.000000000 +0200 @@ -167,6 +167,9 @@ typedef enum { oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oRemoteCommand, +#ifdef __APPLE_KEYCHAIN__ + oUseKeychain, +#endif oVisualHostKey, oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass, oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, @@ -291,6 +294,9 @@ static struct { { "localcommand", oLocalCommand }, { "permitlocalcommand", oPermitLocalCommand }, { "remotecommand", oRemoteCommand }, +#ifdef __APPLE_KEYCHAIN__ + { "usekeychain", oUseKeychain}, +#endif { "visualhostkey", oVisualHostKey }, { "kexalgorithms", oKexAlgorithms }, { "ipqos", oIPQoS }, @@ -1520,6 +1526,12 @@ parse_keytypes: charptr = &options->remote_command; goto parse_command; +#ifdef __APPLE_KEYCHAIN__ + case oUseKeychain: + intptr = &options->use_keychain; + goto parse_flag; +#endif + case oVisualHostKey: intptr = &options->visual_host_key; goto parse_flag; @@ -1929,6 +1941,9 @@ initialize_options(Options * options) options->local_command = NULL; options->permit_local_command = -1; options->remote_command = NULL; +#ifdef __APPLE_KEYCHAIN__ + options->use_keychain = -1; +#endif options->add_keys_to_agent = -1; options->identity_agent = NULL; options->visual_host_key = -1; @@ -2168,6 +2183,11 @@ fill_default_options(Options * options) /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ /* options->preferred_authentications will be set in ssh */ + +#ifdef __APPLE_KEYCHAIN__ + if (options->use_keychain == -1) + options->use_keychain = 0; +#endif } struct fwdarg { --- a/readconf.h 2019-10-09 02:31:03.000000000 +0200 +++ b/readconf.h 2019-10-24 18:24:42.000000000 +0200 @@ -137,6 +137,9 @@ typedef struct { char *local_command; int permit_local_command; char *remote_command; +#ifdef __APPLE_KEYCHAIN__ + int use_keychain; +#endif int visual_host_key; int request_tty; --- a/session.c 2019-10-09 02:31:03.000000000 +0200 +++ b/session.c 2019-10-24 18:24:42.000000000 +0200 @@ -1185,6 +1185,21 @@ do_setup_env(struct ssh *ssh, Session *s child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", original_command); +#ifdef __APPLE_TMPDIR__ + char tmpdir[MAXPATHLEN] = {0}; + size_t len = 0; + + len = confstr(_CS_DARWIN_USER_TEMP_DIR, tmpdir, sizeof(tmpdir)); + if (len > 0) { + child_set_env(&env, &envsize, "TMPDIR", tmpdir); + debug2("%s: set TMPDIR", __func__); + } else { + // errno is set by confstr + errno = 0; + debug2("%s: unable to set TMPDIR", __func__); + } +#endif /* __APPLE_TMPDIR__ */ + if (debug_flag) { /* dump the environment */ fprintf(stderr, "Environment:\n"); --- a/ssh-add.0 2019-10-09 02:39:14.000000000 +0200 +++ b/ssh-add.0 2019-10-24 18:24:42.000000000 +0200 @@ -4,7 +4,7 @@ NAME ssh-add M-bM-^@M-^S adds private key identities to the authentication agent SYNOPSIS - ssh-add [-cDdkLlqvXx] [-E fingerprint_hash] [-t life] [file ...] + ssh-add [-AcDdKkLlqvXx] [-E fingerprint_hash] [-t life] [file ...] ssh-add -s pkcs11 ssh-add -e pkcs11 ssh-add -T pubkey ... @@ -28,6 +28,9 @@ DESCRIPTION The options are as follows: + -A Add identities to the agent using any passphrases stored in your + macOS keychain. + -c Indicates that added identities should be subject to confirmation before being used for authentication. Confirmation is performed by ssh-askpass(1). Successful confirmation is signaled by a zero @@ -52,6 +55,10 @@ DESCRIPTION -e pkcs11 Remove keys provided by the PKCS#11 shared library pkcs11. + -K When adding identities, each passphrase will also be stored in + your macOS keychain. When removing identities with -d, each + passphrase will be removed from your macOS keychain. + -k When loading keys into or deleting keys from the agent, process plain private keys only and skip certificates. --- a/ssh-add.1 2019-10-09 02:31:03.000000000 +0200 +++ b/ssh-add.1 2019-10-24 18:24:42.000000000 +0200 @@ -43,7 +43,7 @@ .Nd adds private key identities to the authentication agent .Sh SYNOPSIS .Nm ssh-add -.Op Fl cDdkLlqvXx +.Op Fl AcDdKKkLlqvXx .Op Fl E Ar fingerprint_hash .Op Fl t Ar life .Op Ar @@ -87,6 +87,9 @@ to work. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl A +Add identities to the agent using any passphrases stored in your macOS +keychain. .It Fl c Indicates that added identities should be subject to confirmation before being used for authentication. @@ -121,6 +124,10 @@ The default is .It Fl e Ar pkcs11 Remove keys provided by the PKCS#11 shared library .Ar pkcs11 . +.It Fl K +When adding identities, each passphrase will also be stored in your macOS +keychain. When removing identities with -d, each passphrase will be removed +from your macOS keychain. .It Fl k When loading keys into or deleting keys from the agent, process plain private keys only and skip certificates. --- a/ssh-add.c 2019-10-09 02:31:03.000000000 +0200 +++ b/ssh-add.c 2019-10-24 18:24:42.000000000 +0200 @@ -67,6 +67,11 @@ #include "ssherr.h" #include "digest.h" +#ifdef __APPLE_KEYCHAIN__ +#include "keychain.h" +static int use_keychain = 0; +#endif + /* argv0 */ extern char *__progname; @@ -115,6 +120,11 @@ delete_file(int agent_fd, const char *fi char *certpath = NULL, *comment = NULL; int r, ret = -1; +#ifdef __APPLE_KEYCHAIN__ + if (use_keychain) + remove_from_keychain(filename); +#endif + if ((r = sshkey_load_public(filename, &public, &comment)) != 0) { printf("Bad key file %s: %s\n", filename, ssh_err(r)); return -1; @@ -246,7 +256,22 @@ add_file(int agent_fd, const char *filen filename, ssh_err(r)); goto fail_load; } +#ifdef __APPLE_KEYCHAIN__ + if (use_keychain && private != NULL) + store_in_keychain(filename, pass); +#endif + } + +#ifdef __APPLE_KEYCHAIN__ + // try the keychain + if (private == NULL && use_keychain) { + clear_pass(); + pass = keychain_read_passphrase(filename); + if (pass != NULL) + sshkey_parse_private_fileblob(keyblob, pass, &private, &comment); } +#endif + if (private == NULL) { /* clear passphrase since it did not work */ clear_pass(); @@ -258,7 +283,15 @@ add_file(int agent_fd, const char *filen goto fail_load; if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private, &comment)) == 0) +#ifdef __APPLE_KEYCHAIN__ + { + if (use_keychain && private != NULL) + store_in_keychain(filename, pass); break; + } +#else + break; +#endif else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) { fprintf(stderr, "Error loading key \"%s\": %s\n", @@ -563,6 +596,11 @@ usage(void) fprintf(stderr, " -T pubkey Test if ssh-agent can access matching private key.\n"); fprintf(stderr, " -q Be quiet after a successful operation.\n"); fprintf(stderr, " -v Be more verbose.\n"); +#ifdef __APPLE_KEYCHAIN__ + fprintf(stderr, " -A Add all identities stored in your macOS keychain.\n"); + fprintf(stderr, " -K Store passphrases in your macOS keychain.\n"); + fprintf(stderr, " With -d, remove passphrases from your macOS keychain.\n"); +#endif } int --- a/ssh-agent.c 2019-10-09 02:31:03.000000000 +0200 +++ b/ssh-agent.c 2019-10-24 18:24:42.000000000 +0200 @@ -70,6 +70,10 @@ #include #include #include +#ifdef __APPLE_LAUNCHD__ +#include +#include +#endif #ifdef HAVE_UTIL_H # include #endif @@ -1088,6 +1092,9 @@ int main(int ac, char **av) { int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0; + #ifdef __APPLE_LAUNCHD__ + int l_flag = 0; + #endif int sock, fd, ch, result, saved_errno; char *shell, *format, *pidstr, *agentsocket = NULL; #ifdef HAVE_SETRLIMIT @@ -1119,7 +1126,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); @@ -1139,6 +1150,11 @@ main(int ac, char **av) fatal("-P option already specified"); pkcs11_whitelist = xstrdup(optarg); break; +#ifdef __APPLE_LAUNCHD__ + case 'l': + l_flag++; + break; +#endif case 's': if (c_flag) usage(); @@ -1241,6 +1257,75 @@ 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) { +#if ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) + int *fds = NULL; + size_t count = 0; + result = launch_activate_socket("Listeners", &fds, &count); + + if (result != 0 || fds == NULL || count < 1) { + errno = result; + perror("launch_activate_socket()"); + exit(1); + } + + size_t i; + for (i = 0; i < count; i++) { + new_socket(AUTH_SOCKET, fds[i]); + } + + if (fds) + free(fds); + + goto skip2; +#else /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + 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); +#endif /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + } else { +#endif prev_mask = umask(0177); sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0); if (sock < 0) { @@ -1248,7 +1333,18 @@ main(int ac, char **av) *socket_name = '\0'; /* Don't unlink any existing file */ cleanup_exit(1); } + umask(prev_mask); +#ifdef __APPLE_LAUNCHD__ + } +#endif + +#ifdef __APPLE_LAUNCHD__ +#if ((!(defined (MAC_OS_X_VERSION_10_11))) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11)) + if (l_flag) + goto skip2; +#endif /* ((!(defined (MAC_OS_X_VERSION_10_11))) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11)) */ +#endif /* defined (__APPLE_LAUNCHD__) */ /* * Fork, and have the parent execute the command, if any, or present @@ -1326,6 +1422,9 @@ skip: pkcs11_init(0); #endif new_socket(AUTH_SOCKET, sock); +#ifdef __APPLE_LAUNCHD__ +skip2: +#endif if (ac > 0) parent_alive_interval = 10; idtab_init(); --- a/sshconnect2.c 2019-10-09 02:31:03.000000000 +0200 +++ b/sshconnect2.c 2019-10-24 18:24:42.000000000 +0200 @@ -73,6 +73,11 @@ #include "ssherr.h" #include "utf8.h" +#ifdef __APPLE_KEYCHAIN__ +#include "keychain.h" +int found_in_keychain = 0; +#endif + #ifdef GSSAPI #include "ssh-gss.h" #endif @@ -1415,6 +1420,12 @@ load_identity_file(Identity *id) snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", id->filename); for (i = 0; i <= options.number_of_password_prompts; i++) { +#ifdef __APPLE_KEYCHAIN__ + if (i == 0 && options.use_keychain && (passphrase = keychain_read_passphrase(id->filename)) != NULL) { + found_in_keychain = 1; + debug2("using passphrase from keychain"); + } else +#endif if (i == 0) passphrase = ""; else { @@ -1450,6 +1461,14 @@ load_identity_file(Identity *id) quit = 1; break; } + +#ifdef __APPLE_KEYCHAIN__ + if (!quit && private != NULL && !(id->key && id->isprivate) && options.use_keychain && !found_in_keychain) { + debug2("storing passphrase in keychain"); + store_in_keychain(id->filename, passphrase); + } +#endif + if (!quit && private != NULL && id->agent_fd == -1 && !(id->key && id->isprivate)) maybe_add_key_to_agent(id->filename, private, comment, --- a/ssh-agent.0 2019-10-09 02:39:14.000000000 +0200 +++ b/ssh-agent.0 2019-10-24 18:24:42.000000000 +0200 @@ -7,6 +7,7 @@ SYNOPSIS ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash] [-P pkcs11_whitelist] [-t life] [command [arg ...]] ssh-agent [-c | -s] -k + ssh-agent -l DESCRIPTION ssh-agent is a program to hold private keys used for public key @@ -64,6 +65,9 @@ DESCRIPTION for an identity with ssh-add(1) overrides this value. Without this option the default maximum lifetime is forever. + -l Start in launchd mode. This feature should only be used by macOS + itself. It is not very useful to users. + If a command line is given, this is executed as a subprocess of the agent. When the command dies, so does the agent. --- a/ssh-agent.1 2019-10-09 02:31:03.000000000 +0200 +++ b/ssh-agent.1 2019-10-24 18:24:43.000000000 +0200 @@ -52,6 +52,8 @@ .Nm ssh-agent .Op Fl c | s .Fl k +.Nm ssh-agent +.Fl l .Sh DESCRIPTION .Nm is a program to hold private keys used for public key authentication @@ -148,6 +150,10 @@ A lifetime specified for an identity wit .Xr ssh-add 1 overrides this value. Without this option the default maximum lifetime is forever. +.It Fl l +Start in launchd mode. +This feature should only be used by macOS itself. +It is not very useful to users. .El .Pp If a command line is given, this is executed as a subprocess of the agent. --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ b/SecItemPriv-shim.h 2019-10-24 18:25:16.000000000 +0200 @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2006-2013 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + @header SecItemPriv + SecItemPriv defines private constants and SPI functions for access to + Security items (certificates, identities, keys, and keychain items.) + + ====== MACPORTS NOTICE ====== + Apple uses this private header file for building its OpenSSH keychain + integration. They are able to do this because they have either converted + all (or most?) upstream projects into Xcode projects and can then use + private headers, but our users can't. + + Private header files are never installed onto user systems and there + aren't any SDKs that users could install to get them. + + Luckily, the Security Framework *is* (currently) free software, so we do + have access to it via https://opensource.apple.com + + We can, hence, take a look at it and copy relevant parts/declarations. + + We cannot, however, make sure that the declarations in here are actually + defined in the Security Framework binaries/libraries themselves, so + building this part, especially on older systems, might still fail. + ====== MACPORTS NOTICE ====== +*/ + +#ifndef _SECURITY_SECITEMPRIV_H_ +#define _SECURITY_SECITEMPRIV_H_ + +#include +#include +#include +#include +#include + +#if ((defined (MAC_OS_X_VERSION_10_7)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) +#include + +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) +#include +#endif +#endif /* ((defined (MAC_OS_X_VERSION_10_7)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) */ + +__BEGIN_DECLS + +/* + @enum Attribute Key Constants (Private) + @discussion Predefined item attribute keys used to get or set values in a + dictionary. Not all attributes apply to each item class. The table + below lists the currently defined attributes for each item class: + + @constant kSecAttrNoLegacy Specifies a dictionary key whose + value is a CFBooleanRef indicating that the query must be run on the + syncable backend even for non syncable items. +*/ + +#if ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) +extern const CFStringRef kSecAttrNoLegacy + __OSX_AVAILABLE(10.11) __IOS_AVAILABLE(9.3) __TVOS_AVAILABLE(9.3) __WATCHOS_AVAILABLE(2.3); +#endif /* ((defined (MAC_OS_X_VERSION_10_11)) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) */ + +__END_DECLS + +#endif /* !_SECURITY_SECITEMPRIV_H_ */