osxkeychain: store new attributes

d208bfdfef (credential: new attribute password_expiry_utc, 2023-02-18) and a5c76569e7 (credential: new attribute oauth_refresh_token, 2023-04-21) introduced new credential attributes but support was missing from git-credential-osxkeychain. Support these attributes by appending the data to the password in the keychain, separated by line breaks. Line breaks cannot appear in a git credential password so it is an appropriate separator. Fixes the remaining test failures with osxkeychain: 18 - helper (osxkeychain) gets password_expiry_utc 19 - helper (osxkeychain) overwrites when password_expiry_utc changes 21 - helper (osxkeychain) gets oauth_refresh_token Signed-off-by: Bo Anderson <mail@boanderson.me> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Bo Anderson committed Feb 17, 2024 at 23:34 UTC d5b35bba86e6fdf0484ea71bf5b8ef1167f14015
1 file changed +62 -6
contrib/credential/osxkeychain/git-credential-osxkeychain.c
+62 -6
@@ -6,10 +6,12 @@
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;
12 -static CFNumberRef port;
13 +static CFDataRef password_expiry_utc;
14 +static CFDataRef oauth_refresh_token;
15
16 static void clear_credential(void)
17 {
@@ -17,6 +19,10 @@ static void clear_credential(void)
19 CFRelease(host);
20 host = NULL;
21 }
22 + if (port) {
23 + CFRelease(port);
24 + port = NULL;
25 + }
26 if (path) {
27 CFRelease(path);
28 path = NULL;
@@ -29,12 +35,18 @@ static void clear_credential(void)
35 CFRelease(password);
36 password = NULL;
37 }
32 - if (port) {
33 - CFRelease(port);
34 - port = NULL;
38 + if (password_expiry_utc) {
39 + CFRelease(password_expiry_utc);
40 + password_expiry_utc = NULL;
41 + }
42 + if (oauth_refresh_token) {
43 + CFRelease(oauth_refresh_token);
44 + oauth_refresh_token = NULL;
45 }
46 }
47
48 +#define STRING_WITH_LENGTH(s) s, sizeof(s) - 1
49 +
50 __attribute__((format (printf, 1, 2), __noreturn__))
51 static void die(const char *err, ...)
52 {
@@ -197,9 +209,27 @@ static OSStatus delete_ref(const void *itemRef)
209 CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
210 result = SecItemCopyMatching(query, (CFTypeRef *)&data);
211 if (!result) {
200 - if (CFEqual(data, password))
212 + CFDataRef kc_password;
213 + const UInt8 *raw_data;
214 + const UInt8 *line;
215 +
216 + /* Don't match appended metadata */
217 + raw_data = CFDataGetBytePtr(data);
218 + line = memchr(raw_data, '\n', CFDataGetLength(data));
219 + if (line)
220 + kc_password = CFDataCreateWithBytesNoCopy(
221 + kCFAllocatorDefault,
222 + raw_data,
223 + line - raw_data,
224 + kCFAllocatorNull);
225 + else
226 + kc_password = data;
227 +
228 + if (CFEqual(kc_password, password))
229 result = SecItemDelete(delete_query);
230
231 + if (line)
232 + CFRelease(kc_password);
233 CFRelease(data);
234 }
235
@@ -250,6 +280,7 @@ static OSStatus delete_internet_password(void)
280
281 static OSStatus add_internet_password(void)
282 {
283 + CFMutableDataRef data;
284 CFDictionaryRef attrs;
285 OSStatus result;
286
@@ -257,7 +288,23 @@ static OSStatus add_internet_password(void)
288 if (!protocol || !host || !username || !password)
289 return -1;
290
260 - attrs = CREATE_SEC_ATTRIBUTES(kSecValueData, password,
291 + data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password);
292 + if (password_expiry_utc) {
293 + CFDataAppendBytes(data,
294 + (const UInt8 *)STRING_WITH_LENGTH("\npassword_expiry_utc="));
295 + CFDataAppendBytes(data,
296 + CFDataGetBytePtr(password_expiry_utc),
297 + CFDataGetLength(password_expiry_utc));
298 + }
299 + if (oauth_refresh_token) {
300 + CFDataAppendBytes(data,
301 + (const UInt8 *)STRING_WITH_LENGTH("\noauth_refresh_token="));
302 + CFDataAppendBytes(data,
303 + CFDataGetBytePtr(oauth_refresh_token),
304 + CFDataGetLength(oauth_refresh_token));
305 + }
306 +
307 + attrs = CREATE_SEC_ATTRIBUTES(kSecValueData, data,
308 NULL);
309
310 result = SecItemAdd(attrs, NULL);
@@ -268,6 +315,7 @@ static OSStatus add_internet_password(void)
315 CFRelease(query);
316 }
317
318 + CFRelease(data);
319 CFRelease(attrs);
320
321 return result;
@@ -339,6 +387,14 @@ static void read_credential(void)
387 password = CFDataCreate(kCFAllocatorDefault,
388 (UInt8 *)v,
389 strlen(v));
390 + else if (!strcmp(buf, "password_expiry_utc"))
391 + password_expiry_utc = CFDataCreate(kCFAllocatorDefault,
392 + (UInt8 *)v,
393 + strlen(v));
394 + else if (!strcmp(buf, "oauth_refresh_token"))
395 + oauth_refresh_token = CFDataCreate(kCFAllocatorDefault,
396 + (UInt8 *)v,
397 + strlen(v));
398 /*
399 * Ignore other lines; we don't know what they mean, but
400 * this future-proofs us when later versions of git do