From: Jeff King <peff@peff.net>
To: Junio C Hamano <gitster@pobox.com>
Cc: Jay Soffian <jaysoffian@gmail.com>, git@vger.kernel.org
Subject: [PATCH 1/1] contrib: add credential helper for OS X Keychain
Date: Sat, 10 Dec 2011 05:53:14 -0500 [thread overview]
Message-ID: <20111210105314.GB16478@sigill.intra.peff.net> (raw)
In-Reply-To: <20111210102827.GA16460@sigill.intra.peff.net>
On Sat, Dec 10, 2011 at 05:28:27AM -0500, Jeff King wrote:
> Here's the latest re-roll of the credential helpers series. I think this
> one is probably ready to go to 'next'.
>
> It's rebased on the latest tip of 'master' (applying it to an older
> commit will get you a minor textual conflict in strbuf.c). It
> incorporates the erase-safety we discussed, fixes a few commit message
> typos, and tweaks the test scripts to make testing the external OS X
> helper a little easier.
And here is that helper.
-- >8 --
Subject: [PATCH] contrib: add credential helper for OS X Keychain
With this installed in your $PATH, you can store
git-over-http passwords in your keychain by doing:
git config credential.helper osxkeychain
The code is based in large part on the work of Jay Soffian,
who wrote the helper originally for the initial, unpublished
version of the credential helper protocol.
This version will pass t0303 if you do:
GIT_TEST_CREDENTIAL_HELPER=osxkeychain \
GIT_TEST_CREDENTIAL_HELPER_SETUP="export HOME=$HOME" \
./t0303-credential-external.sh
The "HOME" setup is unfortunately necessary. The test
scripts set HOME to the trash directory, but this causes the
keychain API to complain.
Signed-off-by: Jeff King <peff@peff.net>
---
I tried clever workarounds for the HOME thing, like running "security
create-keychain" in the trash directory. But if I try to create the
"login" keychain, it says it's already there! So it's like we're in a
weird limbo where we both have and do not have access to the keychains.
I'm not too concerned with the hackishness of my solution, though; this
isn't even a part of the regular test scripts, but just a thing you can
do manually to test the helper from contrib.
Many thanks to Jay for the initial version; even though this version is
quite chopped-up, having somebody else provide a nice example of how to
use the SecKeychain functions made it much easier. I tried to trim the
code down to the bare essentials to glue our input into the SecKeychain
API, and to show good practices for other helpers to follow.
I dropped the python version, as it is redundant and I didn't feel like
porting it to the new interface. It could be added on top if somebody
feels like converting it as an exercise. :)
contrib/credential/osxkeychain/.gitignore | 1 +
contrib/credential/osxkeychain/Makefile | 14 ++
.../osxkeychain/git-credential-osxkeychain.c | 173 ++++++++++++++++++++
3 files changed, 188 insertions(+), 0 deletions(-)
create mode 100644 contrib/credential/osxkeychain/.gitignore
create mode 100644 contrib/credential/osxkeychain/Makefile
create mode 100644 contrib/credential/osxkeychain/git-credential-osxkeychain.c
diff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore
new file mode 100644
index 0000000..6c5b702
--- /dev/null
+++ b/contrib/credential/osxkeychain/.gitignore
@@ -0,0 +1 @@
+git-credential-osxkeychain
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile
new file mode 100644
index 0000000..75c07f8
--- /dev/null
+++ b/contrib/credential/osxkeychain/Makefile
@@ -0,0 +1,14 @@
+all:: git-credential-osxkeychain
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -Wall
+
+git-credential-osxkeychain: git-credential-osxkeychain.o
+ $(CC) -o $@ $< -Wl,-framework -Wl,Security
+
+git-credential-osxkeychain.o: git-credential-osxkeychain.c
+ $(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 0000000..6beed12
--- /dev/null
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <Security/Security.h>
+
+static SecProtocolType protocol;
+static char *host;
+static char *path;
+static char *username;
+static char *password;
+static UInt16 port;
+
+static void die(const char *err, ...)
+{
+ char msg[4096];
+ va_list params;
+ va_start(params, err);
+ vsnprintf(msg, sizeof(msg), err, params);
+ fprintf(stderr, "%s\n", msg);
+ va_end(params);
+ exit(1);
+}
+
+static void *xstrdup(const char *s1)
+{
+ void *ret = strdup(s1);
+ if (!ret)
+ die("Out of memory");
+ return ret;
+}
+
+#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
+#define KEYCHAIN_ARGS \
+ NULL, /* default keychain */ \
+ KEYCHAIN_ITEM(host), \
+ 0, NULL, /* account domain */ \
+ KEYCHAIN_ITEM(username), \
+ KEYCHAIN_ITEM(path), \
+ port, \
+ protocol, \
+ kSecAuthenticationTypeDefault
+
+static void write_item(const char *what, const char *buf, int len)
+{
+ printf("%s=", what);
+ fwrite(buf, 1, len, stdout);
+ putchar('\n');
+}
+
+static void find_username_in_item(SecKeychainItemRef item)
+{
+ SecKeychainAttributeList list;
+ SecKeychainAttribute attr;
+
+ list.count = 1;
+ list.attr = &attr;
+ attr.tag = kSecAccountItemAttr;
+
+ if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
+ return;
+
+ write_item("username", attr.data, attr.length);
+ SecKeychainItemFreeContent(&list, NULL);
+}
+
+static void find_internet_password(void)
+{
+ void *buf;
+ UInt32 len;
+ SecKeychainItemRef item;
+
+ if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
+ return;
+
+ write_item("password", buf, len);
+ if (!username)
+ find_username_in_item(item);
+
+ SecKeychainItemFreeContent(NULL, buf);
+}
+
+static void delete_internet_password(void)
+{
+ SecKeychainItemRef item;
+
+ /*
+ * Require at least a protocol and host for removal, which is what git
+ * will give us; if you want to do something more fancy, use the
+ * Keychain manager.
+ */
+ if (!protocol || !host)
+ return;
+
+ if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
+ return;
+
+ SecKeychainItemDelete(item);
+}
+
+static void add_internet_password(void)
+{
+ /* Only store complete credentials */
+ if (!protocol || !host || !username || !password)
+ return;
+
+ if (SecKeychainAddInternetPassword(
+ KEYCHAIN_ARGS,
+ KEYCHAIN_ITEM(password),
+ NULL))
+ return;
+}
+
+static void read_credential(void)
+{
+ char buf[1024];
+
+ while (fgets(buf, sizeof(buf), stdin)) {
+ char *v;
+
+ if (!strcmp(buf, "\n"))
+ break;
+ buf[strlen(buf)-1] = '\0';
+
+ v = strchr(buf, '=');
+ if (!v)
+ die("bad input: %s", buf);
+ *v++ = '\0';
+
+ if (!strcmp(buf, "protocol")) {
+ if (!strcmp(v, "https"))
+ protocol = kSecProtocolTypeHTTPS;
+ else if (!strcmp(v, "http"))
+ protocol = kSecProtocolTypeHTTP;
+ else /* we don't yet handle other protocols */
+ exit(0);
+ }
+ else if (!strcmp(buf, "host")) {
+ char *colon = strchr(v, ':');
+ if (colon) {
+ *colon++ = '\0';
+ port = atoi(colon);
+ }
+ host = xstrdup(v);
+ }
+ else if (!strcmp(buf, "path"))
+ path = xstrdup(v);
+ else if (!strcmp(buf, "username"))
+ username = xstrdup(v);
+ else if (!strcmp(buf, "password"))
+ password = xstrdup(v);
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ const char *usage =
+ "Usage: git credential-osxkeychain <get|store|erase>";
+
+ if (!argv[1])
+ die(usage);
+
+ read_credential();
+
+ if (!strcmp(argv[1], "get"))
+ find_internet_password();
+ else if (!strcmp(argv[1], "store"))
+ add_internet_password();
+ else if (!strcmp(argv[1], "erase"))
+ delete_internet_password();
+ /* otherwise, ignore unknown action */
+
+ return 0;
+}
--
1.7.8.rc2.40.gaf387
prev parent reply other threads:[~2011-12-10 10:53 UTC|newest]
Thread overview: 54+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-12-10 10:28 [PATCHv3 0/13] credential helpers Jeff King
2011-12-10 10:30 ` [PATCHv3 01/13] test-lib: add test_config_global variant Jeff King
2011-12-10 10:30 ` [PATCHv3 02/13] t5550: fix typo Jeff King
2011-12-10 10:31 ` [PATCHv3 03/13] introduce credentials API Jeff King
2011-12-10 11:43 ` Jakub Narebski
2011-12-10 19:48 ` Jeff King
2011-12-10 10:31 ` [PATCHv3 04/13] credential: add function for parsing url components Jeff King
2011-12-10 10:31 ` [PATCHv3 05/13] http: use credential API to get passwords Jeff King
2011-12-10 10:31 ` [PATCHv3 06/13] credential: apply helper config Jeff King
2011-12-10 10:31 ` [PATCHv3 07/13] credential: add credential.*.username Jeff King
2011-12-10 10:31 ` [PATCHv3 08/13] credential: make relevance of http path configurable Jeff King
2011-12-10 11:50 ` Jakub Narebski
2011-12-10 19:50 ` Jeff King
2011-12-10 10:31 ` [PATCHv3 09/13] docs: end-user documentation for the credential subsystem Jeff King
2011-12-10 10:34 ` [PATCHv3 10/13] credentials: add "cache" helper Jeff King
2012-01-10 1:50 ` Jonathan Nieder
2012-01-10 4:44 ` Jeff King
2012-01-10 4:57 ` Jeff King
2012-01-10 16:59 ` Junio C Hamano
2012-01-17 6:02 ` Jeff King
2012-01-17 6:51 ` Junio C Hamano
2012-01-10 17:44 ` Jonathan Nieder
2012-01-10 17:53 ` Jeff King
2012-01-11 23:50 ` Jonathan Nieder
2012-01-12 3:07 ` Jeff King
2011-12-10 10:34 ` [PATCHv3 11/13] strbuf: add strbuf_add*_urlencode Jeff King
2011-12-10 11:57 ` Jakub Narebski
2011-12-10 20:09 ` Jeff King
2011-12-10 10:34 ` [PATCHv3 12/13] credentials: add "store" helper Jeff King
2011-12-10 10:35 ` [PATCHv3 13/13] t: add test harness for external credential helpers Jeff King
2011-12-10 10:39 ` [PATCHv2 0/9] echo characters in username prompt Jeff King
2011-12-10 10:40 ` [PATCHv2 1/9] imap-send: avoid buffer overflow Jeff King
2011-12-10 10:40 ` [PATCHv2 2/9] imap-send: don't check return value of git_getpass Jeff King
2011-12-10 10:40 ` [PATCHv2 3/9] move git_getpass to its own source file Jeff King
2011-12-10 10:40 ` [PATCHv2 4/9] refactor git_getpass into generic prompt function Jeff King
2011-12-10 10:41 ` [PATCHv2 5/9] add generic terminal " Jeff King
2011-12-15 12:48 ` Pete Wyckoff
2011-12-15 13:39 ` Jeff King
2011-12-15 21:59 ` Pete Wyckoff
2011-12-10 10:41 ` [PATCHv2 6/9] prompt: use git_terminal_prompt Jeff King
2011-12-10 10:41 ` [PATCHv2 7/9] credential: use git_prompt instead of git_getpass Jeff King
2011-12-10 10:41 ` [PATCHv2 8/9] Makefile: linux has /dev/tty Jeff King
2011-12-10 10:41 ` [PATCHv2 9/9] Makefile: OS X " Jeff King
2011-12-12 21:10 ` [PATCH 1/2] Makefile: Windows lacks /dev/tty Johannes Sixt
2011-12-12 21:12 ` [PATCH 2/2] Makefile: optionally exclude code that needs Unix sockets Johannes Sixt
2011-12-12 21:39 ` Jeff King
2011-12-12 23:31 ` Junio C Hamano
2011-12-13 0:58 ` Jeff King
2011-12-13 0:45 ` Junio C Hamano
2011-12-13 20:00 ` Johannes Sixt
2011-12-14 0:14 ` Junio C Hamano
2011-12-12 21:18 ` [PATCH 1/2] Makefile: Windows lacks /dev/tty Jeff King
2011-12-12 21:52 ` Johannes Sixt
2011-12-10 10:53 ` Jeff King [this message]
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=20111210105314.GB16478@sigill.intra.peff.net \
--to=peff@peff.net \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jaysoffian@gmail.com \
/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 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).