Browse Source

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>
maint
Jeff King 13 years ago committed by Junio C Hamano
parent
commit
34961d30da
  1. 1
      contrib/credential/osxkeychain/.gitignore
  2. 14
      contrib/credential/osxkeychain/Makefile
  3. 173
      contrib/credential/osxkeychain/git-credential-osxkeychain.c

1
contrib/credential/osxkeychain/.gitignore vendored

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

14
contrib/credential/osxkeychain/Makefile

@ -0,0 +1,14 @@ @@ -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

173
contrib/credential/osxkeychain/git-credential-osxkeychain.c

@ -0,0 +1,173 @@ @@ -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;
}
Loading…
Cancel
Save