Skip to content

Commit 34961d3

Browse files
peffgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3f3a970 commit 34961d3

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
git-credential-osxkeychain
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
all:: git-credential-osxkeychain
2+
3+
CC = gcc
4+
RM = rm -f
5+
CFLAGS = -g -Wall
6+
7+
git-credential-osxkeychain: git-credential-osxkeychain.o
8+
$(CC) -o $@ $< -Wl,-framework -Wl,Security
9+
10+
git-credential-osxkeychain.o: git-credential-osxkeychain.c
11+
$(CC) -c $(CFLAGS) $<
12+
13+
clean:
14+
$(RM) git-credential-osxkeychain git-credential-osxkeychain.o
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
#include <stdlib.h>
4+
#include <Security/Security.h>
5+
6+
static SecProtocolType protocol;
7+
static char *host;
8+
static char *path;
9+
static char *username;
10+
static char *password;
11+
static UInt16 port;
12+
13+
static void die(const char *err, ...)
14+
{
15+
char msg[4096];
16+
va_list params;
17+
va_start(params, err);
18+
vsnprintf(msg, sizeof(msg), err, params);
19+
fprintf(stderr, "%s\n", msg);
20+
va_end(params);
21+
exit(1);
22+
}
23+
24+
static void *xstrdup(const char *s1)
25+
{
26+
void *ret = strdup(s1);
27+
if (!ret)
28+
die("Out of memory");
29+
return ret;
30+
}
31+
32+
#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
33+
#define KEYCHAIN_ARGS \
34+
NULL, /* default keychain */ \
35+
KEYCHAIN_ITEM(host), \
36+
0, NULL, /* account domain */ \
37+
KEYCHAIN_ITEM(username), \
38+
KEYCHAIN_ITEM(path), \
39+
port, \
40+
protocol, \
41+
kSecAuthenticationTypeDefault
42+
43+
static void write_item(const char *what, const char *buf, int len)
44+
{
45+
printf("%s=", what);
46+
fwrite(buf, 1, len, stdout);
47+
putchar('\n');
48+
}
49+
50+
static void find_username_in_item(SecKeychainItemRef item)
51+
{
52+
SecKeychainAttributeList list;
53+
SecKeychainAttribute attr;
54+
55+
list.count = 1;
56+
list.attr = &attr;
57+
attr.tag = kSecAccountItemAttr;
58+
59+
if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
60+
return;
61+
62+
write_item("username", attr.data, attr.length);
63+
SecKeychainItemFreeContent(&list, NULL);
64+
}
65+
66+
static void find_internet_password(void)
67+
{
68+
void *buf;
69+
UInt32 len;
70+
SecKeychainItemRef item;
71+
72+
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
73+
return;
74+
75+
write_item("password", buf, len);
76+
if (!username)
77+
find_username_in_item(item);
78+
79+
SecKeychainItemFreeContent(NULL, buf);
80+
}
81+
82+
static void delete_internet_password(void)
83+
{
84+
SecKeychainItemRef item;
85+
86+
/*
87+
* Require at least a protocol and host for removal, which is what git
88+
* will give us; if you want to do something more fancy, use the
89+
* Keychain manager.
90+
*/
91+
if (!protocol || !host)
92+
return;
93+
94+
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
95+
return;
96+
97+
SecKeychainItemDelete(item);
98+
}
99+
100+
static void add_internet_password(void)
101+
{
102+
/* Only store complete credentials */
103+
if (!protocol || !host || !username || !password)
104+
return;
105+
106+
if (SecKeychainAddInternetPassword(
107+
KEYCHAIN_ARGS,
108+
KEYCHAIN_ITEM(password),
109+
NULL))
110+
return;
111+
}
112+
113+
static void read_credential(void)
114+
{
115+
char buf[1024];
116+
117+
while (fgets(buf, sizeof(buf), stdin)) {
118+
char *v;
119+
120+
if (!strcmp(buf, "\n"))
121+
break;
122+
buf[strlen(buf)-1] = '\0';
123+
124+
v = strchr(buf, '=');
125+
if (!v)
126+
die("bad input: %s", buf);
127+
*v++ = '\0';
128+
129+
if (!strcmp(buf, "protocol")) {
130+
if (!strcmp(v, "https"))
131+
protocol = kSecProtocolTypeHTTPS;
132+
else if (!strcmp(v, "http"))
133+
protocol = kSecProtocolTypeHTTP;
134+
else /* we don't yet handle other protocols */
135+
exit(0);
136+
}
137+
else if (!strcmp(buf, "host")) {
138+
char *colon = strchr(v, ':');
139+
if (colon) {
140+
*colon++ = '\0';
141+
port = atoi(colon);
142+
}
143+
host = xstrdup(v);
144+
}
145+
else if (!strcmp(buf, "path"))
146+
path = xstrdup(v);
147+
else if (!strcmp(buf, "username"))
148+
username = xstrdup(v);
149+
else if (!strcmp(buf, "password"))
150+
password = xstrdup(v);
151+
}
152+
}
153+
154+
int main(int argc, const char **argv)
155+
{
156+
const char *usage =
157+
"Usage: git credential-osxkeychain <get|store|erase>";
158+
159+
if (!argv[1])
160+
die(usage);
161+
162+
read_credential();
163+
164+
if (!strcmp(argv[1], "get"))
165+
find_internet_password();
166+
else if (!strcmp(argv[1], "store"))
167+
add_internet_password();
168+
else if (!strcmp(argv[1], "erase"))
169+
delete_internet_password();
170+
/* otherwise, ignore unknown action */
171+
172+
return 0;
173+
}

0 commit comments

Comments
 (0)