зеркало из https://github.com/microsoft/git.git
Merge branch 'ba/osxkeychain-updates'
Update osxkeychain backend with features required for the recent credential subsystem. * ba/osxkeychain-updates: osxkeychain: store new attributes osxkeychain: erase matching passwords only osxkeychain: erase all matching credentials osxkeychain: replace deprecated SecKeychain API
This commit is contained in:
Коммит
51c15ac1b6
|
@ -8,7 +8,8 @@ CFLAGS = -g -O2 -Wall
|
|||
-include ../../../config.mak
|
||||
|
||||
git-credential-osxkeychain: git-credential-osxkeychain.o
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -Wl,-framework -Wl,Security
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) \
|
||||
-framework Security -framework CoreFoundation
|
||||
|
||||
git-credential-osxkeychain.o: git-credential-osxkeychain.c
|
||||
$(CC) -c $(CFLAGS) $<
|
||||
|
|
|
@ -3,14 +3,51 @@
|
|||
#include <stdlib.h>
|
||||
#include <Security/Security.h>
|
||||
|
||||
static SecProtocolType protocol;
|
||||
static char *host;
|
||||
static char *path;
|
||||
static char *username;
|
||||
static char *password;
|
||||
static UInt16 port;
|
||||
#define ENCODING kCFStringEncodingUTF8
|
||||
static CFStringRef protocol; /* Stores constant strings - not memory managed */
|
||||
static CFStringRef host;
|
||||
static CFNumberRef port;
|
||||
static CFStringRef path;
|
||||
static CFStringRef username;
|
||||
static CFDataRef password;
|
||||
static CFDataRef password_expiry_utc;
|
||||
static CFDataRef oauth_refresh_token;
|
||||
|
||||
__attribute__((format (printf, 1, 2)))
|
||||
static void clear_credential(void)
|
||||
{
|
||||
if (host) {
|
||||
CFRelease(host);
|
||||
host = NULL;
|
||||
}
|
||||
if (port) {
|
||||
CFRelease(port);
|
||||
port = NULL;
|
||||
}
|
||||
if (path) {
|
||||
CFRelease(path);
|
||||
path = NULL;
|
||||
}
|
||||
if (username) {
|
||||
CFRelease(username);
|
||||
username = NULL;
|
||||
}
|
||||
if (password) {
|
||||
CFRelease(password);
|
||||
password = NULL;
|
||||
}
|
||||
if (password_expiry_utc) {
|
||||
CFRelease(password_expiry_utc);
|
||||
password_expiry_utc = NULL;
|
||||
}
|
||||
if (oauth_refresh_token) {
|
||||
CFRelease(oauth_refresh_token);
|
||||
oauth_refresh_token = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define STRING_WITH_LENGTH(s) s, sizeof(s) - 1
|
||||
|
||||
__attribute__((format (printf, 1, 2), __noreturn__))
|
||||
static void die(const char *err, ...)
|
||||
{
|
||||
char msg[4096];
|
||||
|
@ -19,70 +56,199 @@ static void die(const char *err, ...)
|
|||
vsnprintf(msg, sizeof(msg), err, params);
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
va_end(params);
|
||||
clear_credential();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *xstrdup(const char *s1)
|
||||
static void *xmalloc(size_t len)
|
||||
{
|
||||
void *ret = strdup(s1);
|
||||
void *ret = malloc(len);
|
||||
if (!ret)
|
||||
die("Out of memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
|
||||
#define KEYCHAIN_ARGS \
|
||||
NULL, /* default keychain */ \
|
||||
KEYCHAIN_ITEM(host), \
|
||||
0, NULL, /* account domain */ \
|
||||
KEYCHAIN_ITEM(username), \
|
||||
KEYCHAIN_ITEM(path), \
|
||||
port, \
|
||||
protocol, \
|
||||
kSecAuthenticationTypeDefault
|
||||
static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
|
||||
{
|
||||
va_list args;
|
||||
const void *key;
|
||||
CFMutableDictionaryRef result;
|
||||
|
||||
static void write_item(const char *what, const char *buf, int len)
|
||||
result = CFDictionaryCreateMutable(allocator,
|
||||
0,
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
|
||||
va_start(args, allocator);
|
||||
while ((key = va_arg(args, const void *)) != NULL) {
|
||||
const void *value;
|
||||
value = va_arg(args, const void *);
|
||||
if (value)
|
||||
CFDictionarySetValue(result, key, value);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CREATE_SEC_ATTRIBUTES(...) \
|
||||
create_dictionary(kCFAllocatorDefault, \
|
||||
kSecClass, kSecClassInternetPassword, \
|
||||
kSecAttrServer, host, \
|
||||
kSecAttrAccount, username, \
|
||||
kSecAttrPath, path, \
|
||||
kSecAttrPort, port, \
|
||||
kSecAttrProtocol, protocol, \
|
||||
kSecAttrAuthenticationType, \
|
||||
kSecAttrAuthenticationTypeDefault, \
|
||||
__VA_ARGS__);
|
||||
|
||||
static void write_item(const char *what, const char *buf, size_t len)
|
||||
{
|
||||
printf("%s=", what);
|
||||
fwrite(buf, 1, len, stdout);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void find_username_in_item(SecKeychainItemRef item)
|
||||
static void find_username_in_item(CFDictionaryRef item)
|
||||
{
|
||||
SecKeychainAttributeList list;
|
||||
SecKeychainAttribute attr;
|
||||
CFStringRef account_ref;
|
||||
char *username_buf;
|
||||
CFIndex buffer_len;
|
||||
|
||||
list.count = 1;
|
||||
list.attr = &attr;
|
||||
attr.tag = kSecAccountItemAttr;
|
||||
|
||||
if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
|
||||
account_ref = CFDictionaryGetValue(item, kSecAttrAccount);
|
||||
if (!account_ref)
|
||||
{
|
||||
write_item("username", "", 0);
|
||||
return;
|
||||
|
||||
write_item("username", attr.data, attr.length);
|
||||
SecKeychainItemFreeContent(&list, NULL);
|
||||
}
|
||||
|
||||
static void find_internet_password(void)
|
||||
username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
|
||||
if (username_buf)
|
||||
{
|
||||
void *buf;
|
||||
UInt32 len;
|
||||
SecKeychainItemRef item;
|
||||
|
||||
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
|
||||
write_item("username", username_buf, strlen(username_buf));
|
||||
return;
|
||||
}
|
||||
|
||||
write_item("password", buf, len);
|
||||
/* If we can't get a CString pointer then
|
||||
* we need to allocate our own buffer */
|
||||
buffer_len = CFStringGetMaximumSizeForEncoding(
|
||||
CFStringGetLength(account_ref), ENCODING) + 1;
|
||||
username_buf = xmalloc(buffer_len);
|
||||
if (CFStringGetCString(account_ref,
|
||||
username_buf,
|
||||
buffer_len,
|
||||
ENCODING)) {
|
||||
write_item("username", username_buf, buffer_len - 1);
|
||||
}
|
||||
free(username_buf);
|
||||
}
|
||||
|
||||
static OSStatus find_internet_password(void)
|
||||
{
|
||||
CFDictionaryRef attrs;
|
||||
CFDictionaryRef item;
|
||||
CFDataRef data;
|
||||
OSStatus result;
|
||||
|
||||
attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitOne,
|
||||
kSecReturnAttributes, kCFBooleanTrue,
|
||||
kSecReturnData, kCFBooleanTrue,
|
||||
NULL);
|
||||
result = SecItemCopyMatching(attrs, (CFTypeRef *)&item);
|
||||
if (result) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
data = CFDictionaryGetValue(item, kSecValueData);
|
||||
|
||||
write_item("password",
|
||||
(const char *)CFDataGetBytePtr(data),
|
||||
CFDataGetLength(data));
|
||||
if (!username)
|
||||
find_username_in_item(item);
|
||||
|
||||
SecKeychainItemFreeContent(NULL, buf);
|
||||
CFRelease(item);
|
||||
|
||||
out:
|
||||
CFRelease(attrs);
|
||||
|
||||
/* We consider not found to not be an error */
|
||||
if (result == errSecItemNotFound)
|
||||
result = errSecSuccess;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void delete_internet_password(void)
|
||||
static OSStatus delete_ref(const void *itemRef)
|
||||
{
|
||||
SecKeychainItemRef item;
|
||||
CFArrayRef item_ref_list;
|
||||
CFDictionaryRef delete_query;
|
||||
OSStatus result;
|
||||
|
||||
item_ref_list = CFArrayCreate(kCFAllocatorDefault,
|
||||
&itemRef,
|
||||
1,
|
||||
&kCFTypeArrayCallBacks);
|
||||
delete_query = create_dictionary(kCFAllocatorDefault,
|
||||
kSecClass, kSecClassInternetPassword,
|
||||
kSecMatchItemList, item_ref_list,
|
||||
NULL);
|
||||
|
||||
if (password) {
|
||||
/* We only want to delete items with a matching password */
|
||||
CFIndex capacity;
|
||||
CFMutableDictionaryRef query;
|
||||
CFDataRef data;
|
||||
|
||||
capacity = CFDictionaryGetCount(delete_query) + 1;
|
||||
query = CFDictionaryCreateMutableCopy(kCFAllocatorDefault,
|
||||
capacity,
|
||||
delete_query);
|
||||
CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
|
||||
result = SecItemCopyMatching(query, (CFTypeRef *)&data);
|
||||
if (!result) {
|
||||
CFDataRef kc_password;
|
||||
const UInt8 *raw_data;
|
||||
const UInt8 *line;
|
||||
|
||||
/* Don't match appended metadata */
|
||||
raw_data = CFDataGetBytePtr(data);
|
||||
line = memchr(raw_data, '\n', CFDataGetLength(data));
|
||||
if (line)
|
||||
kc_password = CFDataCreateWithBytesNoCopy(
|
||||
kCFAllocatorDefault,
|
||||
raw_data,
|
||||
line - raw_data,
|
||||
kCFAllocatorNull);
|
||||
else
|
||||
kc_password = data;
|
||||
|
||||
if (CFEqual(kc_password, password))
|
||||
result = SecItemDelete(delete_query);
|
||||
|
||||
if (line)
|
||||
CFRelease(kc_password);
|
||||
CFRelease(data);
|
||||
}
|
||||
|
||||
CFRelease(query);
|
||||
} else {
|
||||
result = SecItemDelete(delete_query);
|
||||
}
|
||||
|
||||
CFRelease(delete_query);
|
||||
CFRelease(item_ref_list);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static OSStatus delete_internet_password(void)
|
||||
{
|
||||
CFDictionaryRef attrs;
|
||||
CFArrayRef refs;
|
||||
OSStatus result;
|
||||
|
||||
/*
|
||||
* Require at least a protocol and host for removal, which is what git
|
||||
|
@ -90,25 +256,69 @@ static void delete_internet_password(void)
|
|||
* Keychain manager.
|
||||
*/
|
||||
if (!protocol || !host)
|
||||
return;
|
||||
return -1;
|
||||
|
||||
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
|
||||
return;
|
||||
attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitAll,
|
||||
kSecReturnRef, kCFBooleanTrue,
|
||||
NULL);
|
||||
result = SecItemCopyMatching(attrs, (CFTypeRef *)&refs);
|
||||
CFRelease(attrs);
|
||||
|
||||
SecKeychainItemDelete(item);
|
||||
if (!result) {
|
||||
for (CFIndex i = 0; !result && i < CFArrayGetCount(refs); i++)
|
||||
result = delete_ref(CFArrayGetValueAtIndex(refs, i));
|
||||
|
||||
CFRelease(refs);
|
||||
}
|
||||
|
||||
static void add_internet_password(void)
|
||||
/* We consider not found to not be an error */
|
||||
if (result == errSecItemNotFound)
|
||||
result = errSecSuccess;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static OSStatus add_internet_password(void)
|
||||
{
|
||||
CFMutableDataRef data;
|
||||
CFDictionaryRef attrs;
|
||||
OSStatus result;
|
||||
|
||||
/* Only store complete credentials */
|
||||
if (!protocol || !host || !username || !password)
|
||||
return;
|
||||
return -1;
|
||||
|
||||
if (SecKeychainAddInternetPassword(
|
||||
KEYCHAIN_ARGS,
|
||||
KEYCHAIN_ITEM(password),
|
||||
NULL))
|
||||
return;
|
||||
data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password);
|
||||
if (password_expiry_utc) {
|
||||
CFDataAppendBytes(data,
|
||||
(const UInt8 *)STRING_WITH_LENGTH("\npassword_expiry_utc="));
|
||||
CFDataAppendBytes(data,
|
||||
CFDataGetBytePtr(password_expiry_utc),
|
||||
CFDataGetLength(password_expiry_utc));
|
||||
}
|
||||
if (oauth_refresh_token) {
|
||||
CFDataAppendBytes(data,
|
||||
(const UInt8 *)STRING_WITH_LENGTH("\noauth_refresh_token="));
|
||||
CFDataAppendBytes(data,
|
||||
CFDataGetBytePtr(oauth_refresh_token),
|
||||
CFDataGetLength(oauth_refresh_token));
|
||||
}
|
||||
|
||||
attrs = CREATE_SEC_ATTRIBUTES(kSecValueData, data,
|
||||
NULL);
|
||||
|
||||
result = SecItemAdd(attrs, NULL);
|
||||
if (result == errSecDuplicateItem) {
|
||||
CFDictionaryRef query;
|
||||
query = CREATE_SEC_ATTRIBUTES(NULL);
|
||||
result = SecItemUpdate(query, attrs);
|
||||
CFRelease(query);
|
||||
}
|
||||
|
||||
CFRelease(data);
|
||||
CFRelease(attrs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void read_credential(void)
|
||||
|
@ -131,36 +341,60 @@ static void read_credential(void)
|
|||
|
||||
if (!strcmp(buf, "protocol")) {
|
||||
if (!strcmp(v, "imap"))
|
||||
protocol = kSecProtocolTypeIMAP;
|
||||
protocol = kSecAttrProtocolIMAP;
|
||||
else if (!strcmp(v, "imaps"))
|
||||
protocol = kSecProtocolTypeIMAPS;
|
||||
protocol = kSecAttrProtocolIMAPS;
|
||||
else if (!strcmp(v, "ftp"))
|
||||
protocol = kSecProtocolTypeFTP;
|
||||
protocol = kSecAttrProtocolFTP;
|
||||
else if (!strcmp(v, "ftps"))
|
||||
protocol = kSecProtocolTypeFTPS;
|
||||
protocol = kSecAttrProtocolFTPS;
|
||||
else if (!strcmp(v, "https"))
|
||||
protocol = kSecProtocolTypeHTTPS;
|
||||
protocol = kSecAttrProtocolHTTPS;
|
||||
else if (!strcmp(v, "http"))
|
||||
protocol = kSecProtocolTypeHTTP;
|
||||
protocol = kSecAttrProtocolHTTP;
|
||||
else if (!strcmp(v, "smtp"))
|
||||
protocol = kSecProtocolTypeSMTP;
|
||||
else /* we don't yet handle other protocols */
|
||||
protocol = kSecAttrProtocolSMTP;
|
||||
else {
|
||||
/* we don't yet handle other protocols */
|
||||
clear_credential();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(buf, "host")) {
|
||||
char *colon = strchr(v, ':');
|
||||
if (colon) {
|
||||
UInt16 port_i;
|
||||
*colon++ = '\0';
|
||||
port = atoi(colon);
|
||||
port_i = atoi(colon);
|
||||
port = CFNumberCreate(kCFAllocatorDefault,
|
||||
kCFNumberShortType,
|
||||
&port_i);
|
||||
}
|
||||
host = xstrdup(v);
|
||||
host = CFStringCreateWithCString(kCFAllocatorDefault,
|
||||
v,
|
||||
ENCODING);
|
||||
}
|
||||
else if (!strcmp(buf, "path"))
|
||||
path = xstrdup(v);
|
||||
path = CFStringCreateWithCString(kCFAllocatorDefault,
|
||||
v,
|
||||
ENCODING);
|
||||
else if (!strcmp(buf, "username"))
|
||||
username = xstrdup(v);
|
||||
username = CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
v,
|
||||
ENCODING);
|
||||
else if (!strcmp(buf, "password"))
|
||||
password = xstrdup(v);
|
||||
password = CFDataCreate(kCFAllocatorDefault,
|
||||
(UInt8 *)v,
|
||||
strlen(v));
|
||||
else if (!strcmp(buf, "password_expiry_utc"))
|
||||
password_expiry_utc = CFDataCreate(kCFAllocatorDefault,
|
||||
(UInt8 *)v,
|
||||
strlen(v));
|
||||
else if (!strcmp(buf, "oauth_refresh_token"))
|
||||
oauth_refresh_token = CFDataCreate(kCFAllocatorDefault,
|
||||
(UInt8 *)v,
|
||||
strlen(v));
|
||||
/*
|
||||
* Ignore other lines; we don't know what they mean, but
|
||||
* this future-proofs us when later versions of git do
|
||||
|
@ -173,6 +407,7 @@ static void read_credential(void)
|
|||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
OSStatus result = 0;
|
||||
const char *usage =
|
||||
"usage: git credential-osxkeychain <get|store|erase>";
|
||||
|
||||
|
@ -182,12 +417,17 @@ int main(int argc, const char **argv)
|
|||
read_credential();
|
||||
|
||||
if (!strcmp(argv[1], "get"))
|
||||
find_internet_password();
|
||||
result = find_internet_password();
|
||||
else if (!strcmp(argv[1], "store"))
|
||||
add_internet_password();
|
||||
result = add_internet_password();
|
||||
else if (!strcmp(argv[1], "erase"))
|
||||
delete_internet_password();
|
||||
result = delete_internet_password();
|
||||
/* otherwise, ignore unknown action */
|
||||
|
||||
if (result)
|
||||
die("failed to %s: %d", argv[1], (int)result);
|
||||
|
||||
clear_credential();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче