diff options
-rw-r--r-- | cmake/SelectGSSAPI.cmake | 6 | ||||
-rw-r--r-- | cmake/SelectHTTPSBackend.cmake | 4 | ||||
-rw-r--r-- | src/libgit2/transports/auth_negotiate.h | 2 | ||||
-rw-r--r-- | src/libgit2/transports/auth_ntlm.h | 6 | ||||
-rw-r--r-- | src/libgit2/transports/auth_sspi.c | 341 |
5 files changed, 350 insertions, 9 deletions
diff --git a/cmake/SelectGSSAPI.cmake b/cmake/SelectGSSAPI.cmake index 24e2d68b9..5bde11697 100644 --- a/cmake/SelectGSSAPI.cmake +++ b/cmake/SelectGSSAPI.cmake @@ -29,7 +29,7 @@ if(USE_GSSAPI) list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSFRAMEWORK_LIBRARIES}) set(GIT_GSSFRAMEWORK 1) - add_feature_info(SPNEGO GIT_GSSFRAMEWORK "SPNEGO authentication support (${USE_GSSAPI})") + add_feature_info(GSSAPI GIT_GSSFRAMEWORK "GSSAPI support for SPNEGO authentication (${USE_GSSAPI})") elseif(USE_GSSAPI STREQUAL "gssapi") if(NOT GSSAPI_FOUND) message(FATAL_ERROR "Asked for gssapi GSS backend, but it wasn't found") @@ -38,11 +38,11 @@ if(USE_GSSAPI) list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSAPI_LIBRARIES}) set(GIT_GSSAPI 1) - add_feature_info(SPNEGO GIT_GSSAPI "SPNEGO authentication support (${USE_GSSAPI})") + add_feature_info(GSSAPI GIT_GSSAPI "GSSAPI support for SPNEGO authentication (${USE_GSSAPI})") else() message(FATAL_ERROR "Asked for backend ${USE_GSSAPI} but it wasn't found") endif() else() set(GIT_GSSAPI 0) - add_feature_info(SPNEGO NO "SPNEGO authentication support") + add_feature_info(GSSAPI NO "GSSAPI support for SPNEGO authentication") endif() diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index 64c7a1097..d14941643 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -125,8 +125,8 @@ if(USE_HTTPS) list(APPEND LIBGIT2_PC_LIBS "-lwinhttp") endif() - list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") - list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic") set(GIT_OPENSSL 1) set(GIT_OPENSSL_DYNAMIC 1) diff --git a/src/libgit2/transports/auth_negotiate.h b/src/libgit2/transports/auth_negotiate.h index 34aff295b..4360785c5 100644 --- a/src/libgit2/transports/auth_negotiate.h +++ b/src/libgit2/transports/auth_negotiate.h @@ -12,7 +12,7 @@ #include "git2.h" #include "auth.h" -#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) || defined(GIT_WIN32) extern int git_http_auth_negotiate( git_http_auth_context **out, diff --git a/src/libgit2/transports/auth_ntlm.h b/src/libgit2/transports/auth_ntlm.h index 2bf8f41f6..33406ae94 100644 --- a/src/libgit2/transports/auth_ntlm.h +++ b/src/libgit2/transports/auth_ntlm.h @@ -5,15 +5,15 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_transports_auth_ntlmclient_h__ -#define INCLUDE_transports_auth_ntlmclient_h__ +#ifndef INCLUDE_transports_auth_ntlm_h__ +#define INCLUDE_transports_auth_ntlm_h__ #include "auth.h" /* NTLM requires a full request/challenge/response */ #define GIT_AUTH_STEPS_NTLM 2 -#ifdef GIT_NTLM +#if defined(GIT_NTLM) || defined(GIT_WIN32) #if defined(GIT_OPENSSL) # define CRYPT_OPENSSL diff --git a/src/libgit2/transports/auth_sspi.c b/src/libgit2/transports/auth_sspi.c new file mode 100644 index 000000000..f8269365d --- /dev/null +++ b/src/libgit2/transports/auth_sspi.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "auth_ntlm.h" +#include "auth_negotiate.h" + +#ifdef GIT_WIN32 + +#define SECURITY_WIN32 + +#include "git2.h" +#include "auth.h" +#include "git2/sys/credential.h" + +#include <windows.h> +#include <security.h> + +typedef struct { + git_http_auth_context parent; + wchar_t *target; + + const char *package_name; + size_t package_name_len; + wchar_t *package_name_w; + SecPkgInfoW *package_info; + SEC_WINNT_AUTH_IDENTITY_W identity; + CredHandle cred; + CtxtHandle context; + + int has_identity : 1, + has_credentials : 1, + has_context : 1, + complete : 1; + git_str challenge; +} http_auth_sspi_context; + +static void sspi_reset_context(http_auth_sspi_context *ctx) +{ + if (ctx->has_identity) { + git__free(ctx->identity.User); + git__free(ctx->identity.Domain); + git__free(ctx->identity.Password); + + memset(&ctx->identity, 0, sizeof(SEC_WINNT_AUTH_IDENTITY_W)); + + ctx->has_identity = 0; + } + + if (ctx->has_credentials) { + FreeCredentialsHandle(&ctx->cred); + memset(&ctx->cred, 0, sizeof(CredHandle)); + + ctx->has_credentials = 0; + } + + if (ctx->has_context) { + DeleteSecurityContext(&ctx->context); + memset(&ctx->context, 0, sizeof(CtxtHandle)); + + ctx->has_context = 0; + } + + ctx->complete = 0; + + git_str_dispose(&ctx->challenge); +} + +static int sspi_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + size_t challenge_len = strlen(challenge); + + git_str_clear(&ctx->challenge); + + if (strncmp(challenge, ctx->package_name, ctx->package_name_len) != 0) { + git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name); + return -1; + } + + /* + * A package type indicator without a base64 payload indicates the + * mechanism; it's not an actual challenge. Ignore it. + */ + if (challenge[ctx->package_name_len] == 0) { + return 0; + } else if (challenge[ctx->package_name_len] != ' ') { + git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name); + return -1; + } + + if (git_str_decode_base64(&ctx->challenge, + challenge + (ctx->package_name_len + 1), + challenge_len - (ctx->package_name_len + 1)) < 0) { + git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name); + return -1; + } + + GIT_ASSERT(ctx->challenge.size <= ULONG_MAX); + return 0; +} + +static int create_identity( + SEC_WINNT_AUTH_IDENTITY_W **out, + http_auth_sspi_context *ctx, + git_credential *cred) +{ + git_credential_userpass_plaintext *userpass; + wchar_t *username = NULL, *domain = NULL, *password = NULL; + int username_len = 0, domain_len = 0, password_len = 0; + const char *sep; + + if (cred->credtype == GIT_CREDENTIAL_DEFAULT) { + *out = NULL; + return 0; + } + + if (cred->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + git_error_set(GIT_ERROR_NET, "unknown credential type: %d", cred->credtype); + return -1; + } + + userpass = (git_credential_userpass_plaintext *)cred; + + if ((sep = strchr(userpass->username, '\\')) != NULL) { + GIT_ASSERT(sep - userpass->username < INT_MAX); + + username_len = git_utf8_to_16_alloc(&username, sep + 1); + domain_len = git_utf8_to_16_alloc_with_len(&domain, + userpass->username, (int)(sep - userpass->username)); + } else { + username_len = git_utf8_to_16_alloc(&username, + userpass->username); + } + + password_len = git_utf8_to_16_alloc(&password, userpass->password); + + if (username_len < 0 || domain_len < 0 || password_len < 0) { + git__free(username); + git__free(domain); + git__free(password); + return -1; + } + + ctx->identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + ctx->identity.User = username; + ctx->identity.UserLength = (unsigned long)username_len; + ctx->identity.Password = password; + ctx->identity.PasswordLength = (unsigned long)password_len; + ctx->identity.Domain = domain; + ctx->identity.DomainLength = (unsigned long)domain_len; + + ctx->has_identity = 1; + + *out = &ctx->identity; + + return 0; +} + +static int sspi_next_token( + git_str *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + SEC_WINNT_AUTH_IDENTITY_W *identity = NULL; + TimeStamp timestamp; + DWORD context_flags; + SecBuffer input_buf = { 0, SECBUFFER_TOKEN, NULL }; + SecBuffer output_buf = { 0, SECBUFFER_TOKEN, NULL }; + SecBufferDesc input_buf_desc = { SECBUFFER_VERSION, 1, &input_buf }; + SecBufferDesc output_buf_desc = { SECBUFFER_VERSION, 1, &output_buf }; + SECURITY_STATUS status; + + if (ctx->complete) + sspi_reset_context(ctx); + + if (!ctx->has_context) { + if (create_identity(&identity, ctx, cred) < 0) + return -1; + + status = AcquireCredentialsHandleW(NULL, ctx->package_name_w, + SECPKG_CRED_BOTH, NULL, identity, NULL, + NULL, &ctx->cred, ×tamp); + + if (status != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, "could not acquire credentials"); + return -1; + } + + ctx->has_credentials = 1; + } + + context_flags = ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_MUTUAL_AUTH; + + if (ctx->challenge.size > 0) { + input_buf.BufferType = SECBUFFER_TOKEN; + input_buf.cbBuffer = (unsigned long)ctx->challenge.size; + input_buf.pvBuffer = ctx->challenge.ptr; + } + + status = InitializeSecurityContextW(&ctx->cred, + ctx->has_context ? &ctx->context : NULL, + ctx->target, + context_flags, + 0, + SECURITY_NETWORK_DREP, + ctx->has_context ? &input_buf_desc : NULL, + 0, + ctx->has_context ? NULL : &ctx->context, + &output_buf_desc, + &context_flags, + NULL); + + if (status == SEC_I_COMPLETE_AND_CONTINUE || + status == SEC_I_COMPLETE_NEEDED) + status = CompleteAuthToken(&ctx->context, &output_buf_desc); + + if (status == SEC_E_OK) { + ctx->complete = 1; + } else if (status != SEC_I_CONTINUE_NEEDED) { + git_error_set(GIT_ERROR_OS, "could not initialize security context"); + return -1; + } + + ctx->has_context = 1; + git_str_clear(&ctx->challenge); + + if (output_buf.cbBuffer > 0) { + git_str_put(buf, ctx->package_name, ctx->package_name_len); + git_str_putc(buf, ' '); + git_str_encode_base64(buf, output_buf.pvBuffer, output_buf.cbBuffer); + + FreeContextBuffer(output_buf.pvBuffer); + + if (git_str_oom(buf)) + return -1; + } + + return 0; +} + +static int sspi_is_complete(git_http_auth_context *c) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + + return ctx->complete; +} + +static void sspi_context_free(git_http_auth_context *c) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + + sspi_reset_context(ctx); + + FreeContextBuffer(ctx->package_info); + git__free(ctx->target); + git__free(ctx); +} + +static int sspi_init_context( + git_http_auth_context **out, + git_http_auth_t type, + const git_net_url *url) +{ + http_auth_sspi_context *ctx; + git_str target = GIT_STR_INIT; + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_sspi_context)); + GIT_ERROR_CHECK_ALLOC(ctx); + + switch (type) { + case GIT_HTTP_AUTH_NTLM: + ctx->package_name = "NTLM"; + ctx->package_name_len = CONST_STRLEN("NTLM"); + ctx->package_name_w = L"NTLM"; + ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT | + GIT_CREDENTIAL_DEFAULT; + break; + case GIT_HTTP_AUTH_NEGOTIATE: + ctx->package_name = "Negotiate"; + ctx->package_name_len = CONST_STRLEN("Negotiate"); + ctx->package_name_w = L"Negotiate"; + ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; + break; + default: + git_error_set(GIT_ERROR_NET, "unknown SSPI auth type: %d", ctx->parent.type); + git__free(ctx); + return -1; + } + + if (QuerySecurityPackageInfoW(ctx->package_name_w, &ctx->package_info) != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, "could not query security package"); + git__free(ctx); + return -1; + } + + if (git_str_printf(&target, "http/%s", url->host) < 0 || + git_utf8_to_16_alloc(&ctx->target, target.ptr) < 0) { + FreeContextBuffer(ctx->package_info); + git__free(ctx); + return -1; + } + + ctx->parent.type = type; + ctx->parent.connection_affinity = 1; + ctx->parent.set_challenge = sspi_set_challenge; + ctx->parent.next_token = sspi_next_token; + ctx->parent.is_complete = sspi_is_complete; + ctx->parent.free = sspi_context_free; + + *out = (git_http_auth_context *)ctx; + + git_str_dispose(&target); + return 0; +} + +int git_http_auth_negotiate( + git_http_auth_context **out, + const git_net_url *url) +{ + return sspi_init_context(out, GIT_HTTP_AUTH_NEGOTIATE, url); +} + +int git_http_auth_ntlm( + git_http_auth_context **out, + const git_net_url *url) +{ + return sspi_init_context(out, GIT_HTTP_AUTH_NTLM, url); +} + +#endif /* GIT_WIN32 */ |