mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
osxkeychain: avoid incorrectly skipping store operation
git-credential-osxkeychain skips storing a credential if its "get"
action sets "state[]=osxkeychain:seen=1". This behavior was introduced
in e1ab45b2 (osxkeychain: state to skip unnecessary store operations,
2024-05-15), which appeared in v2.46.
However, this state[] persists even if a credential returned by
"git-credential-osxkeychain get" is invalid and a subsequent helper's
"get" operation returns a valid credential. Another subsequent helper
(such as [1]) may expect git-credential-osxkeychain to store the valid
credential, but the "store" operation is incorrectly skipped because it
only checks "state[]=osxkeychain:seen=1".
To solve this issue, "state[]=osxkeychain:seen" needs to contain enough
information to identify whether the current "store" input matches the
output from the previous "get" operation (and not a credential from
another helper).
Set "state[]=osxkeychain:seen" to a value encoding the credential output
by "get", and compare it with a value encoding the credential input by
"store".
[1]: https://github.com/hickford/git-credential-oauth
Reported-by: Petter Sælen <petter@saelen.eu>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Koji Nakamaru <koji.nakamaru@gree.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
committed by
Junio C Hamano
parent
fd372d9b1a
commit
4580bcd235
@@ -1,21 +1,55 @@
|
||||
# The default target of this Makefile is...
|
||||
all:: git-credential-osxkeychain
|
||||
|
||||
include ../../../config.mak.uname
|
||||
-include ../../../config.mak.autogen
|
||||
-include ../../../config.mak
|
||||
|
||||
ifdef ZLIB_NG
|
||||
BASIC_CFLAGS += -DHAVE_ZLIB_NG
|
||||
ifdef ZLIB_NG_PATH
|
||||
BASIC_CFLAGS += -I$(ZLIB_NG_PATH)/include
|
||||
EXTLIBS += $(call libpath_template,$(ZLIB_NG_PATH)/$(lib))
|
||||
endif
|
||||
EXTLIBS += -lz-ng
|
||||
else
|
||||
ifdef ZLIB_PATH
|
||||
BASIC_CFLAGS += -I$(ZLIB_PATH)/include
|
||||
EXTLIBS += $(call libpath_template,$(ZLIB_PATH)/$(lib))
|
||||
endif
|
||||
EXTLIBS += -lz
|
||||
endif
|
||||
ifndef NO_ICONV
|
||||
ifdef NEEDS_LIBICONV
|
||||
ifdef ICONVDIR
|
||||
BASIC_CFLAGS += -I$(ICONVDIR)/include
|
||||
ICONV_LINK = $(call libpath_template,$(ICONVDIR)/$(lib))
|
||||
else
|
||||
ICONV_LINK =
|
||||
endif
|
||||
ifdef NEEDS_LIBINTL_BEFORE_LIBICONV
|
||||
ICONV_LINK += -lintl
|
||||
endif
|
||||
EXTLIBS += $(ICONV_LINK) -liconv
|
||||
endif
|
||||
endif
|
||||
ifndef LIBC_CONTAINS_LIBINTL
|
||||
EXTLIBS += -lintl
|
||||
endif
|
||||
|
||||
prefix ?= /usr/local
|
||||
gitexecdir ?= $(prefix)/libexec/git-core
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -g -O2 -Wall
|
||||
CFLAGS ?= -g -O2 -Wall -I../../.. $(BASIC_CFLAGS)
|
||||
LDFLAGS ?= $(BASIC_LDFLAGS) $(EXTLIBS)
|
||||
INSTALL ?= install
|
||||
RM ?= rm -f
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
|
||||
|
||||
git-credential-osxkeychain: git-credential-osxkeychain.o
|
||||
git-credential-osxkeychain: git-credential-osxkeychain.o ../../../libgit.a
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) \
|
||||
-framework Security -framework CoreFoundation
|
||||
|
||||
@@ -23,6 +57,9 @@ install: git-credential-osxkeychain
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
|
||||
$(INSTALL) -m 755 $< $(DESTDIR)$(gitexecdir)
|
||||
|
||||
../../../libgit.a:
|
||||
cd ../../..; make libgit.a
|
||||
|
||||
clean:
|
||||
$(RM) git-credential-osxkeychain git-credential-osxkeychain.o
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <Security/Security.h>
|
||||
#include "git-compat-util.h"
|
||||
#include "strbuf.h"
|
||||
#include "wrapper.h"
|
||||
|
||||
#define ENCODING kCFStringEncodingUTF8
|
||||
static CFStringRef protocol; /* Stores constant strings - not memory managed */
|
||||
@@ -12,7 +15,7 @@ static CFStringRef username;
|
||||
static CFDataRef password;
|
||||
static CFDataRef password_expiry_utc;
|
||||
static CFDataRef oauth_refresh_token;
|
||||
static int state_seen;
|
||||
static char *state_seen;
|
||||
|
||||
static void clear_credential(void)
|
||||
{
|
||||
@@ -48,27 +51,6 @@ static void clear_credential(void)
|
||||
|
||||
#define STRING_WITH_LENGTH(s) s, sizeof(s) - 1
|
||||
|
||||
__attribute__((format (printf, 1, 2), __noreturn__))
|
||||
static void die(const char *err, ...)
|
||||
{
|
||||
char msg[4096];
|
||||
va_list params;
|
||||
va_start(params, err);
|
||||
vsnprintf(msg, sizeof(msg), err, params);
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
va_end(params);
|
||||
clear_credential();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *xmalloc(size_t len)
|
||||
{
|
||||
void *ret = malloc(len);
|
||||
if (!ret)
|
||||
die("Out of memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
|
||||
{
|
||||
va_list args;
|
||||
@@ -112,6 +94,66 @@ static void write_item(const char *what, const char *buf, size_t len)
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void write_item_strbuf(struct strbuf *sb, const char *what, const char *buf, int n)
|
||||
{
|
||||
char s[32];
|
||||
|
||||
xsnprintf(s, sizeof(s), "__%s=", what);
|
||||
strbuf_add(sb, s, strlen(s));
|
||||
strbuf_add(sb, buf, n);
|
||||
}
|
||||
|
||||
static void write_item_strbuf_cfstring(struct strbuf *sb, const char *what, CFStringRef ref)
|
||||
{
|
||||
char *buf;
|
||||
int len;
|
||||
|
||||
if (!ref)
|
||||
return;
|
||||
len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(ref), ENCODING) + 1;
|
||||
buf = xmalloc(len);
|
||||
if (CFStringGetCString(ref, buf, len, ENCODING))
|
||||
write_item_strbuf(sb, what, buf, strlen(buf));
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void write_item_strbuf_cfnumber(struct strbuf *sb, const char *what, CFNumberRef ref)
|
||||
{
|
||||
short n;
|
||||
char buf[32];
|
||||
|
||||
if (!ref)
|
||||
return;
|
||||
if (!CFNumberGetValue(ref, kCFNumberShortType, &n))
|
||||
return;
|
||||
xsnprintf(buf, sizeof(buf), "%d", n);
|
||||
write_item_strbuf(sb, what, buf, strlen(buf));
|
||||
}
|
||||
|
||||
static void write_item_strbuf_cfdata(struct strbuf *sb, const char *what, CFDataRef ref)
|
||||
{
|
||||
char *buf;
|
||||
int len;
|
||||
|
||||
if (!ref)
|
||||
return;
|
||||
buf = (char *)CFDataGetBytePtr(ref);
|
||||
if (!buf || strlen(buf) == 0)
|
||||
return;
|
||||
len = CFDataGetLength(ref);
|
||||
write_item_strbuf(sb, what, buf, len);
|
||||
}
|
||||
|
||||
static void encode_state_seen(struct strbuf *sb)
|
||||
{
|
||||
strbuf_add(sb, "osxkeychain:seen=", strlen("osxkeychain:seen="));
|
||||
write_item_strbuf_cfstring(sb, "host", host);
|
||||
write_item_strbuf_cfnumber(sb, "port", port);
|
||||
write_item_strbuf_cfstring(sb, "path", path);
|
||||
write_item_strbuf_cfstring(sb, "username", username);
|
||||
write_item_strbuf_cfdata(sb, "password", password);
|
||||
}
|
||||
|
||||
static void find_username_in_item(CFDictionaryRef item)
|
||||
{
|
||||
CFStringRef account_ref;
|
||||
@@ -124,6 +166,7 @@ static void find_username_in_item(CFDictionaryRef item)
|
||||
write_item("username", "", 0);
|
||||
return;
|
||||
}
|
||||
username = CFStringCreateCopy(kCFAllocatorDefault, account_ref);
|
||||
|
||||
username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
|
||||
if (username_buf)
|
||||
@@ -163,6 +206,7 @@ static OSStatus find_internet_password(void)
|
||||
}
|
||||
|
||||
data = CFDictionaryGetValue(item, kSecValueData);
|
||||
password = CFDataCreateCopy(kCFAllocatorDefault, data);
|
||||
|
||||
write_item("password",
|
||||
(const char *)CFDataGetBytePtr(data),
|
||||
@@ -173,7 +217,14 @@ static OSStatus find_internet_password(void)
|
||||
CFRelease(item);
|
||||
|
||||
write_item("capability[]", "state", strlen("state"));
|
||||
write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1"));
|
||||
{
|
||||
struct strbuf sb;
|
||||
|
||||
strbuf_init(&sb, 1024);
|
||||
encode_state_seen(&sb);
|
||||
write_item("state[]", sb.buf, strlen(sb.buf));
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
out:
|
||||
CFRelease(attrs);
|
||||
@@ -288,13 +339,22 @@ static OSStatus add_internet_password(void)
|
||||
CFDictionaryRef attrs;
|
||||
OSStatus result;
|
||||
|
||||
if (state_seen)
|
||||
return errSecSuccess;
|
||||
|
||||
/* Only store complete credentials */
|
||||
if (!protocol || !host || !username || !password)
|
||||
return -1;
|
||||
|
||||
if (state_seen) {
|
||||
struct strbuf sb;
|
||||
|
||||
strbuf_init(&sb, 1024);
|
||||
encode_state_seen(&sb);
|
||||
if (!strcmp(state_seen, sb.buf)) {
|
||||
strbuf_release(&sb);
|
||||
return errSecSuccess;
|
||||
}
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password);
|
||||
if (password_expiry_utc) {
|
||||
CFDataAppendBytes(data,
|
||||
@@ -403,8 +463,9 @@ static void read_credential(void)
|
||||
(UInt8 *)v,
|
||||
strlen(v));
|
||||
else if (!strcmp(buf, "state[]")) {
|
||||
if (!strcmp(v, "osxkeychain:seen=1"))
|
||||
state_seen = 1;
|
||||
int len = strlen("osxkeychain:seen=");
|
||||
if (!strncmp(v, "osxkeychain:seen=", len))
|
||||
state_seen = xstrdup(v);
|
||||
}
|
||||
/*
|
||||
* Ignore other lines; we don't know what they mean, but
|
||||
@@ -443,5 +504,8 @@ int main(int argc, const char **argv)
|
||||
|
||||
clear_credential();
|
||||
|
||||
if (state_seen)
|
||||
free(state_seen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
executable('git-credential-osxkeychain',
|
||||
sources: 'git-credential-osxkeychain.c',
|
||||
dependencies: [
|
||||
libgit,
|
||||
dependency('CoreFoundation'),
|
||||
dependency('Security'),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user