mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
http: add SSPI authentication on Windows
Add support for SSPI on Windows, which offers NTLM and Negotiate authentication.
This commit is contained in:
committed by
Edward Thomson
parent
0d7f3f5291
commit
f15c8ac71a
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
341
src/libgit2/transports/auth_sspi.c
Normal file
341
src/libgit2/transports/auth_sspi.c
Normal file
@@ -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 */
|
||||
Reference in New Issue
Block a user