osxkeychain: erase matching passwords only

Other credential helpers support deleting credentials that match a specified password. See 7144dee3ec (credential/libsecret: erase matching creds only, 2023-07-26) and cb626f8e5c (credential/wincred: erase matching creds only, 2023-07-26). Support this in osxkeychain too by extracting, decrypting and comparing the stored password before deleting. Fixes the following test failure with osxkeychain: 11 - helper (osxkeychain) does not erase a password distinct from input 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 e3cef40db89f5a7c91f4e9d6c4959ca1e41f4647
1 file changed +55 -1
contrib/credential/osxkeychain/git-credential-osxkeychain.c
+55 -1
@@ -169,9 +169,55 @@ out:
169 return result;
170 }
171
172 +static OSStatus delete_ref(const void *itemRef)
173 +{
174 + CFArrayRef item_ref_list;
175 + CFDictionaryRef delete_query;
176 + OSStatus result;
177 +
178 + item_ref_list = CFArrayCreate(kCFAllocatorDefault,
179 + &itemRef,
180 + 1,
181 + &kCFTypeArrayCallBacks);
182 + delete_query = create_dictionary(kCFAllocatorDefault,
183 + kSecClass, kSecClassInternetPassword,
184 + kSecMatchItemList, item_ref_list,
185 + NULL);
186 +
187 + if (password) {
188 + /* We only want to delete items with a matching password */
189 + CFIndex capacity;
190 + CFMutableDictionaryRef query;
191 + CFDataRef data;
192 +
193 + capacity = CFDictionaryGetCount(delete_query) + 1;
194 + query = CFDictionaryCreateMutableCopy(kCFAllocatorDefault,
195 + capacity,
196 + delete_query);
197 + CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
198 + result = SecItemCopyMatching(query, (CFTypeRef *)&data);
199 + if (!result) {
200 + if (CFEqual(data, password))
201 + result = SecItemDelete(delete_query);
202 +
203 + CFRelease(data);
204 + }
205 +
206 + CFRelease(query);
207 + } else {
208 + result = SecItemDelete(delete_query);
209 + }
210 +
211 + CFRelease(delete_query);
212 + CFRelease(item_ref_list);
213 +
214 + return result;
215 +}
216 +
217 static OSStatus delete_internet_password(void)
218 {
219 CFDictionaryRef attrs;
220 + CFArrayRef refs;
221 OSStatus result;
222
223 /*
@@ -183,10 +229,18 @@ static OSStatus delete_internet_password(void)
229 return -1;
230
231 attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitAll,
232 + kSecReturnRef, kCFBooleanTrue,
233 NULL);
187 - result = SecItemDelete(attrs);
234 + result = SecItemCopyMatching(attrs, (CFTypeRef *)&refs);
235 CFRelease(attrs);
236
237 + if (!result) {
238 + for (CFIndex i = 0; !result && i < CFArrayGetCount(refs); i++)
239 + result = delete_ref(CFArrayGetValueAtIndex(refs, i));
240 +
241 + CFRelease(refs);
242 + }
243 +
244 /* We consider not found to not be an error */
245 if (result == errSecItemNotFound)
246 result = errSecSuccess;