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>
Jeff King committed
Dec 10, 2011 at 05:53 UTC
34961d30dae69b00a8a5aabd568fb87f376ebb87
3 files changed
+188
contrib/credential/osxkeychain/.gitignore
new
+1
@@ -0,0 +1 @@
1
+git-credential-osxkeychain
contrib/credential/osxkeychain/Makefile
new
+14
@@ -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
contrib/credential/osxkeychain/git-credential-osxkeychain.c
new
+173
@@ -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
+}