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>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jeff King 2011-12-10 05:53:14 -05:00 committed by Junio C Hamano
parent 3f3a9701ae
commit 34961d30da
3 changed files with 188 additions and 0 deletions

View file

@ -0,0 +1 @@
git-credential-osxkeychain

View file

@ -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

View file

@ -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;
}