From 84efffc33ab352d68cf981da0d6c8d358244cabc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 13 Nov 2013 16:57:51 -0500 Subject: [PATCH] Introduce git_cred_default for NTLM/SPNEGO auth --- include/git2/transport.h | 20 +++++++++++++++++--- src/transports/cred.c | 27 +++++++++++++++++++++++++++ src/transports/winhttp.c | 28 ++++++++++++++++++++++++++++ tests/online/push.c | 16 +++++++++++++++- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 81ebf4dc9..fe4883f99 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -31,13 +31,16 @@ GIT_BEGIN_DECL /** Authentication type requested */ typedef enum { /* git_cred_userpass_plaintext */ - GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0), + GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0), /* git_cred_ssh_key */ GIT_CREDTYPE_SSH_KEY = (1u << 1), /* git_cred_ssh_custom */ - GIT_CREDTYPE_SSH_CUSTOM = (1u << 2), + GIT_CREDTYPE_SSH_CUSTOM = (1u << 2), + + /* git_cred_default */ + GIT_CREDTYPE_DEFAULT = (1u << 3), } git_credtype_t; /* The base structure for all credential types */ @@ -48,7 +51,7 @@ struct git_cred { void (*free)(git_cred *cred); }; -/* A plaintext username and password */ +/** A plaintext username and password */ typedef struct { git_cred parent; char *username; @@ -84,6 +87,9 @@ typedef struct git_cred_ssh_custom { void *sign_data; } git_cred_ssh_custom; +/** A key for NTLM/Kerberos "default" credentials */ +typedef struct git_cred git_cred_default; + /** * Check whether a credential object contains username information. * @@ -150,6 +156,14 @@ GIT_EXTERN(int) git_cred_ssh_custom_new( git_cred_sign_callback sign_fn, void *sign_data); +/** + * Create a "default" credential usable for Negotiate mechanisms like NTLM + * or Kerberos authentication. + * + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_cred_default_new(git_cred **out); + /** * Signature of a function which acquires a credential object. * diff --git a/src/transports/cred.c b/src/transports/cred.c index cc7fdab4b..05d2c8dc6 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -29,6 +29,10 @@ int git_cred_has_username(git_cred *cred) ret = !!c->username; break; } + case GIT_CREDTYPE_DEFAULT: { + ret = 0; + break; + } } return ret; @@ -115,6 +119,13 @@ static void ssh_custom_free(struct git_cred *cred) git__free(c); } +static void default_free(struct git_cred *cred) +{ + git_cred_default *c = (git_cred_default *)cred; + + git__free(c); +} + int git_cred_ssh_key_new( git_cred **cred, const char *username, @@ -191,3 +202,19 @@ int git_cred_ssh_custom_new( *cred = &c->parent; return 0; } + +int git_cred_default_new(git_cred **cred) +{ + git_cred_default *c; + + assert(cred); + + c = git__calloc(1, sizeof(git_cred_default)); + GITERR_CHECK_ALLOC(c); + + c->credtype = GIT_CREDTYPE_DEFAULT; + c->free = default_free; + + *cred = c; + return 0; +} diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index f7566458e..673cd0faf 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -52,6 +52,7 @@ static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | typedef enum { GIT_WINHTTP_AUTH_BASIC = 1, + GIT_WINHTTP_AUTH_NEGOTIATE = 2, } winhttp_authmechanism_t; typedef struct { @@ -138,6 +139,22 @@ on_error: return error; } +static int apply_default_credentials(HINTERNET request) +{ + /* If we are explicitly asked to deliver default credentials, turn set + * the security level to low which will guarantee they are delivered. + * The default is "medium" which applies to the intranet and sounds + * like it would correspond to Internet Explorer security zones, but + * in fact does not. + */ + DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; + + if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD))) + return -1; + + return 0; +} + static int winhttp_stream_connect(winhttp_stream *s) { winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); @@ -317,6 +334,11 @@ static int winhttp_stream_connect(winhttp_stream *s) t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC && apply_basic_credential(s->request, t->cred) < 0) goto on_error; + else if (t->cred && + t->cred->credtype == GIT_CREDTYPE_DEFAULT && + t->auth_mechanism == GIT_WINHTTP_AUTH_NEGOTIATE && + apply_default_credentials(s->request) < 0) + goto on_error; /* If no other credentials have been applied and the URL has username and * password, use those */ @@ -361,6 +383,12 @@ static int parse_unauthorized_response( *auth_mechanism = GIT_WINHTTP_AUTH_BASIC; } + if ((WINHTTP_AUTH_SCHEME_NTLM & supported) || + (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported)) { + *allowed_types |= GIT_CREDTYPE_DEFAULT; + *auth_mechanism = GIT_WINHTTP_AUTH_NEGOTIATE; + } + return 0; } diff --git a/tests/online/push.c b/tests/online/push.c index aeb1ab47d..be505c3a1 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -9,14 +9,17 @@ static git_repository *_repo; +static char *_remote_url; + static char *_remote_ssh_key; static char *_remote_ssh_pubkey; static char *_remote_ssh_passphrase; -static char *_remote_url; static char *_remote_user; static char *_remote_pass; +static char *_remote_default; + static int cred_acquire_cb(git_cred **, const char *, const char *, unsigned int, void *); static git_remote *_remote; @@ -47,11 +50,21 @@ static int cred_acquire_cb( GIT_UNUSED(user_from_url); GIT_UNUSED(payload); + if (GIT_CREDTYPE_DEFAULT & allowed_types) { + if (!_remote_default) { + printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n"); + return -1; + } + + return git_cred_default_new(cred); + } + if (GIT_CREDTYPE_SSH_KEY & allowed_types) { if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) { printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n"); return -1; } + return git_cred_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); } @@ -298,6 +311,7 @@ void test_online_push__initialize(void) _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY"); _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); + _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); _remote = NULL; if (_remote_url) {