4 #include <Security/Security.h>
6 #define ENCODING kCFStringEncodingUTF8
7 static CFStringRef protocol
; /* Stores constant strings - not memory managed */
8 static CFStringRef host
;
9 static CFNumberRef port
;
10 static CFStringRef path
;
11 static CFStringRef username
;
12 static CFDataRef password
;
13 static CFDataRef password_expiry_utc
;
14 static CFDataRef oauth_refresh_token
;
15 static int state_seen
;
17 static void clear_credential(void)
39 if (password_expiry_utc
) {
40 CFRelease(password_expiry_utc
);
41 password_expiry_utc
= NULL
;
43 if (oauth_refresh_token
) {
44 CFRelease(oauth_refresh_token
);
45 oauth_refresh_token
= NULL
;
49 #define STRING_WITH_LENGTH(s) s, sizeof(s) - 1
51 __attribute__((format (printf
, 1, 2), __noreturn__
))
52 static void die(const char *err
, ...)
56 va_start(params
, err
);
57 vsnprintf(msg
, sizeof(msg
), err
, params
);
58 fprintf(stderr
, "%s\n", msg
);
64 static void *xmalloc(size_t len
)
66 void *ret
= malloc(len
);
72 static CFDictionaryRef
create_dictionary(CFAllocatorRef allocator
, ...)
76 CFMutableDictionaryRef result
;
78 result
= CFDictionaryCreateMutable(allocator
,
80 &kCFTypeDictionaryKeyCallBacks
,
81 &kCFTypeDictionaryValueCallBacks
);
84 va_start(args
, allocator
);
85 while ((key
= va_arg(args
, const void *)) != NULL
) {
87 value
= va_arg(args
, const void *);
89 CFDictionarySetValue(result
, key
, value
);
96 #define CREATE_SEC_ATTRIBUTES(...) \
97 create_dictionary(kCFAllocatorDefault, \
98 kSecClass, kSecClassInternetPassword, \
99 kSecAttrServer, host, \
100 kSecAttrAccount, username, \
101 kSecAttrPath, path, \
102 kSecAttrPort, port, \
103 kSecAttrProtocol, protocol, \
104 kSecAttrAuthenticationType, \
105 kSecAttrAuthenticationTypeDefault, \
108 static void write_item(const char *what
, const char *buf
, size_t len
)
111 fwrite(buf
, 1, len
, stdout
);
115 static void find_username_in_item(CFDictionaryRef item
)
117 CFStringRef account_ref
;
121 account_ref
= CFDictionaryGetValue(item
, kSecAttrAccount
);
124 write_item("username", "", 0);
128 username_buf
= (char *)CFStringGetCStringPtr(account_ref
, ENCODING
);
131 write_item("username", username_buf
, strlen(username_buf
));
135 /* If we can't get a CString pointer then
136 * we need to allocate our own buffer */
137 buffer_len
= CFStringGetMaximumSizeForEncoding(
138 CFStringGetLength(account_ref
), ENCODING
) + 1;
139 username_buf
= xmalloc(buffer_len
);
140 if (CFStringGetCString(account_ref
,
144 write_item("username", username_buf
, strlen(username_buf
));
149 static OSStatus
find_internet_password(void)
151 CFDictionaryRef attrs
;
152 CFDictionaryRef item
;
156 attrs
= CREATE_SEC_ATTRIBUTES(kSecMatchLimit
, kSecMatchLimitOne
,
157 kSecReturnAttributes
, kCFBooleanTrue
,
158 kSecReturnData
, kCFBooleanTrue
,
160 result
= SecItemCopyMatching(attrs
, (CFTypeRef
*)&item
);
165 data
= CFDictionaryGetValue(item
, kSecValueData
);
167 write_item("password",
168 (const char *)CFDataGetBytePtr(data
),
169 CFDataGetLength(data
));
171 find_username_in_item(item
);
175 write_item("capability[]", "state", strlen("state"));
176 write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1"));
181 /* We consider not found to not be an error */
182 if (result
== errSecItemNotFound
)
183 result
= errSecSuccess
;
188 static OSStatus
delete_ref(const void *itemRef
)
190 CFArrayRef item_ref_list
;
191 CFDictionaryRef delete_query
;
194 item_ref_list
= CFArrayCreate(kCFAllocatorDefault
,
197 &kCFTypeArrayCallBacks
);
198 delete_query
= create_dictionary(kCFAllocatorDefault
,
199 kSecClass
, kSecClassInternetPassword
,
200 kSecMatchItemList
, item_ref_list
,
204 /* We only want to delete items with a matching password */
206 CFMutableDictionaryRef query
;
209 capacity
= CFDictionaryGetCount(delete_query
) + 1;
210 query
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
,
213 CFDictionarySetValue(query
, kSecReturnData
, kCFBooleanTrue
);
214 result
= SecItemCopyMatching(query
, (CFTypeRef
*)&data
);
216 CFDataRef kc_password
;
217 const UInt8
*raw_data
;
220 /* Don't match appended metadata */
221 raw_data
= CFDataGetBytePtr(data
);
222 line
= memchr(raw_data
, '\n', CFDataGetLength(data
));
224 kc_password
= CFDataCreateWithBytesNoCopy(
232 if (CFEqual(kc_password
, password
))
233 result
= SecItemDelete(delete_query
);
236 CFRelease(kc_password
);
242 result
= SecItemDelete(delete_query
);
245 CFRelease(delete_query
);
246 CFRelease(item_ref_list
);
251 static OSStatus
delete_internet_password(void)
253 CFDictionaryRef attrs
;
258 * Require at least a protocol and host for removal, which is what git
259 * will give us; if you want to do something more fancy, use the
262 if (!protocol
|| !host
)
265 attrs
= CREATE_SEC_ATTRIBUTES(kSecMatchLimit
, kSecMatchLimitAll
,
266 kSecReturnRef
, kCFBooleanTrue
,
268 result
= SecItemCopyMatching(attrs
, (CFTypeRef
*)&refs
);
272 for (CFIndex i
= 0; !result
&& i
< CFArrayGetCount(refs
); i
++)
273 result
= delete_ref(CFArrayGetValueAtIndex(refs
, i
));
278 /* We consider not found to not be an error */
279 if (result
== errSecItemNotFound
)
280 result
= errSecSuccess
;
285 static OSStatus
add_internet_password(void)
287 CFMutableDataRef data
;
288 CFDictionaryRef attrs
;
292 return errSecSuccess
;
294 /* Only store complete credentials */
295 if (!protocol
|| !host
|| !username
|| !password
)
298 data
= CFDataCreateMutableCopy(kCFAllocatorDefault
, 0, password
);
299 if (password_expiry_utc
) {
300 CFDataAppendBytes(data
,
301 (const UInt8
*)STRING_WITH_LENGTH("\npassword_expiry_utc="));
302 CFDataAppendBytes(data
,
303 CFDataGetBytePtr(password_expiry_utc
),
304 CFDataGetLength(password_expiry_utc
));
306 if (oauth_refresh_token
) {
307 CFDataAppendBytes(data
,
308 (const UInt8
*)STRING_WITH_LENGTH("\noauth_refresh_token="));
309 CFDataAppendBytes(data
,
310 CFDataGetBytePtr(oauth_refresh_token
),
311 CFDataGetLength(oauth_refresh_token
));
314 attrs
= CREATE_SEC_ATTRIBUTES(kSecValueData
, data
,
317 result
= SecItemAdd(attrs
, NULL
);
318 if (result
== errSecDuplicateItem
) {
319 CFDictionaryRef query
;
320 query
= CREATE_SEC_ATTRIBUTES(NULL
);
321 result
= SecItemUpdate(query
, attrs
);
331 static void read_credential(void)
337 while ((line_len
= getline(&buf
, &alloc
, stdin
)) > 0) {
340 if (!strcmp(buf
, "\n"))
342 buf
[line_len
-1] = '\0';
344 v
= strchr(buf
, '=');
346 die("bad input: %s", buf
);
349 if (!strcmp(buf
, "protocol")) {
350 if (!strcmp(v
, "imap"))
351 protocol
= kSecAttrProtocolIMAP
;
352 else if (!strcmp(v
, "imaps"))
353 protocol
= kSecAttrProtocolIMAPS
;
354 else if (!strcmp(v
, "ftp"))
355 protocol
= kSecAttrProtocolFTP
;
356 else if (!strcmp(v
, "ftps"))
357 protocol
= kSecAttrProtocolFTPS
;
358 else if (!strcmp(v
, "https"))
359 protocol
= kSecAttrProtocolHTTPS
;
360 else if (!strcmp(v
, "http"))
361 protocol
= kSecAttrProtocolHTTP
;
362 else if (!strcmp(v
, "smtp"))
363 protocol
= kSecAttrProtocolSMTP
;
365 /* we don't yet handle other protocols */
370 else if (!strcmp(buf
, "host")) {
371 char *colon
= strchr(v
, ':');
375 port_i
= atoi(colon
);
376 port
= CFNumberCreate(kCFAllocatorDefault
,
380 host
= CFStringCreateWithCString(kCFAllocatorDefault
,
384 else if (!strcmp(buf
, "path"))
385 path
= CFStringCreateWithCString(kCFAllocatorDefault
,
388 else if (!strcmp(buf
, "username"))
389 username
= CFStringCreateWithCString(
393 else if (!strcmp(buf
, "password"))
394 password
= CFDataCreate(kCFAllocatorDefault
,
397 else if (!strcmp(buf
, "password_expiry_utc"))
398 password_expiry_utc
= CFDataCreate(kCFAllocatorDefault
,
401 else if (!strcmp(buf
, "oauth_refresh_token"))
402 oauth_refresh_token
= CFDataCreate(kCFAllocatorDefault
,
405 else if (!strcmp(buf
, "state[]")) {
406 if (!strcmp(v
, "osxkeychain:seen=1"))
410 * Ignore other lines; we don't know what they mean, but
411 * this future-proofs us when later versions of git do
412 * learn new lines, and the helpers are updated to match.
419 int main(int argc
, const char **argv
)
423 "usage: git credential-osxkeychain <get|store|erase>";
428 if (open(argv
[0], O_RDONLY
| O_EXLOCK
) == -1)
429 die("failed to lock %s", argv
[0]);
433 if (!strcmp(argv
[1], "get"))
434 result
= find_internet_password();
435 else if (!strcmp(argv
[1], "store"))
436 result
= add_internet_password();
437 else if (!strcmp(argv
[1], "erase"))
438 result
= delete_internet_password();
439 /* otherwise, ignore unknown action */
442 die("failed to %s: %d", argv
[1], (int)result
);