Skip to content

Commit 8b2d219

Browse files
kbleeskusma
authored andcommitted
wincred: improve compatibility with windows versions
On WinXP, the windows credential helper doesn't work at all (due to missing Cred[Un]PackAuthenticationBuffer APIs). On Win7, the credential format used by wincred is incompatible with native Windows tools (such as the control panel applet or 'cmdkey.exe /generic'). These Windows tools only set the TargetName, UserName and CredentialBlob members of the CREDENTIAL structure (where CredentialBlob is the UTF-16-encoded password). Remove the unnecessary packing / unpacking of the password, along with the related API definitions, for compatibility with Windows XP. Don't use CREDENTIAL_ATTRIBUTEs to identify credentials for compatibility with Windows credential manager tools. Parse the protocol, username, host and path fields from the credential's target name instead. Credentials created with an old wincred version will have mangled or empty passwords after this change. Signed-off-by: Karsten Blees <[email protected]> Signed-off-by: Erik Faye-Lund <[email protected]>
1 parent 3b12f46 commit 8b2d219

File tree

1 file changed

+70
-129
lines changed

1 file changed

+70
-129
lines changed

contrib/credential/wincred/git-credential-wincred.c

Lines changed: 70 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
/* common helpers */
1111

12+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
13+
1214
static void die(const char *err, ...)
1315
{
1416
char msg[4096];
@@ -30,14 +32,6 @@ static void *xmalloc(size_t size)
3032
return ret;
3133
}
3234

33-
static char *xstrdup(const char *str)
34-
{
35-
char *ret = strdup(str);
36-
if (!ret)
37-
die("Out of memory");
38-
return ret;
39-
}
40-
4135
/* MinGW doesn't have wincred.h, so we need to define stuff */
4236

4337
typedef struct _CREDENTIAL_ATTRIBUTEW {
@@ -67,95 +61,99 @@ typedef struct _CREDENTIALW {
6761
#define CRED_MAX_ATTRIBUTES 64
6862

6963
typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
70-
typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
71-
LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
7264
typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
7365
PCREDENTIALW **);
74-
typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
75-
PBYTE, DWORD *);
7666
typedef VOID (WINAPI *CredFreeT)(PVOID);
7767
typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
7868

79-
static HMODULE advapi, credui;
69+
static HMODULE advapi;
8070
static CredWriteWT CredWriteW;
81-
static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
8271
static CredEnumerateWT CredEnumerateW;
83-
static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
8472
static CredFreeT CredFree;
8573
static CredDeleteWT CredDeleteW;
8674

8775
static void load_cred_funcs(void)
8876
{
8977
/* load DLLs */
9078
advapi = LoadLibrary("advapi32.dll");
91-
credui = LoadLibrary("credui.dll");
92-
if (!advapi || !credui)
93-
die("failed to load DLLs");
79+
if (!advapi)
80+
die("failed to load advapi32.dll");
9481

9582
/* get function pointers */
9683
CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
97-
CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
98-
GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
9984
CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
10085
"CredEnumerateW");
101-
CredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)
102-
GetProcAddress(credui, "CredPackAuthenticationBufferW");
10386
CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
10487
CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
105-
if (!CredWriteW || !CredUnPackAuthenticationBufferW ||
106-
!CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||
107-
!CredDeleteW)
88+
if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
10889
die("failed to load functions");
10990
}
11091

111-
static char target_buf[1024];
112-
static char *protocol, *host, *path, *username;
113-
static WCHAR *wusername, *password, *target;
92+
static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
11493

115-
static void write_item(const char *what, WCHAR *wbuf)
94+
static void write_item(const char *what, LPCWSTR wbuf, int wlen)
11695
{
11796
char *buf;
118-
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
97+
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
11998
FALSE);
12099
buf = xmalloc(len);
121100

122-
if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
101+
if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
123102
die("WideCharToMultiByte failed!");
124103

125104
printf("%s=", what);
126-
fwrite(buf, 1, len - 1, stdout);
105+
fwrite(buf, 1, len, stdout);
127106
putchar('\n');
128107
free(buf);
129108
}
130109

131-
static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
132-
const char *want)
110+
/*
111+
* Match an (optional) expected string and a delimiter in the target string,
112+
* consuming the matched text by updating the target pointer.
113+
*/
114+
static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
133115
{
134-
int i;
135-
if (!want)
136-
return 1;
137-
138-
for (i = 0; i < cred->AttributeCount; ++i)
139-
if (!wcscmp(cred->Attributes[i].Keyword, keyword))
140-
return !strcmp((const char *)cred->Attributes[i].Value,
141-
want);
142-
143-
return 0; /* not found */
116+
LPCWSTR delim_pos, start = *ptarget;
117+
int len;
118+
119+
/* find start of delimiter (or end-of-string if delim is empty) */
120+
if (*delim)
121+
delim_pos = wcsstr(start, delim);
122+
else
123+
delim_pos = start + wcslen(start);
124+
125+
/*
126+
* match text up to delimiter, or end of string (e.g. the '/' after
127+
* host is optional if not followed by a path)
128+
*/
129+
if (delim_pos)
130+
len = delim_pos - start;
131+
else
132+
len = wcslen(start);
133+
134+
/* update ptarget if we either found a delimiter or need a match */
135+
if (delim_pos || want)
136+
*ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
137+
138+
return !want || (!wcsncmp(want, start, len) && !want[len]);
144139
}
145140

146141
static int match_cred(const CREDENTIALW *cred)
147142
{
148-
return (!wusername || !wcscmp(wusername, cred->UserName)) &&
149-
match_attr(cred, L"git_protocol", protocol) &&
150-
match_attr(cred, L"git_host", host) &&
151-
match_attr(cred, L"git_path", path);
143+
LPCWSTR target = cred->TargetName;
144+
if (wusername && wcscmp(wusername, cred->UserName))
145+
return 0;
146+
147+
return match_part(&target, L"git", L":") &&
148+
match_part(&target, protocol, L"://") &&
149+
match_part(&target, wusername, L"@") &&
150+
match_part(&target, host, L"/") &&
151+
match_part(&target, path, L"");
152152
}
153153

154154
static void get_credential(void)
155155
{
156-
WCHAR *user_buf, *pass_buf;
157-
DWORD user_buf_size = 0, pass_buf_size = 0;
158-
CREDENTIALW **creds, *cred = NULL;
156+
CREDENTIALW **creds;
159157
DWORD num_creds;
160158
int i;
161159

@@ -165,90 +163,36 @@ static void get_credential(void)
165163
/* search for the first credential that matches username */
166164
for (i = 0; i < num_creds; ++i)
167165
if (match_cred(creds[i])) {
168-
cred = creds[i];
166+
write_item("username", creds[i]->UserName,
167+
wcslen(creds[i]->UserName));
168+
write_item("password",
169+
(LPCWSTR)creds[i]->CredentialBlob,
170+
creds[i]->CredentialBlobSize / sizeof(WCHAR));
169171
break;
170172
}
171-
if (!cred)
172-
return;
173-
174-
CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
175-
cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
176-
NULL, &pass_buf_size);
177-
178-
user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
179-
pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
180-
181-
if (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
182-
cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,
183-
pass_buf, &pass_buf_size))
184-
die("CredUnPackAuthenticationBuffer failed");
185173

186174
CredFree(creds);
187-
188-
/* zero-terminate (sizes include zero-termination) */
189-
user_buf[user_buf_size - 1] = L'\0';
190-
pass_buf[pass_buf_size - 1] = L'\0';
191-
192-
write_item("username", user_buf);
193-
write_item("password", pass_buf);
194-
195-
free(user_buf);
196-
free(pass_buf);
197-
}
198-
199-
static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
200-
const char *value)
201-
{
202-
attr->Keyword = (LPWSTR)keyword;
203-
attr->Flags = 0;
204-
attr->ValueSize = strlen(value) + 1; /* store zero-termination */
205-
attr->Value = (LPBYTE)value;
206175
}
207176

208177
static void store_credential(void)
209178
{
210179
CREDENTIALW cred;
211-
BYTE *auth_buf;
212-
DWORD auth_buf_size = 0;
213-
CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
214180

215181
if (!wusername || !password)
216182
return;
217183

218-
/* query buffer size */
219-
CredPackAuthenticationBufferW(0, wusername, password,
220-
NULL, &auth_buf_size);
221-
222-
auth_buf = xmalloc(auth_buf_size);
223-
224-
if (!CredPackAuthenticationBufferW(0, wusername, password,
225-
auth_buf, &auth_buf_size))
226-
die("CredPackAuthenticationBuffer failed");
227-
228184
cred.Flags = 0;
229185
cred.Type = CRED_TYPE_GENERIC;
230186
cred.TargetName = target;
231187
cred.Comment = L"saved by git-credential-wincred";
232-
cred.CredentialBlobSize = auth_buf_size;
233-
cred.CredentialBlob = auth_buf;
188+
cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
189+
cred.CredentialBlob = (LPVOID)password;
234190
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
235-
cred.AttributeCount = 1;
236-
cred.Attributes = attrs;
191+
cred.AttributeCount = 0;
192+
cred.Attributes = NULL;
237193
cred.TargetAlias = NULL;
238194
cred.UserName = wusername;
239195

240-
write_attr(attrs, L"git_protocol", protocol);
241-
242-
if (host) {
243-
write_attr(attrs + cred.AttributeCount, L"git_host", host);
244-
cred.AttributeCount++;
245-
}
246-
247-
if (path) {
248-
write_attr(attrs + cred.AttributeCount, L"git_path", path);
249-
cred.AttributeCount++;
250-
}
251-
252196
if (!CredWriteW(&cred, 0))
253197
die("CredWrite failed");
254198
}
@@ -298,13 +242,12 @@ static void read_credential(void)
298242
*v++ = '\0';
299243

300244
if (!strcmp(buf, "protocol"))
301-
protocol = xstrdup(v);
245+
protocol = utf8_to_utf16_dup(v);
302246
else if (!strcmp(buf, "host"))
303-
host = xstrdup(v);
247+
host = utf8_to_utf16_dup(v);
304248
else if (!strcmp(buf, "path"))
305-
path = xstrdup(v);
249+
path = utf8_to_utf16_dup(v);
306250
else if (!strcmp(buf, "username")) {
307-
username = xstrdup(v);
308251
wusername = utf8_to_utf16_dup(v);
309252
} else if (!strcmp(buf, "password"))
310253
password = utf8_to_utf16_dup(v);
@@ -333,22 +276,20 @@ int main(int argc, char *argv[])
333276
return 0;
334277

335278
/* prepare 'target', the unique key for the credential */
336-
strncat(target_buf, "git:", sizeof(target_buf));
337-
strncat(target_buf, protocol, sizeof(target_buf));
338-
strncat(target_buf, "://", sizeof(target_buf));
339-
if (username) {
340-
strncat(target_buf, username, sizeof(target_buf));
341-
strncat(target_buf, "@", sizeof(target_buf));
279+
wcscpy(target, L"git:");
280+
wcsncat(target, protocol, ARRAY_SIZE(target));
281+
wcsncat(target, L"://", ARRAY_SIZE(target));
282+
if (wusername) {
283+
wcsncat(target, wusername, ARRAY_SIZE(target));
284+
wcsncat(target, L"@", ARRAY_SIZE(target));
342285
}
343286
if (host)
344-
strncat(target_buf, host, sizeof(target_buf));
287+
wcsncat(target, host, ARRAY_SIZE(target));
345288
if (path) {
346-
strncat(target_buf, "/", sizeof(target_buf));
347-
strncat(target_buf, path, sizeof(target_buf));
289+
wcsncat(target, L"/", ARRAY_SIZE(target));
290+
wcsncat(target, path, ARRAY_SIZE(target));
348291
}
349292

350-
target = utf8_to_utf16_dup(target_buf);
351-
352293
if (!strcmp(argv[1], "get"))
353294
get_credential();
354295
else if (!strcmp(argv[1], "store"))

0 commit comments

Comments
 (0)