* [PATCH] contrib: add a credential helper for Mac OS X's keychain
@ 2011-09-10 19:44 Jay Soffian
2011-09-14 8:01 ` John Szakmeister
2011-09-14 10:24 ` John Szakmeister
0 siblings, 2 replies; 7+ messages in thread
From: Jay Soffian @ 2011-09-10 19:44 UTC (permalink / raw)
To: git; +Cc: Jay Soffian, Junio C Hamano, Jeff King
A credential helper which uses /usr/bin/security to add, search,
and remove entries from the Mac OS X keychain.
Tested with 10.6.8.
Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
This is a quick script to explore the new credential API. A more robust
implementation would be to link to OS X's Security framework from C.
contrib/credential/git-credential-osxkeychain | 148 +++++++++++++++++++++++++
1 files changed, 148 insertions(+), 0 deletions(-)
create mode 100755 contrib/credential/git-credential-osxkeychain
diff --git a/contrib/credential/git-credential-osxkeychain b/contrib/credential/git-credential-osxkeychain
new file mode 100755
index 0000000000..ae5ec00d68
--- /dev/null
+++ b/contrib/credential/git-credential-osxkeychain
@@ -0,0 +1,148 @@
+#!/usr/bin/python
+# Copyright 2011 Jay Soffian. All rights reserved.
+# FreeBSD License.
+"""
+A git credential helper that interfaces with the Mac OS X keychain via
+/usr/bin/security.
+"""
+
+import os
+import re
+import sys
+import termios
+from getpass import _raw_input
+from optparse import OptionParser
+from subprocess import Popen, PIPE
+
+USERNAME = 'USERNAME'
+PASSWORD = 'PASSWORD'
+PROMPTS = dict(USERNAME='Username', PASSWORD='Password')
+
+def prompt_tty(what, desc):
+ """Prompt on TTY for username or password with optional description"""
+ prompt = '%s%s: ' % (PROMPTS[what], " for '%s'" % desc if desc else '')
+ # Borrowed mostly from getpass.py
+ fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
+ tty = os.fdopen(fd, 'w+', 1)
+ if what == USERNAME:
+ return _raw_input(prompt, tty, tty)
+ old = termios.tcgetattr(fd) # a copy to save
+ new = old[:]
+ new[3] &= ~termios.ECHO # 3 == 'lflags'
+ try:
+ termios.tcsetattr(fd, termios.TCSADRAIN, new)
+ return _raw_input(prompt, tty, tty)
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
+ tty.write('\n')
+
+def emit_user_pass(username, password):
+ if username:
+ print 'username=' + username
+ if password:
+ print 'password=' + password
+
+def make_security_args(command, protocol, hostname, username):
+ args = ['/usr/bin/security', command]
+ # tlfd is 'dflt' backwards - obvious /usr/bin/security bug
+ # but allows us to ignore matching saved web forms.
+ args.extend(['-t', 'tlfd'])
+ args.extend(['-r', protocol])
+ if hostname:
+ args.extend(['-s', hostname])
+ if username:
+ args.extend(['-a', username])
+ return args
+
+def find_internet_password(protocol, hostname, username):
+ args = make_security_args('find-internet-password',
+ protocol, hostname, username)
+ args.append('-g') # asks for password on stderr
+ p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ # grok stdout for username
+ out, err = p.communicate()
+ if p.returncode != 0:
+ return
+ for line in out.splitlines(): # pylint:disable-msg=E1103
+ m = re.search(r'^\s+"acct"<blob>=[^"]*"(.*)"$', line)
+ if m:
+ username = m.group(1)
+ break
+ # grok stderr for password
+ m = re.search(r'^password:[^"]*"(.*)"$', err)
+ if not m:
+ return
+ emit_user_pass(username, m.group(1))
+ return True
+
+def delete_internet_password(protocol, hostname, username):
+ args = make_security_args('delete-internet-password',
+ protocol, hostname, username)
+ p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ p.communicate()
+
+def add_internet_password(protocol, hostname, username, password):
+ # We do this over a pipe so that we can provide the password more
+ # securely than as an argument which would show up in ps output.
+ # Unfortunately this is possibly less robust since the security man
+ # page does not document how to quote arguments. Emprically it seems
+ # that using the double-quote, escaping \ and " works properly.
+ username = username.replace('\\', '\\\\').replace('"', '\\"')
+ password = password.replace('\\', '\\\\').replace('"', '\\"')
+ command = ' '.join([
+ 'add-internet-password', '-U',
+ '-r', protocol,
+ '-s', hostname,
+ '-a "%s"' % username,
+ '-w "%s"' % password,
+ '-j default',
+ '-l "%s (%s)"' % (hostname, username),
+ ]) + '\n'
+ args = ['/usr/bin/security', '-i']
+ p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ p.communicate(command)
+
+def main():
+ p = OptionParser()
+ p.add_option('--description')
+ p.add_option('--reject', action='store_true')
+ p.add_option('--unique', dest='token', help='REQUIRED OPTION')
+ p.add_option('--username')
+ opts, _ = p.parse_args()
+
+ if not opts.token:
+ p.error('--unique option required')
+ if not ':' in opts.token:
+ print >> sys.stderr, "Invalid token: '%s'" % opts.token
+ return 1
+ protocol, hostname = opts.token.split(':', 1)
+ if protocol not in ('http', 'https'):
+ print >> sys.stderr, "Unsupported protocol: '%s'" % protocol
+ return 1
+ if protocol == 'https':
+ protocol = 'htps'
+
+ # "GitHub for Mac" compatibility
+ if hostname == 'github.com':
+ hostname = 'github.com/mac'
+
+ # if this is a rejection delete the existing creds
+ if opts.reject:
+ delete_internet_password(protocol, hostname, opts.username)
+ return 0
+
+ # otherwise look for creds
+ if find_internet_password(protocol, hostname, opts.username):
+ return 0
+
+ # creds not found, so prompt the user then store the creds
+ username = opts.username
+ if username is None:
+ username = prompt_tty(USERNAME, opts.description)
+ password = prompt_tty(PASSWORD, opts.description)
+ add_internet_password(protocol, hostname, username, password)
+ emit_user_pass(username, password)
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
--
1.7.6.346.g5a895
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] contrib: add a credential helper for Mac OS X's keychain
2011-09-10 19:44 [PATCH] contrib: add a credential helper for Mac OS X's keychain Jay Soffian
@ 2011-09-14 8:01 ` John Szakmeister
2011-09-14 13:31 ` Jay Soffian
2011-09-14 10:24 ` John Szakmeister
1 sibling, 1 reply; 7+ messages in thread
From: John Szakmeister @ 2011-09-14 8:01 UTC (permalink / raw)
To: Jay Soffian; +Cc: git, Junio C Hamano, Jeff King
On Sat, Sep 10, 2011 at 3:44 PM, Jay Soffian <jaysoffian@gmail.com> wrote:
> A credential helper which uses /usr/bin/security to add, search,
> and remove entries from the Mac OS X keychain.
>
> Tested with 10.6.8.
>
> Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
> ---
> This is a quick script to explore the new credential API. A more robust
> implementation would be to link to OS X's Security framework from C.
[snip]
> +def add_internet_password(protocol, hostname, username, password):
> + # We do this over a pipe so that we can provide the password more
> + # securely than as an argument which would show up in ps output.
> + # Unfortunately this is possibly less robust since the security man
> + # page does not document how to quote arguments. Emprically it seems
> + # that using the double-quote, escaping \ and " works properly.
I'm not sure this comment is correct... it looks like you're passing
the password on the command line...
> + username = username.replace('\\', '\\\\').replace('"', '\\"')
> + password = password.replace('\\', '\\\\').replace('"', '\\"')
> + command = ' '.join([
> + 'add-internet-password', '-U',
> + '-r', protocol,
> + '-s', hostname,
> + '-a "%s"' % username,
> + '-w "%s"' % password,
...right here. :-(
-John
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] contrib: add a credential helper for Mac OS X's keychain
2011-09-10 19:44 [PATCH] contrib: add a credential helper for Mac OS X's keychain Jay Soffian
2011-09-14 8:01 ` John Szakmeister
@ 2011-09-14 10:24 ` John Szakmeister
1 sibling, 0 replies; 7+ messages in thread
From: John Szakmeister @ 2011-09-14 10:24 UTC (permalink / raw)
To: Jay Soffian; +Cc: git, Junio C Hamano, Jeff King
On Sat, Sep 10, 2011 at 3:44 PM, Jay Soffian <jaysoffian@gmail.com> wrote:
> A credential helper which uses /usr/bin/security to add, search,
> and remove entries from the Mac OS X keychain.
>
> Tested with 10.6.8.
>
> Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
> ---
> This is a quick script to explore the new credential API. A more robust
> implementation would be to link to OS X's Security framework from C.
I'm pretty close with a version of this that is written in C. I've
done it as a completely separate build from git, although it probably
wouldn't be hard to make it build in the contrib area.
If no one else picks it up, I may look at doing a version against the
secrets api for Linux too.
-John
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] contrib: add a credential helper for Mac OS X's keychain
2011-09-14 8:01 ` John Szakmeister
@ 2011-09-14 13:31 ` Jay Soffian
2011-09-14 22:23 ` John Szakmeister
0 siblings, 1 reply; 7+ messages in thread
From: Jay Soffian @ 2011-09-14 13:31 UTC (permalink / raw)
To: John Szakmeister; +Cc: git, Junio C Hamano, Jeff King
On Wed, Sep 14, 2011 at 4:01 AM, John Szakmeister <john@szakmeister.net> wrote:
>> +def add_internet_password(protocol, hostname, username, password):
>> + # We do this over a pipe so that we can provide the password more
>> + # securely than as an argument which would show up in ps output.
>> + # Unfortunately this is possibly less robust since the security man
>> + # page does not document how to quote arguments. Emprically it seems
>> + # that using the double-quote, escaping \ and " works properly.
>
> I'm not sure this comment is correct... it looks like you're passing
> the password on the command line...
>
>> + username = username.replace('\\', '\\\\').replace('"', '\\"')
>> + password = password.replace('\\', '\\\\').replace('"', '\\"')
>> + command = ' '.join([
>> + 'add-internet-password', '-U',
>> + '-r', protocol,
>> + '-s', hostname,
>> + '-a "%s"' % username,
>> + '-w "%s"' % password,
>
> ...right here. :-(
Nope, you snipped out too much context. That command is turned into a
string and then sent to /usr/bin/security on its stdin. It is
absolutely not passed on the command-line.
j.
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH] contrib: add a credential helper for Mac OS X's keychain
@ 2011-09-14 17:58 Jay Soffian
2011-09-14 18:19 ` Jay Soffian
0 siblings, 1 reply; 7+ messages in thread
From: Jay Soffian @ 2011-09-14 17:58 UTC (permalink / raw)
To: git; +Cc: Jay Soffian, Jeff King, John Szakmeister, Junio C Hamano
This credential helper adds, searches, and removes entries from
the Mac OS X keychain via OS X's Security Framework.
Tested with 10.6.8.
Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
And here's a C version. I cargo-culted the Makefile from
contrib/svn-fe/Makefile. Sadly, linking against git bloats the
binary quite a bit which is dissappointing since this helper
won't be installed as a hard-link. Hmm, maybe it can be if I
dlopen the security framework instead of linking against it.
contrib/credential-osxkeychain/Makefile | 35 ++++
.../git-credential-osxkeychain.c | 205 ++++++++++++++++++++
2 files changed, 240 insertions(+), 0 deletions(-)
create mode 100644 contrib/credential-osxkeychain/Makefile
create mode 100644 contrib/credential-osxkeychain/git-credential-osxkeychain.c
diff --git a/contrib/credential-osxkeychain/Makefile b/contrib/credential-osxkeychain/Makefile
new file mode 100644
index 0000000000..dc6bbbc3f9
--- /dev/null
+++ b/contrib/credential-osxkeychain/Makefile
@@ -0,0 +1,35 @@
+all:: git-credential-osxkeychain
+
+CC = gcc
+RM = rm -f
+CFLAGS = -O2 -Wall -I../.. -DSHA1_HEADER='<openssl/sha.h>'
+GIT_LIBS = ../../libgit.a ../../xdiff/lib.a
+LIBS = $(GIT_LIBS) -lz -liconv -lcrypto -lssl
+
+QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1 =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+ QUIET_CC = @echo ' ' CC $@;
+ QUIET_LINK = @echo ' ' LINK $@;
+ QUIET_SUBDIR0 = +@subdir=
+ QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
+ $(MAKE) $(PRINT_DIR) -C $$subdir
+endif
+endif
+
+git-credential-osxkeychain: git-credential-osxkeychain.o $(GIT_LIBS)
+ $(QUIET_LINK)$(CC) -o $@ $< $(LIBS) -Wl,-framework -Wl,Security
+
+git-credential-osxkeychain.o: git-credential-osxkeychain.c
+ $(QUIET_CC)$(CC) -c $(CFLAGS) $<
+
+clean:
+ $(RM) git-credential-osxkeychain git-credential-osxkeychain.o
diff --git a/contrib/credential-osxkeychain/git-credential-osxkeychain.c b/contrib/credential-osxkeychain/git-credential-osxkeychain.c
new file mode 100644
index 0000000000..64bcc636cb
--- /dev/null
+++ b/contrib/credential-osxkeychain/git-credential-osxkeychain.c
@@ -0,0 +1,205 @@
+/* Copyright 2011 Jay Soffian. All rights reserved.
+ * FreeBSD License.
+ *
+ * A git credential helper that interfaces with the Mac OS X keychain
+ * via the Security framework.
+ */
+#include <stdlib.h>
+#include <Security/Security.h>
+#include "cache.h"
+#include "credential.h"
+#include "parse-options.h"
+
+void emit_user_pass(char *username, char *password)
+{
+ if (username)
+ printf("username=%s\n", username);
+ if (password)
+ printf("password=%s\n", password);
+}
+
+char *username_from_keychain_item(SecKeychainItemRef item)
+{
+ OSStatus status;
+ SecKeychainAttributeList list;
+ SecKeychainAttribute attr;
+ list.count = 1;
+ list.attr = &attr;
+ attr.tag = kSecAccountItemAttr;
+ char *username;
+
+ status = SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL);
+ if (status != noErr)
+ return NULL;
+ username = xmalloc(attr.length + 1);
+ strncpy(username, attr.data, attr.length);
+ username[attr.length] = '\0';
+ SecKeychainItemFreeContent(&list, NULL);
+ return username;
+}
+
+int find_internet_password(SecProtocolType protocol,
+ char *hostname,
+ char *username)
+{
+ void *password_buf;
+ UInt32 password_len;
+ OSStatus status;
+ char *password;
+ int free_username;
+ SecKeychainItemRef item;
+
+ status = SecKeychainFindInternetPassword(
+ NULL,
+ strlen(hostname), hostname,
+ 0, NULL,
+ username ? strlen(username) : 0, username,
+ 0, NULL,
+ 0,
+ protocol,
+ kSecAuthenticationTypeDefault,
+ &password_len, &password_buf,
+ &item);
+ if (status != noErr)
+ return -1;
+
+ password = xmalloc(password_len + 1);
+ strncpy(password, password_buf, password_len);
+ password[password_len] = '\0';
+ SecKeychainItemFreeContent(NULL, password_buf);
+ if (!username) {
+ username = username_from_keychain_item(item);
+ free_username = 1;
+ }
+ emit_user_pass(username, password);
+ if (free_username)
+ free(username);
+ free(password);
+ return 0;
+}
+
+void delete_internet_password(SecProtocolType protocol,
+ char *hostname,
+ char *username)
+{
+ OSStatus status;
+ SecKeychainItemRef item;
+
+ status = SecKeychainFindInternetPassword(
+ NULL,
+ strlen(hostname), hostname,
+ 0, NULL,
+ username ? strlen(username) : 0, username,
+ 0, NULL,
+ 0,
+ protocol,
+ kSecAuthenticationTypeDefault,
+ 0, NULL,
+ &item);
+ if (status != noErr)
+ return;
+ SecKeychainItemDelete(item);
+}
+
+void add_internet_password(SecProtocolType protocol,
+ char *hostname,
+ char *username,
+ char *password,
+ char *comment)
+{
+ struct strbuf label = STRBUF_INIT;
+ OSStatus status;
+ SecKeychainItemRef item;
+ SecKeychainAttributeList list;
+ SecKeychainAttribute attr;
+ list.count = 1;
+ list.attr = &attr;
+ status = SecKeychainAddInternetPassword(
+ NULL,
+ strlen(hostname), hostname,
+ 0, NULL,
+ strlen(username), username,
+ 0, NULL,
+ 0,
+ protocol,
+ kSecAuthenticationTypeDefault,
+ strlen(password), password,
+ &item);
+ if (status != noErr)
+ return;
+
+ /* set the comment */
+ attr.tag = kSecCommentItemAttr;
+ attr.data = comment;
+ attr.length = strlen(comment);
+ SecKeychainItemModifyContent(item, &list, 0, NULL);
+
+ /* override the label */
+ strbuf_addf(&label, "%s (%s)", hostname, username);
+ attr.tag = kSecLabelItemAttr;
+ attr.data = label.buf;
+ attr.length = label.len;
+ SecKeychainItemModifyContent(item, &list, 0, NULL);
+}
+
+int main(int argc, const char **argv)
+{
+ const char * const usage[] = {
+ "git credential-osxkeychain [options]",
+ NULL
+ };
+ struct credential c = { NULL };
+ int reject = 0;
+ SecProtocolType protocol;
+ char *hostname;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "reject", &reject,
+ "reject a stored credential"),
+ OPT_STRING(0, "username", &c.username, "name",
+ "an existing username"),
+ OPT_STRING(0, "description", &c.description, "desc",
+ "human-readable description of the credential"),
+ OPT_STRING(0, "unique", &c.unique, "token",
+ "a unique context for the credential [REQUIRED]"),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, NULL, options, usage, 0);
+ if (argc)
+ usage_with_options(usage, options);
+
+ if (!c.unique)
+ die(_("--unique option required"));
+ hostname = strchr(c.unique, ':');
+ if (!hostname)
+ die(_("Invalid token: '%s'"), c.unique);
+ *hostname++ = '\0';
+
+ /* "GitHub for Mac" compatibility */
+ if (!strcmp(hostname, "github.com"))
+ hostname = "github.com/mac";
+
+ if (!strcmp(c.unique, "https")) {
+ protocol = kSecProtocolTypeHTTPS;
+ } else if (!strcmp(c.unique, "http")) {
+ protocol = kSecProtocolTypeHTTP;
+ }
+ else
+ die(_("Unsupported protocol: '%s'"), c.unique);
+
+ /* if this is a rejection delete the existing creds */
+ if (reject) {
+ delete_internet_password(protocol, hostname, c.username);
+ return 0;
+ }
+
+ /* otherwise look for a matching keychain item */
+ if (!find_internet_password(protocol, hostname, c.username))
+ return 0;
+
+ /* no keychain item found, prompt the user and store the result */
+ credential_getpass(&c);
+ add_internet_password(protocol, hostname, c.username, c.password,
+ c.description ? c.description : "default");
+ emit_user_pass(c.username, c.password);
+ return 0;
+}
--
1.7.7.rc1.1.g011e1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] contrib: add a credential helper for Mac OS X's keychain
2011-09-14 17:58 Jay Soffian
@ 2011-09-14 18:19 ` Jay Soffian
0 siblings, 0 replies; 7+ messages in thread
From: Jay Soffian @ 2011-09-14 18:19 UTC (permalink / raw)
To: git; +Cc: Jay Soffian, Jeff King, John Szakmeister, Junio C Hamano
On Wed, Sep 14, 2011 at 1:58 PM, Jay Soffian <jaysoffian@gmail.com> wrote:
> diff --git a/contrib/credential-osxkeychain/git-credential-osxkeychain.c b/contrib/credential-osxkeychain/git-credential-osxkeychain.c
> new file mode 100644
> index 0000000000..64bcc636cb
> --- /dev/null
> +++ b/contrib/credential-osxkeychain/git-credential-osxkeychain.c
>
> [...]
>
> +int find_internet_password(SecProtocolType protocol,
> + char *hostname,
> + char *username)
> +{
> + void *password_buf;
> + UInt32 password_len;
> + OSStatus status;
> + char *password;
> + int free_username;
int free_username = 0;
j.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] contrib: add a credential helper for Mac OS X's keychain
2011-09-14 13:31 ` Jay Soffian
@ 2011-09-14 22:23 ` John Szakmeister
0 siblings, 0 replies; 7+ messages in thread
From: John Szakmeister @ 2011-09-14 22:23 UTC (permalink / raw)
To: Jay Soffian; +Cc: git, Junio C Hamano, Jeff King
On Wed, Sep 14, 2011 at 9:31 AM, Jay Soffian <jaysoffian@gmail.com> wrote:
[snip]
> Nope, you snipped out too much context. That command is turned into a
> string and then sent to /usr/bin/security on its stdin. It is
> absolutely not passed on the command-line.
Bah. I see it now. Thanks.
-John
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2011-09-14 22:23 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-09-10 19:44 [PATCH] contrib: add a credential helper for Mac OS X's keychain Jay Soffian
2011-09-14 8:01 ` John Szakmeister
2011-09-14 13:31 ` Jay Soffian
2011-09-14 22:23 ` John Szakmeister
2011-09-14 10:24 ` John Szakmeister
-- strict thread matches above, loose matches on Subject: below --
2011-09-14 17:58 Jay Soffian
2011-09-14 18:19 ` Jay Soffian
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).