Skip to content

Commit 9abe31f

Browse files
Bo98gitster
authored andcommitted
osxkeychain: replace deprecated SecKeychain API
The SecKeychain API was deprecated in macOS 10.10, nearly 10 years ago. The replacement SecItem API however is available as far back as macOS 10.6. While supporting older macOS was perhaps prevously a concern, git-credential-osxkeychain already requires a minimum of macOS 10.7 since 5747c80 (contrib/credential: avoid fixed-size buffer in osxkeychain, 2023-05-01) so using the newer API should not regress the range of macOS versions supported. Adapting to use the newer SecItem API also happens to fix two test failures in osxkeychain: 8 - helper (osxkeychain) overwrites on store 9 - helper (osxkeychain) can forget host The new API is compatible with credentials saved with the older API. Signed-off-by: Bo Anderson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c2cbfbd commit 9abe31f

File tree

2 files changed

+199
-69
lines changed

2 files changed

+199
-69
lines changed

contrib/credential/osxkeychain/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ CFLAGS = -g -O2 -Wall
88
-include ../../../config.mak
99

1010
git-credential-osxkeychain: git-credential-osxkeychain.o
11-
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -Wl,-framework -Wl,Security
11+
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) \
12+
-framework Security -framework CoreFoundation
1213

1314
git-credential-osxkeychain.o: git-credential-osxkeychain.c
1415
$(CC) -c $(CFLAGS) $<

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

Lines changed: 197 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,39 @@
33
#include <stdlib.h>
44
#include <Security/Security.h>
55

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-
__attribute__((format (printf, 1, 2)))
6+
#define ENCODING kCFStringEncodingUTF8
7+
static CFStringRef protocol; /* Stores constant strings - not memory managed */
8+
static CFStringRef host;
9+
static CFStringRef path;
10+
static CFStringRef username;
11+
static CFDataRef password;
12+
static CFNumberRef port;
13+
14+
static void clear_credential(void)
15+
{
16+
if (host) {
17+
CFRelease(host);
18+
host = NULL;
19+
}
20+
if (path) {
21+
CFRelease(path);
22+
path = NULL;
23+
}
24+
if (username) {
25+
CFRelease(username);
26+
username = NULL;
27+
}
28+
if (password) {
29+
CFRelease(password);
30+
password = NULL;
31+
}
32+
if (port) {
33+
CFRelease(port);
34+
port = NULL;
35+
}
36+
}
37+
38+
__attribute__((format (printf, 1, 2), __noreturn__))
1439
static void die(const char *err, ...)
1540
{
1641
char msg[4096];
@@ -19,96 +44,178 @@ static void die(const char *err, ...)
1944
vsnprintf(msg, sizeof(msg), err, params);
2045
fprintf(stderr, "%s\n", msg);
2146
va_end(params);
47+
clear_credential();
2248
exit(1);
2349
}
2450

25-
static void *xstrdup(const char *s1)
51+
static void *xmalloc(size_t len)
2652
{
27-
void *ret = strdup(s1);
53+
void *ret = malloc(len);
2854
if (!ret)
2955
die("Out of memory");
3056
return ret;
3157
}
3258

33-
#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
34-
#define KEYCHAIN_ARGS \
35-
NULL, /* default keychain */ \
36-
KEYCHAIN_ITEM(host), \
37-
0, NULL, /* account domain */ \
38-
KEYCHAIN_ITEM(username), \
39-
KEYCHAIN_ITEM(path), \
40-
port, \
41-
protocol, \
42-
kSecAuthenticationTypeDefault
43-
44-
static void write_item(const char *what, const char *buf, int len)
59+
static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
60+
{
61+
va_list args;
62+
const void *key;
63+
CFMutableDictionaryRef result;
64+
65+
result = CFDictionaryCreateMutable(allocator,
66+
0,
67+
&kCFTypeDictionaryKeyCallBacks,
68+
&kCFTypeDictionaryValueCallBacks);
69+
70+
71+
va_start(args, allocator);
72+
while ((key = va_arg(args, const void *)) != NULL) {
73+
const void *value;
74+
value = va_arg(args, const void *);
75+
if (value)
76+
CFDictionarySetValue(result, key, value);
77+
}
78+
va_end(args);
79+
80+
return result;
81+
}
82+
83+
#define CREATE_SEC_ATTRIBUTES(...) \
84+
create_dictionary(kCFAllocatorDefault, \
85+
kSecClass, kSecClassInternetPassword, \
86+
kSecAttrServer, host, \
87+
kSecAttrAccount, username, \
88+
kSecAttrPath, path, \
89+
kSecAttrPort, port, \
90+
kSecAttrProtocol, protocol, \
91+
kSecAttrAuthenticationType, \
92+
kSecAttrAuthenticationTypeDefault, \
93+
__VA_ARGS__);
94+
95+
static void write_item(const char *what, const char *buf, size_t len)
4596
{
4697
printf("%s=", what);
4798
fwrite(buf, 1, len, stdout);
4899
putchar('\n');
49100
}
50101

51-
static void find_username_in_item(SecKeychainItemRef item)
102+
static void find_username_in_item(CFDictionaryRef item)
52103
{
53-
SecKeychainAttributeList list;
54-
SecKeychainAttribute attr;
104+
CFStringRef account_ref;
105+
char *username_buf;
106+
CFIndex buffer_len;
55107

56-
list.count = 1;
57-
list.attr = &attr;
58-
attr.tag = kSecAccountItemAttr;
108+
account_ref = CFDictionaryGetValue(item, kSecAttrAccount);
109+
if (!account_ref)
110+
{
111+
write_item("username", "", 0);
112+
return;
113+
}
59114

60-
if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
115+
username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
116+
if (username_buf)
117+
{
118+
write_item("username", username_buf, strlen(username_buf));
61119
return;
120+
}
62121

63-
write_item("username", attr.data, attr.length);
64-
SecKeychainItemFreeContent(&list, NULL);
122+
/* If we can't get a CString pointer then
123+
* we need to allocate our own buffer */
124+
buffer_len = CFStringGetMaximumSizeForEncoding(
125+
CFStringGetLength(account_ref), ENCODING) + 1;
126+
username_buf = xmalloc(buffer_len);
127+
if (CFStringGetCString(account_ref,
128+
username_buf,
129+
buffer_len,
130+
ENCODING)) {
131+
write_item("username", username_buf, buffer_len - 1);
132+
}
133+
free(username_buf);
65134
}
66135

67-
static void find_internet_password(void)
136+
static OSStatus find_internet_password(void)
68137
{
69-
void *buf;
70-
UInt32 len;
71-
SecKeychainItemRef item;
138+
CFDictionaryRef attrs;
139+
CFDictionaryRef item;
140+
CFDataRef data;
141+
OSStatus result;
72142

73-
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
74-
return;
143+
attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitOne,
144+
kSecReturnAttributes, kCFBooleanTrue,
145+
kSecReturnData, kCFBooleanTrue,
146+
NULL);
147+
result = SecItemCopyMatching(attrs, (CFTypeRef *)&item);
148+
if (result) {
149+
goto out;
150+
}
75151

76-
write_item("password", buf, len);
152+
data = CFDictionaryGetValue(item, kSecValueData);
153+
154+
write_item("password",
155+
(const char *)CFDataGetBytePtr(data),
156+
CFDataGetLength(data));
77157
if (!username)
78158
find_username_in_item(item);
79159

80-
SecKeychainItemFreeContent(NULL, buf);
160+
CFRelease(item);
161+
162+
out:
163+
CFRelease(attrs);
164+
165+
/* We consider not found to not be an error */
166+
if (result == errSecItemNotFound)
167+
result = errSecSuccess;
168+
169+
return result;
81170
}
82171

83-
static void delete_internet_password(void)
172+
static OSStatus delete_internet_password(void)
84173
{
85-
SecKeychainItemRef item;
174+
CFDictionaryRef attrs;
175+
OSStatus result;
86176

87177
/*
88178
* Require at least a protocol and host for removal, which is what git
89179
* will give us; if you want to do something more fancy, use the
90180
* Keychain manager.
91181
*/
92182
if (!protocol || !host)
93-
return;
183+
return -1;
94184

95-
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
96-
return;
185+
attrs = CREATE_SEC_ATTRIBUTES(NULL);
186+
result = SecItemDelete(attrs);
187+
CFRelease(attrs);
188+
189+
/* We consider not found to not be an error */
190+
if (result == errSecItemNotFound)
191+
result = errSecSuccess;
97192

98-
SecKeychainItemDelete(item);
193+
return result;
99194
}
100195

101-
static void add_internet_password(void)
196+
static OSStatus add_internet_password(void)
102197
{
198+
CFDictionaryRef attrs;
199+
OSStatus result;
200+
103201
/* Only store complete credentials */
104202
if (!protocol || !host || !username || !password)
105-
return;
203+
return -1;
106204

107-
if (SecKeychainAddInternetPassword(
108-
KEYCHAIN_ARGS,
109-
KEYCHAIN_ITEM(password),
110-
NULL))
111-
return;
205+
attrs = CREATE_SEC_ATTRIBUTES(kSecValueData, password,
206+
NULL);
207+
208+
result = SecItemAdd(attrs, NULL);
209+
if (result == errSecDuplicateItem) {
210+
CFDictionaryRef query;
211+
query = CREATE_SEC_ATTRIBUTES(NULL);
212+
result = SecItemUpdate(query, attrs);
213+
CFRelease(query);
214+
}
215+
216+
CFRelease(attrs);
217+
218+
return result;
112219
}
113220

114221
static void read_credential(void)
@@ -131,36 +238,52 @@ static void read_credential(void)
131238

132239
if (!strcmp(buf, "protocol")) {
133240
if (!strcmp(v, "imap"))
134-
protocol = kSecProtocolTypeIMAP;
241+
protocol = kSecAttrProtocolIMAP;
135242
else if (!strcmp(v, "imaps"))
136-
protocol = kSecProtocolTypeIMAPS;
243+
protocol = kSecAttrProtocolIMAPS;
137244
else if (!strcmp(v, "ftp"))
138-
protocol = kSecProtocolTypeFTP;
245+
protocol = kSecAttrProtocolFTP;
139246
else if (!strcmp(v, "ftps"))
140-
protocol = kSecProtocolTypeFTPS;
247+
protocol = kSecAttrProtocolFTPS;
141248
else if (!strcmp(v, "https"))
142-
protocol = kSecProtocolTypeHTTPS;
249+
protocol = kSecAttrProtocolHTTPS;
143250
else if (!strcmp(v, "http"))
144-
protocol = kSecProtocolTypeHTTP;
251+
protocol = kSecAttrProtocolHTTP;
145252
else if (!strcmp(v, "smtp"))
146-
protocol = kSecProtocolTypeSMTP;
147-
else /* we don't yet handle other protocols */
253+
protocol = kSecAttrProtocolSMTP;
254+
else {
255+
/* we don't yet handle other protocols */
256+
clear_credential();
148257
exit(0);
258+
}
149259
}
150260
else if (!strcmp(buf, "host")) {
151261
char *colon = strchr(v, ':');
152262
if (colon) {
263+
UInt16 port_i;
153264
*colon++ = '\0';
154-
port = atoi(colon);
265+
port_i = atoi(colon);
266+
port = CFNumberCreate(kCFAllocatorDefault,
267+
kCFNumberShortType,
268+
&port_i);
155269
}
156-
host = xstrdup(v);
270+
host = CFStringCreateWithCString(kCFAllocatorDefault,
271+
v,
272+
ENCODING);
157273
}
158274
else if (!strcmp(buf, "path"))
159-
path = xstrdup(v);
275+
path = CFStringCreateWithCString(kCFAllocatorDefault,
276+
v,
277+
ENCODING);
160278
else if (!strcmp(buf, "username"))
161-
username = xstrdup(v);
279+
username = CFStringCreateWithCString(
280+
kCFAllocatorDefault,
281+
v,
282+
ENCODING);
162283
else if (!strcmp(buf, "password"))
163-
password = xstrdup(v);
284+
password = CFDataCreate(kCFAllocatorDefault,
285+
(UInt8 *)v,
286+
strlen(v));
164287
/*
165288
* Ignore other lines; we don't know what they mean, but
166289
* this future-proofs us when later versions of git do
@@ -173,6 +296,7 @@ static void read_credential(void)
173296

174297
int main(int argc, const char **argv)
175298
{
299+
OSStatus result = 0;
176300
const char *usage =
177301
"usage: git credential-osxkeychain <get|store|erase>";
178302

@@ -182,12 +306,17 @@ int main(int argc, const char **argv)
182306
read_credential();
183307

184308
if (!strcmp(argv[1], "get"))
185-
find_internet_password();
309+
result = find_internet_password();
186310
else if (!strcmp(argv[1], "store"))
187-
add_internet_password();
311+
result = add_internet_password();
188312
else if (!strcmp(argv[1], "erase"))
189-
delete_internet_password();
313+
result = delete_internet_password();
190314
/* otherwise, ignore unknown action */
191315

316+
if (result)
317+
die("failed to %s: %d", argv[1], (int)result);
318+
319+
clear_credential();
320+
192321
return 0;
193322
}

0 commit comments

Comments
 (0)