Commit f15c8ac7 by Edward Thomson Committed by Edward Thomson

http: add SSPI authentication on Windows

Add support for SSPI on Windows, which offers NTLM and Negotiate
authentication.
parent 0d7f3f52
...@@ -29,7 +29,7 @@ if(USE_GSSAPI) ...@@ -29,7 +29,7 @@ if(USE_GSSAPI)
list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSFRAMEWORK_LIBRARIES}) list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSFRAMEWORK_LIBRARIES})
set(GIT_GSSFRAMEWORK 1) 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") elseif(USE_GSSAPI STREQUAL "gssapi")
if(NOT GSSAPI_FOUND) if(NOT GSSAPI_FOUND)
message(FATAL_ERROR "Asked for gssapi GSS backend, but it wasn't found") message(FATAL_ERROR "Asked for gssapi GSS backend, but it wasn't found")
...@@ -38,11 +38,11 @@ if(USE_GSSAPI) ...@@ -38,11 +38,11 @@ if(USE_GSSAPI)
list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSAPI_LIBRARIES}) list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSAPI_LIBRARIES})
set(GIT_GSSAPI 1) 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() else()
message(FATAL_ERROR "Asked for backend ${USE_GSSAPI} but it wasn't found") message(FATAL_ERROR "Asked for backend ${USE_GSSAPI} but it wasn't found")
endif() endif()
else() else()
set(GIT_GSSAPI 0) set(GIT_GSSAPI 0)
add_feature_info(SPNEGO NO "SPNEGO authentication support") add_feature_info(GSSAPI NO "GSSAPI support for SPNEGO authentication")
endif() endif()
...@@ -125,8 +125,8 @@ if(USE_HTTPS) ...@@ -125,8 +125,8 @@ if(USE_HTTPS)
list(APPEND LIBGIT2_PC_LIBS "-lwinhttp") list(APPEND LIBGIT2_PC_LIBS "-lwinhttp")
endif() endif()
list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32")
list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32")
elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic") elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic")
set(GIT_OPENSSL 1) set(GIT_OPENSSL 1)
set(GIT_OPENSSL_DYNAMIC 1) set(GIT_OPENSSL_DYNAMIC 1)
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include "git2.h" #include "git2.h"
#include "auth.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( extern int git_http_auth_negotiate(
git_http_auth_context **out, git_http_auth_context **out,
......
...@@ -5,15 +5,15 @@ ...@@ -5,15 +5,15 @@
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#ifndef INCLUDE_transports_auth_ntlmclient_h__ #ifndef INCLUDE_transports_auth_ntlm_h__
#define INCLUDE_transports_auth_ntlmclient_h__ #define INCLUDE_transports_auth_ntlm_h__
#include "auth.h" #include "auth.h"
/* NTLM requires a full request/challenge/response */ /* NTLM requires a full request/challenge/response */
#define GIT_AUTH_STEPS_NTLM 2 #define GIT_AUTH_STEPS_NTLM 2
#ifdef GIT_NTLM #if defined(GIT_NTLM) || defined(GIT_WIN32)
#if defined(GIT_OPENSSL) #if defined(GIT_OPENSSL)
# define CRYPT_OPENSSL # define CRYPT_OPENSSL
......
/*
* 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, &timestamp);
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 */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment