All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jay Soffian <jaysoffian@gmail.com>
To: git@vger.kernel.org
Cc: Jay Soffian <jaysoffian@gmail.com>, Jeff King <peff@peff.net>,
	John Szakmeister <john@szakmeister.net>,
	Junio C Hamano <gitster@pobox.com>
Subject: [PATCH] contrib: add a credential helper for Mac OS X's keychain
Date: Wed, 14 Sep 2011 13:58:37 -0400	[thread overview]
Message-ID: <1316023117-84755-1-git-send-email-jaysoffian@gmail.com> (raw)

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

             reply	other threads:[~2011-09-14 17:58 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-09-14 17:58 Jay Soffian [this message]
2011-09-14 18:19 ` [PATCH] contrib: add a credential helper for Mac OS X's keychain Jay Soffian
2011-09-14 22:55 ` [PATCH] credential-osxkeychain: load Security framework dynamically Jay Soffian
2011-09-14 23:08   ` Jeff King
2011-09-14 23:56     ` Jay Soffian
2011-09-15  0:16       ` Jeff King
2011-09-14 23:18   ` Junio C Hamano
  -- strict thread matches above, loose matches on Subject: below --
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1316023117-84755-1-git-send-email-jaysoffian@gmail.com \
    --to=jaysoffian@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=john@szakmeister.net \
    --cc=peff@peff.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.