mirror of
https://github.com/libgit2/libgit2.git
synced 2026-06-22 06:26:26 +00:00
Merge pull request #6533 from libgit2/ethomson/schannel-2
Introduce Schannel and SSPI for Windows
This commit is contained in:
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@@ -130,19 +130,19 @@ jobs:
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
setup-script: osx
|
||||
- name: "Windows (amd64, Visual Studio)"
|
||||
- name: "Windows (amd64, Visual Studio, Schannel)"
|
||||
id: windows-amd64-vs
|
||||
os: windows-2019
|
||||
setup-script: win32
|
||||
env:
|
||||
ARCH: amd64
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2
|
||||
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2
|
||||
BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin
|
||||
BUILD_TEMP: D:\Temp
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (x86, Visual Studio)"
|
||||
- name: "Windows (x86, Visual Studio, WinHTTP)"
|
||||
id: windows-x86-vs
|
||||
os: windows-2019
|
||||
setup-script: win32
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
BUILD_TEMP: D:\Temp
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (amd64, mingw)"
|
||||
- name: "Windows (amd64, mingw, WinHTTP)"
|
||||
id: windows-amd64-mingw
|
||||
os: windows-2019
|
||||
setup-script: mingw
|
||||
@@ -166,14 +166,14 @@ jobs:
|
||||
BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (x86, mingw)"
|
||||
- name: "Windows (x86, mingw, Schannel)"
|
||||
id: windows-x86-mingw
|
||||
os: windows-2019
|
||||
setup-script: mingw
|
||||
env:
|
||||
ARCH: x86
|
||||
CMAKE_GENERATOR: MinGW Makefiles
|
||||
CMAKE_OPTIONS: -DDEPRECATE_HARD=ON
|
||||
CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel
|
||||
BUILD_TEMP: D:\Temp
|
||||
BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
|
||||
SKIP_SSH_TESTS: true
|
||||
|
||||
48
.github/workflows/nightly.yml
vendored
48
.github/workflows/nightly.yml
vendored
@@ -162,32 +162,39 @@ jobs:
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
setup-script: osx
|
||||
- name: "Windows (amd64, Visual Studio)"
|
||||
- name: "Windows (amd64, Visual Studio, WinHTTP)"
|
||||
os: windows-2019
|
||||
env:
|
||||
ARCH: amd64
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON
|
||||
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (no mmap)"
|
||||
os: windows-2019
|
||||
env:
|
||||
ARCH: amd64
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CFLAGS: -DNO_MMAP
|
||||
CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (x86, Visual Studio)"
|
||||
- name: "Windows (x86, Visual Studio, WinHTTP)"
|
||||
os: windows-2019
|
||||
env:
|
||||
ARCH: x86
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON
|
||||
CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (amd64, mingw)"
|
||||
- name: "Windows (amd64, Visual Studio, Schannel)"
|
||||
os: windows-2019
|
||||
env:
|
||||
ARCH: amd64
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (x86, Visual Studio, Schannel)"
|
||||
os: windows-2019
|
||||
env:
|
||||
ARCH: x86
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_BUNDLED_ZLIB=ON
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (amd64, mingw, WinHTTP)"
|
||||
os: windows-2019
|
||||
setup-script: mingw
|
||||
env:
|
||||
@@ -198,17 +205,26 @@ jobs:
|
||||
BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (x86, mingw)"
|
||||
- name: "Windows (x86, mingw, Schannel)"
|
||||
os: windows-2019
|
||||
setup-script: mingw
|
||||
env:
|
||||
ARCH: x86
|
||||
CMAKE_GENERATOR: MinGW Makefiles
|
||||
CMAKE_OPTIONS: -DDEPRECATE_HARD=ON
|
||||
CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel
|
||||
BUILD_TEMP: D:\Temp
|
||||
BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Windows (no mmap)"
|
||||
os: windows-2019
|
||||
env:
|
||||
ARCH: amd64
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CFLAGS: -DNO_MMAP
|
||||
CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON
|
||||
SKIP_SSH_TESTS: true
|
||||
SKIP_NEGOTIATE_TESTS: true
|
||||
- name: "Linux (Bionic, GCC, dynamically-loaded OpenSSL)"
|
||||
container:
|
||||
name: bionic
|
||||
|
||||
@@ -82,12 +82,6 @@ if(MSVC)
|
||||
option(WIN32_LEAKCHECK "Enable leak reporting via crtdbg" OFF)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# By default, libgit2 is built with WinHTTP. To use the built-in
|
||||
# HTTP transport, invoke CMake with the "-DUSE_WINHTTP=OFF" argument.
|
||||
option(USE_WINHTTP "Use Win32 WinHTTP routines" ON)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||
endif()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -19,7 +19,7 @@ if(USE_HTTPS)
|
||||
message(STATUS "Security framework is too old, falling back to OpenSSL")
|
||||
set(USE_HTTPS "OpenSSL")
|
||||
endif()
|
||||
elseif(USE_WINHTTP)
|
||||
elseif(WIN32)
|
||||
set(USE_HTTPS "WinHTTP")
|
||||
elseif(OPENSSL_FOUND)
|
||||
set(USE_HTTPS "OpenSSL")
|
||||
@@ -106,8 +106,27 @@ if(USE_HTTPS)
|
||||
# https://github.com/ARMmbed/mbedtls/issues/228
|
||||
# For now, pass its link flags as our own
|
||||
list(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES})
|
||||
elseif(USE_HTTPS STREQUAL "Schannel")
|
||||
set(GIT_SCHANNEL 1)
|
||||
|
||||
list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32")
|
||||
list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32")
|
||||
elseif(USE_HTTPS STREQUAL "WinHTTP")
|
||||
# WinHTTP setup was handled in the WinHTTP-specific block above
|
||||
set(GIT_WINHTTP 1)
|
||||
|
||||
# Since MinGW does not come with headers or an import library for winhttp,
|
||||
# we have to include a private header and generate our own import library
|
||||
if(MINGW)
|
||||
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/winhttp" "${PROJECT_BINARY_DIR}/deps/winhttp")
|
||||
list(APPEND LIBGIT2_SYSTEM_LIBS winhttp)
|
||||
list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/winhttp")
|
||||
else()
|
||||
list(APPEND LIBGIT2_SYSTEM_LIBS "winhttp")
|
||||
list(APPEND LIBGIT2_PC_LIBS "-lwinhttp")
|
||||
endif()
|
||||
|
||||
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)
|
||||
|
||||
@@ -13,6 +13,8 @@ if(USE_SHA1 STREQUAL ON)
|
||||
elseif(USE_SHA1 STREQUAL "HTTPS")
|
||||
if(USE_HTTPS STREQUAL "SecureTransport")
|
||||
set(USE_SHA1 "CommonCrypto")
|
||||
elseif(USE_HTTPS STREQUAL "Schannel")
|
||||
set(USE_SHA1 "Win32")
|
||||
elseif(USE_HTTPS STREQUAL "WinHTTP")
|
||||
set(USE_SHA1 "Win32")
|
||||
elseif(USE_HTTPS)
|
||||
@@ -51,6 +53,8 @@ endif()
|
||||
if(USE_SHA256 STREQUAL "HTTPS")
|
||||
if(USE_HTTPS STREQUAL "SecureTransport")
|
||||
set(USE_SHA256 "CommonCrypto")
|
||||
elseif(USE_HTTPS STREQUAL "Schannel")
|
||||
set(USE_SHA256 "Win32")
|
||||
elseif(USE_HTTPS STREQUAL "WinHTTP")
|
||||
set(USE_SHA256 "Win32")
|
||||
elseif(USE_HTTPS)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
if(WIN32 AND USE_WINHTTP)
|
||||
set(GIT_WINHTTP 1)
|
||||
|
||||
# Since MinGW does not come with headers or an import library for winhttp,
|
||||
# we have to include a private header and generate our own import library
|
||||
if(MINGW)
|
||||
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/winhttp" "${PROJECT_BINARY_DIR}/deps/winhttp")
|
||||
list(APPEND LIBGIT2_SYSTEM_LIBS winhttp)
|
||||
list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/winhttp")
|
||||
else()
|
||||
list(APPEND LIBGIT2_SYSTEM_LIBS "winhttp")
|
||||
list(APPEND LIBGIT2_PC_LIBS "-lwinhttp")
|
||||
endif()
|
||||
|
||||
list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32")
|
||||
list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32")
|
||||
endif()
|
||||
@@ -42,7 +42,6 @@ include(SelectHashes)
|
||||
include(SelectHTTPParser)
|
||||
include(SelectRegex)
|
||||
include(SelectSSH)
|
||||
include(SelectWinHTTP)
|
||||
include(SelectZlib)
|
||||
|
||||
#
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "streams/registry.h"
|
||||
#include "streams/mbedtls.h"
|
||||
#include "streams/openssl.h"
|
||||
#include "streams/socket.h"
|
||||
#include "transports/smart.h"
|
||||
#include "transports/http.h"
|
||||
#include "transports/ssh.h"
|
||||
@@ -78,6 +79,7 @@ int git_libgit2_init(void)
|
||||
git_merge_driver_global_init,
|
||||
git_transport_ssh_global_init,
|
||||
git_stream_registry_global_init,
|
||||
git_socket_stream_global_init,
|
||||
git_openssl_stream_global_init,
|
||||
git_mbedtls_stream_global_init,
|
||||
git_mwindow_global_init,
|
||||
|
||||
715
src/libgit2/streams/schannel.c
Normal file
715
src/libgit2/streams/schannel.c
Normal file
@@ -0,0 +1,715 @@
|
||||
/*
|
||||
* 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 "streams/schannel.h"
|
||||
|
||||
#ifdef GIT_SCHANNEL
|
||||
|
||||
#define SECURITY_WIN32
|
||||
|
||||
#include <security.h>
|
||||
#include <schannel.h>
|
||||
#include <sspi.h>
|
||||
|
||||
#include "stream.h"
|
||||
#include "streams/socket.h"
|
||||
|
||||
#ifndef SP_PROT_TLS1_2_CLIENT
|
||||
# define SP_PROT_TLS1_2_CLIENT 2048
|
||||
#endif
|
||||
|
||||
#ifndef SP_PROT_TLS1_3_CLIENT
|
||||
# define SP_PROT_TLS1_3_CLIENT 8192
|
||||
#endif
|
||||
|
||||
#ifndef SECBUFFER_ALERT
|
||||
# define SECBUFFER_ALERT 17
|
||||
#endif
|
||||
|
||||
#define READ_BLOCKSIZE (16 * 1024)
|
||||
|
||||
typedef enum {
|
||||
STATE_NONE = 0,
|
||||
STATE_CRED = 1,
|
||||
STATE_CONTEXT = 2,
|
||||
STATE_CERTIFICATE = 3
|
||||
} schannel_state;
|
||||
|
||||
typedef struct {
|
||||
git_stream parent;
|
||||
git_stream *io;
|
||||
int owned;
|
||||
bool connected;
|
||||
wchar_t *host_w;
|
||||
|
||||
schannel_state state;
|
||||
|
||||
CredHandle cred;
|
||||
CtxtHandle context;
|
||||
SecPkgContext_StreamSizes stream_sizes;
|
||||
|
||||
CERT_CONTEXT *certificate;
|
||||
const CERT_CHAIN_CONTEXT *cert_chain;
|
||||
git_cert_x509 x509;
|
||||
|
||||
git_str plaintext_in;
|
||||
git_str ciphertext_in;
|
||||
} schannel_stream;
|
||||
|
||||
static int connect_context(schannel_stream *st)
|
||||
{
|
||||
SCHANNEL_CRED cred = { 0 };
|
||||
SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
|
||||
DWORD context_flags;
|
||||
static size_t MAX_RETRIES = 1024;
|
||||
size_t retries;
|
||||
ssize_t read_len;
|
||||
int error = 0;
|
||||
|
||||
if (st->owned && (error = git_stream_connect(st->io)) < 0)
|
||||
return error;
|
||||
|
||||
cred.dwVersion = SCHANNEL_CRED_VERSION;
|
||||
cred.dwFlags = SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
|
||||
SCH_CRED_IGNORE_REVOCATION_OFFLINE |
|
||||
SCH_CRED_MANUAL_CRED_VALIDATION |
|
||||
SCH_CRED_NO_DEFAULT_CREDS |
|
||||
SCH_CRED_NO_SERVERNAME_CHECK;
|
||||
cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT |
|
||||
SP_PROT_TLS1_3_CLIENT;
|
||||
|
||||
if (AcquireCredentialsHandleW(NULL, SCHANNEL_NAME_W,
|
||||
SECPKG_CRED_OUTBOUND, NULL, &cred, NULL,
|
||||
NULL, &st->cred, NULL) != SEC_E_OK) {
|
||||
git_error_set(GIT_ERROR_OS, "could not acquire credentials handle");
|
||||
return -1;
|
||||
}
|
||||
|
||||
st->state = STATE_CRED;
|
||||
|
||||
context_flags = ISC_REQ_ALLOCATE_MEMORY |
|
||||
ISC_REQ_CONFIDENTIALITY |
|
||||
ISC_REQ_REPLAY_DETECT |
|
||||
ISC_REQ_SEQUENCE_DETECT |
|
||||
ISC_REQ_STREAM;
|
||||
|
||||
for (retries = 0; retries < MAX_RETRIES; retries++) {
|
||||
SecBuffer input_buf[] = {
|
||||
{ (unsigned long)st->ciphertext_in.size,
|
||||
SECBUFFER_TOKEN,
|
||||
st->ciphertext_in.size ? st->ciphertext_in.ptr : NULL },
|
||||
{ 0, SECBUFFER_EMPTY, NULL }
|
||||
};
|
||||
SecBuffer output_buf[] = { { 0, SECBUFFER_TOKEN, NULL },
|
||||
{ 0, SECBUFFER_ALERT, NULL } };
|
||||
|
||||
SecBufferDesc input_buf_desc = { SECBUFFER_VERSION, 2, input_buf };
|
||||
SecBufferDesc output_buf_desc = { SECBUFFER_VERSION, 2, output_buf };
|
||||
|
||||
status = InitializeSecurityContextW(&st->cred,
|
||||
retries ? &st->context : NULL, st->host_w,
|
||||
context_flags, 0, 0, retries ? &input_buf_desc : NULL, 0,
|
||||
retries ? NULL : &st->context, &output_buf_desc,
|
||||
&context_flags, NULL);
|
||||
|
||||
if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) {
|
||||
st->state = STATE_CONTEXT;
|
||||
|
||||
if (output_buf[0].cbBuffer > 0) {
|
||||
error = git_stream__write_full(st->io,
|
||||
output_buf[0].pvBuffer,
|
||||
output_buf[0].cbBuffer, 0);
|
||||
|
||||
FreeContextBuffer(output_buf[0].pvBuffer);
|
||||
}
|
||||
|
||||
/* handle any leftover, unprocessed data */
|
||||
if (input_buf[1].BufferType == SECBUFFER_EXTRA) {
|
||||
GIT_ASSERT(st->ciphertext_in.size > input_buf[1].cbBuffer);
|
||||
|
||||
git_str_consume_bytes(&st->ciphertext_in,
|
||||
st->ciphertext_in.size - input_buf[1].cbBuffer);
|
||||
} else {
|
||||
git_str_clear(&st->ciphertext_in);
|
||||
}
|
||||
|
||||
if (error < 0 || status == SEC_E_OK)
|
||||
break;
|
||||
} else if (status == SEC_E_INCOMPLETE_MESSAGE) {
|
||||
/* we need additional data from the client; */
|
||||
if (git_str_grow_by(&st->ciphertext_in, READ_BLOCKSIZE) < 0) {
|
||||
error = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((read_len = git_stream_read(st->io,
|
||||
st->ciphertext_in.ptr + st->ciphertext_in.size,
|
||||
(st->ciphertext_in.asize - st->ciphertext_in.size))) < 0) {
|
||||
error = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
GIT_ASSERT((size_t)read_len <=
|
||||
st->ciphertext_in.asize - st->ciphertext_in.size);
|
||||
st->ciphertext_in.size += read_len;
|
||||
} else {
|
||||
git_error_set(GIT_ERROR_OS,
|
||||
"could not initialize security context");
|
||||
error = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
GIT_ASSERT(st->ciphertext_in.size < ULONG_MAX);
|
||||
}
|
||||
|
||||
if (retries == MAX_RETRIES) {
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"could not initialize security context: too many retries");
|
||||
error = -1;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
if (QueryContextAttributesW(&st->context,
|
||||
SECPKG_ATTR_STREAM_SIZES,
|
||||
&st->stream_sizes) != SEC_E_OK) {
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"could not query stream sizes");
|
||||
error = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int set_certificate_lookup_error(DWORD status)
|
||||
{
|
||||
switch (status) {
|
||||
case CERT_TRUST_IS_NOT_TIME_VALID:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate is expired or not yet valid");
|
||||
break;
|
||||
case CERT_TRUST_IS_REVOKED:
|
||||
git_error_set(GIT_ERROR_SSL, "certificate is revoked");
|
||||
break;
|
||||
case CERT_TRUST_IS_NOT_SIGNATURE_VALID:
|
||||
case CERT_TRUST_IS_NOT_VALID_FOR_USAGE:
|
||||
case CERT_TRUST_INVALID_EXTENSION:
|
||||
case CERT_TRUST_INVALID_POLICY_CONSTRAINTS:
|
||||
case CERT_TRUST_INVALID_BASIC_CONSTRAINTS:
|
||||
case CERT_TRUST_INVALID_NAME_CONSTRAINTS:
|
||||
case CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT:
|
||||
case CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT:
|
||||
case CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT:
|
||||
case CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT:
|
||||
case CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY:
|
||||
case CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT:
|
||||
git_error_set(GIT_ERROR_SSL, "certificate is not valid");
|
||||
break;
|
||||
case CERT_TRUST_IS_UNTRUSTED_ROOT:
|
||||
case CERT_TRUST_IS_CYCLIC:
|
||||
case CERT_TRUST_IS_EXPLICIT_DISTRUST:
|
||||
git_error_set(GIT_ERROR_SSL, "certificate is not trusted");
|
||||
break;
|
||||
case CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate revocation status could not be verified");
|
||||
break;
|
||||
case CERT_TRUST_IS_OFFLINE_REVOCATION:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate revocation is offline or stale");
|
||||
break;
|
||||
case CERT_TRUST_HAS_WEAK_SIGNATURE:
|
||||
git_error_set(GIT_ERROR_SSL, "certificate has a weak signature");
|
||||
break;
|
||||
default:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"unknown certificate lookup failure: %d", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return GIT_ECERTIFICATE;
|
||||
}
|
||||
|
||||
static int set_certificate_validation_error(DWORD status)
|
||||
{
|
||||
switch (status) {
|
||||
case TRUST_E_CERT_SIGNATURE:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"the certificate cannot be verified");
|
||||
break;
|
||||
case CRYPT_E_REVOKED:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"the certificate or signature has been revoked");
|
||||
break;
|
||||
case CERT_E_UNTRUSTEDROOT:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"the certificate root is not trusted");
|
||||
break;
|
||||
case CERT_E_UNTRUSTEDTESTROOT:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"the certificate root is a test certificate");
|
||||
break;
|
||||
case CERT_E_CHAINING:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"the certificate chain is invalid");
|
||||
break;
|
||||
case CERT_E_WRONG_USAGE:
|
||||
case CERT_E_PURPOSE:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"the certificate is not valid for this usage");
|
||||
break;
|
||||
case CERT_E_EXPIRED:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate is expired or not yet valid");
|
||||
break;
|
||||
case CERT_E_INVALID_NAME:
|
||||
case CERT_E_CN_NO_MATCH:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate is not valid for this hostname");
|
||||
break;
|
||||
case CERT_E_INVALID_POLICY:
|
||||
case TRUST_E_BASIC_CONSTRAINTS:
|
||||
case CERT_E_CRITICAL:
|
||||
case CERT_E_VALIDITYPERIODNESTING:
|
||||
git_error_set(GIT_ERROR_SSL, "certificate is not valid");
|
||||
break;
|
||||
case CRYPT_E_NO_REVOCATION_CHECK:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate revocation status could not be verified");
|
||||
break;
|
||||
case CRYPT_E_REVOCATION_OFFLINE:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"certificate revocation is offline or stale");
|
||||
break;
|
||||
case CERT_E_ROLE:
|
||||
git_error_set(GIT_ERROR_SSL, "certificate authority is not valid");
|
||||
break;
|
||||
default:
|
||||
git_error_set(GIT_ERROR_SSL,
|
||||
"unknown certificate policy checking failure: %d",
|
||||
status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return GIT_ECERTIFICATE;
|
||||
}
|
||||
|
||||
static int check_certificate(schannel_stream* st)
|
||||
{
|
||||
CERT_CHAIN_PARA cert_chain_parameters;
|
||||
SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_policy_parameters;
|
||||
CERT_CHAIN_POLICY_PARA cert_policy_parameters =
|
||||
{ sizeof(CERT_CHAIN_POLICY_PARA), 0, &ssl_policy_parameters };
|
||||
CERT_CHAIN_POLICY_STATUS cert_policy_status;
|
||||
|
||||
memset(&cert_chain_parameters, 0, sizeof(CERT_CHAIN_PARA));
|
||||
cert_chain_parameters.cbSize = sizeof(CERT_CHAIN_PARA);
|
||||
|
||||
if (QueryContextAttributesW(&st->context,
|
||||
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
|
||||
&st->certificate) != SEC_E_OK) {
|
||||
git_error_set(GIT_ERROR_OS,
|
||||
"could not query remote certificate context");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* TODO: do we really want to do revokcation checking ? */
|
||||
if (!CertGetCertificateChain(NULL, st->certificate, NULL,
|
||||
st->certificate->hCertStore, &cert_chain_parameters,
|
||||
CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
|
||||
NULL, &st->cert_chain)) {
|
||||
git_error_set(GIT_ERROR_OS, "could not query remote certificate chain");
|
||||
CertFreeCertificateContext(st->certificate);
|
||||
return -1;
|
||||
}
|
||||
|
||||
st->state = STATE_CERTIFICATE;
|
||||
|
||||
/* Set up the x509 certificate data for future callbacks */
|
||||
|
||||
st->x509.parent.cert_type = GIT_CERT_X509;
|
||||
st->x509.data = st->certificate->pbCertEncoded;
|
||||
st->x509.len = st->certificate->cbCertEncoded;
|
||||
|
||||
/* Handle initial certificate validation */
|
||||
|
||||
if (st->cert_chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR)
|
||||
return set_certificate_lookup_error(st->cert_chain->TrustStatus.dwErrorStatus);
|
||||
|
||||
ssl_policy_parameters.cbSize = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
|
||||
ssl_policy_parameters.dwAuthType = AUTHTYPE_SERVER;
|
||||
ssl_policy_parameters.pwszServerName = st->host_w;
|
||||
|
||||
if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL,
|
||||
st->cert_chain, &cert_policy_parameters,
|
||||
&cert_policy_status)) {
|
||||
git_error_set(GIT_ERROR_OS, "could not verify certificate chain policy");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cert_policy_status.dwError != SEC_E_OK)
|
||||
return set_certificate_validation_error(cert_policy_status.dwError);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int schannel_connect(git_stream *stream)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
int error;
|
||||
|
||||
GIT_ASSERT(st->state == STATE_NONE);
|
||||
|
||||
if ((error = connect_context(st)) < 0 ||
|
||||
(error = check_certificate(st)) < 0)
|
||||
return error;
|
||||
|
||||
st->connected = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int schannel_certificate(git_cert **out, git_stream *stream)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
|
||||
*out = &st->x509.parent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int schannel_set_proxy(
|
||||
git_stream *stream,
|
||||
const git_proxy_options *proxy_options)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
return git_stream_set_proxy(st->io, proxy_options);
|
||||
}
|
||||
|
||||
static ssize_t schannel_write(
|
||||
git_stream *stream,
|
||||
const char *data,
|
||||
size_t data_len,
|
||||
int flags)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
SecBuffer encrypt_buf[3];
|
||||
SecBufferDesc encrypt_buf_desc = { SECBUFFER_VERSION, 3, encrypt_buf };
|
||||
git_str ciphertext_out = GIT_STR_INIT;
|
||||
ssize_t total_len = 0;
|
||||
|
||||
GIT_UNUSED(flags);
|
||||
|
||||
if (data_len > SSIZE_MAX)
|
||||
data_len = SSIZE_MAX;
|
||||
|
||||
git_str_init(&ciphertext_out,
|
||||
st->stream_sizes.cbHeader +
|
||||
st->stream_sizes.cbMaximumMessage +
|
||||
st->stream_sizes.cbTrailer);
|
||||
|
||||
while (data_len > 0) {
|
||||
size_t message_len = min(data_len, st->stream_sizes.cbMaximumMessage);
|
||||
size_t ciphertext_len, ciphertext_written = 0;
|
||||
|
||||
encrypt_buf[0].BufferType = SECBUFFER_STREAM_HEADER;
|
||||
encrypt_buf[0].cbBuffer = st->stream_sizes.cbHeader;
|
||||
encrypt_buf[0].pvBuffer = ciphertext_out.ptr;
|
||||
|
||||
encrypt_buf[1].BufferType = SECBUFFER_DATA;
|
||||
encrypt_buf[1].cbBuffer = (unsigned long)message_len;
|
||||
encrypt_buf[1].pvBuffer =
|
||||
ciphertext_out.ptr + st->stream_sizes.cbHeader;
|
||||
|
||||
encrypt_buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
||||
encrypt_buf[2].cbBuffer = st->stream_sizes.cbTrailer;
|
||||
encrypt_buf[2].pvBuffer =
|
||||
ciphertext_out.ptr + st->stream_sizes.cbHeader +
|
||||
message_len;
|
||||
|
||||
memcpy(ciphertext_out.ptr + st->stream_sizes.cbHeader, data, message_len);
|
||||
|
||||
if (EncryptMessage(&st->context, 0, &encrypt_buf_desc, 0) != SEC_E_OK) {
|
||||
git_error_set(GIT_ERROR_OS, "could not encrypt tls message");
|
||||
total_len = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ciphertext_len = encrypt_buf[0].cbBuffer +
|
||||
encrypt_buf[1].cbBuffer +
|
||||
encrypt_buf[2].cbBuffer;
|
||||
|
||||
while (ciphertext_written < ciphertext_len) {
|
||||
ssize_t chunk_len = git_stream_write(st->io,
|
||||
ciphertext_out.ptr + ciphertext_written,
|
||||
ciphertext_len - ciphertext_written, 0);
|
||||
|
||||
if (chunk_len < 0) {
|
||||
total_len = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ciphertext_len -= chunk_len;
|
||||
ciphertext_written += chunk_len;
|
||||
}
|
||||
|
||||
total_len += message_len;
|
||||
|
||||
data += message_len;
|
||||
data_len -= message_len;
|
||||
}
|
||||
|
||||
done:
|
||||
git_str_dispose(&ciphertext_out);
|
||||
return total_len;
|
||||
}
|
||||
|
||||
static ssize_t schannel_read(git_stream *stream, void *_data, size_t data_len)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
char *data = (char *)_data;
|
||||
SecBuffer decrypt_buf[4];
|
||||
SecBufferDesc decrypt_buf_desc = { SECBUFFER_VERSION, 4, decrypt_buf };
|
||||
SECURITY_STATUS status;
|
||||
ssize_t chunk_len, total_len = 0;
|
||||
|
||||
if (data_len > SSIZE_MAX)
|
||||
data_len = SSIZE_MAX;
|
||||
|
||||
/*
|
||||
* Loop until we have some bytes to return - we may have decrypted
|
||||
* bytes queued or ciphertext from the wire that we can decrypt and
|
||||
* return. Return any queued bytes if they're available to avoid a
|
||||
* network read, which may block. We may return less than the
|
||||
* caller requested, and they can retry for an actual network
|
||||
*/
|
||||
while ((size_t)total_len < data_len) {
|
||||
if (st->plaintext_in.size > 0) {
|
||||
size_t copy_len = min(st->plaintext_in.size, data_len);
|
||||
|
||||
memcpy(data, st->plaintext_in.ptr, copy_len);
|
||||
git_str_consume_bytes(&st->plaintext_in, copy_len);
|
||||
|
||||
data += copy_len;
|
||||
data_len -= copy_len;
|
||||
|
||||
total_len += copy_len;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (st->ciphertext_in.size > 0) {
|
||||
decrypt_buf[0].BufferType = SECBUFFER_DATA;
|
||||
decrypt_buf[0].cbBuffer = (unsigned long)min(st->ciphertext_in.size, ULONG_MAX);
|
||||
decrypt_buf[0].pvBuffer = st->ciphertext_in.ptr;
|
||||
|
||||
decrypt_buf[1].BufferType = SECBUFFER_EMPTY;
|
||||
decrypt_buf[1].cbBuffer = 0;
|
||||
decrypt_buf[1].pvBuffer = NULL;
|
||||
|
||||
decrypt_buf[2].BufferType = SECBUFFER_EMPTY;
|
||||
decrypt_buf[2].cbBuffer = 0;
|
||||
decrypt_buf[2].pvBuffer = NULL;
|
||||
|
||||
decrypt_buf[3].BufferType = SECBUFFER_EMPTY;
|
||||
decrypt_buf[3].cbBuffer = 0;
|
||||
decrypt_buf[3].pvBuffer = NULL;
|
||||
|
||||
status = DecryptMessage(&st->context, &decrypt_buf_desc, 0, NULL);
|
||||
|
||||
if (status == SEC_E_OK) {
|
||||
GIT_ASSERT(decrypt_buf[0].BufferType == SECBUFFER_STREAM_HEADER);
|
||||
GIT_ASSERT(decrypt_buf[1].BufferType == SECBUFFER_DATA);
|
||||
GIT_ASSERT(decrypt_buf[2].BufferType == SECBUFFER_STREAM_TRAILER);
|
||||
|
||||
if (git_str_put(&st->plaintext_in, decrypt_buf[1].pvBuffer, decrypt_buf[1].cbBuffer) < 0) {
|
||||
total_len = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (decrypt_buf[3].BufferType == SECBUFFER_EXTRA) {
|
||||
git_str_consume_bytes(&st->ciphertext_in, (st->ciphertext_in.size - decrypt_buf[3].cbBuffer));
|
||||
} else {
|
||||
git_str_clear(&st->ciphertext_in);
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (status == SEC_E_CONTEXT_EXPIRED) {
|
||||
break;
|
||||
} else if (status != SEC_E_INCOMPLETE_MESSAGE) {
|
||||
git_error_set(GIT_ERROR_SSL, "could not decrypt tls message");
|
||||
total_len = -1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (total_len != 0)
|
||||
break;
|
||||
|
||||
if (git_str_grow_by(&st->ciphertext_in, READ_BLOCKSIZE) < 0) {
|
||||
total_len = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((chunk_len = git_stream_read(st->io, st->ciphertext_in.ptr + st->ciphertext_in.size, st->ciphertext_in.asize - st->ciphertext_in.size)) < 0) {
|
||||
total_len = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
st->ciphertext_in.size += chunk_len;
|
||||
}
|
||||
|
||||
done:
|
||||
return total_len;
|
||||
}
|
||||
|
||||
static int schannel_close(git_stream *stream)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
int error = 0;
|
||||
|
||||
if (st->connected) {
|
||||
SecBuffer shutdown_buf;
|
||||
SecBufferDesc shutdown_buf_desc =
|
||||
{ SECBUFFER_VERSION, 1, &shutdown_buf };
|
||||
DWORD shutdown_message = SCHANNEL_SHUTDOWN, shutdown_flags;
|
||||
|
||||
shutdown_buf.BufferType = SECBUFFER_TOKEN;
|
||||
shutdown_buf.cbBuffer = sizeof(DWORD);
|
||||
shutdown_buf.pvBuffer = &shutdown_message;
|
||||
|
||||
if (ApplyControlToken(&st->context, &shutdown_buf_desc) != SEC_E_OK) {
|
||||
git_error_set(GIT_ERROR_SSL, "could not shutdown stream");
|
||||
error = -1;
|
||||
}
|
||||
|
||||
shutdown_buf.BufferType = SECBUFFER_TOKEN;
|
||||
shutdown_buf.cbBuffer = 0;
|
||||
shutdown_buf.pvBuffer = NULL;
|
||||
|
||||
shutdown_flags = ISC_REQ_ALLOCATE_MEMORY |
|
||||
ISC_REQ_CONFIDENTIALITY |
|
||||
ISC_REQ_REPLAY_DETECT |
|
||||
ISC_REQ_SEQUENCE_DETECT |
|
||||
ISC_REQ_STREAM;
|
||||
|
||||
if (InitializeSecurityContext(&st->cred, &st->context,
|
||||
NULL, shutdown_flags, 0, 0,
|
||||
&shutdown_buf_desc, 0, NULL,
|
||||
&shutdown_buf_desc, &shutdown_flags,
|
||||
NULL) == SEC_E_OK) {
|
||||
if (shutdown_buf.cbBuffer > 0) {
|
||||
if (git_stream__write_full(st->io,
|
||||
shutdown_buf.pvBuffer,
|
||||
shutdown_buf.cbBuffer, 0) < 0)
|
||||
error = -1;
|
||||
|
||||
FreeContextBuffer(shutdown_buf.pvBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
st->connected = false;
|
||||
|
||||
if (st->owned && git_stream_close(st->io) < 0)
|
||||
error = -1;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void schannel_free(git_stream *stream)
|
||||
{
|
||||
schannel_stream *st = (schannel_stream *)stream;
|
||||
|
||||
if (st->state >= STATE_CERTIFICATE) {
|
||||
CertFreeCertificateContext(st->certificate);
|
||||
CertFreeCertificateChain(st->cert_chain);
|
||||
}
|
||||
|
||||
if (st->state >= STATE_CONTEXT)
|
||||
DeleteSecurityContext(&st->context);
|
||||
|
||||
if (st->state >= STATE_CRED)
|
||||
FreeCredentialsHandle(&st->cred);
|
||||
|
||||
st->state = STATE_NONE;
|
||||
|
||||
git_str_dispose(&st->ciphertext_in);
|
||||
git_str_dispose(&st->plaintext_in);
|
||||
|
||||
git__free(st->host_w);
|
||||
|
||||
if (st->owned)
|
||||
git_stream_free(st->io);
|
||||
|
||||
git__free(st);
|
||||
}
|
||||
|
||||
static int schannel_stream_wrap(
|
||||
git_stream **out,
|
||||
git_stream *in,
|
||||
const char *host,
|
||||
int owned)
|
||||
{
|
||||
schannel_stream *st;
|
||||
|
||||
st = git__calloc(1, sizeof(schannel_stream));
|
||||
GIT_ERROR_CHECK_ALLOC(st);
|
||||
|
||||
st->io = in;
|
||||
st->owned = owned;
|
||||
|
||||
if (git_utf8_to_16_alloc(&st->host_w, host) < 0) {
|
||||
git__free(st);
|
||||
return -1;
|
||||
}
|
||||
|
||||
st->parent.version = GIT_STREAM_VERSION;
|
||||
st->parent.encrypted = 1;
|
||||
st->parent.proxy_support = git_stream_supports_proxy(st->io);
|
||||
st->parent.connect = schannel_connect;
|
||||
st->parent.certificate = schannel_certificate;
|
||||
st->parent.set_proxy = schannel_set_proxy;
|
||||
st->parent.read = schannel_read;
|
||||
st->parent.write = schannel_write;
|
||||
st->parent.close = schannel_close;
|
||||
st->parent.free = schannel_free;
|
||||
|
||||
*out = (git_stream *)st;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern int git_schannel_stream_new(
|
||||
git_stream **out,
|
||||
const char *host,
|
||||
const char *port)
|
||||
{
|
||||
git_stream *stream;
|
||||
int error;
|
||||
|
||||
GIT_ASSERT_ARG(out);
|
||||
GIT_ASSERT_ARG(host);
|
||||
GIT_ASSERT_ARG(port);
|
||||
|
||||
if ((error = git_socket_stream_new(&stream, host, port)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = schannel_stream_wrap(out, stream, host, 1)) < 0) {
|
||||
git_stream_close(stream);
|
||||
git_stream_free(stream);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
extern int git_schannel_stream_wrap(
|
||||
git_stream **out,
|
||||
git_stream *in,
|
||||
const char *host)
|
||||
{
|
||||
return schannel_stream_wrap(out, in, host, 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
28
src/libgit2/streams/schannel.h
Normal file
28
src/libgit2/streams/schannel.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_steams_schannel_h__
|
||||
#define INCLUDE_steams_schannel_h__
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "git2/sys/stream.h"
|
||||
|
||||
#ifdef GIT_SCHANNEL
|
||||
|
||||
extern int git_schannel_stream_new(
|
||||
git_stream **out,
|
||||
const char *host,
|
||||
const char *port);
|
||||
|
||||
extern int git_schannel_stream_wrap(
|
||||
git_stream **out,
|
||||
git_stream *in,
|
||||
const char *host);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -10,22 +10,23 @@
|
||||
#include "posix.h"
|
||||
#include "netops.h"
|
||||
#include "registry.h"
|
||||
#include "runtime.h"
|
||||
#include "stream.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <sys/types.h>
|
||||
# include <sys/socket.h>
|
||||
# include <sys/select.h>
|
||||
# include <sys/time.h>
|
||||
# include <netdb.h>
|
||||
# include <netinet/in.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <sys/types.h>
|
||||
# include <sys/socket.h>
|
||||
# include <sys/select.h>
|
||||
# include <sys/time.h>
|
||||
# include <netdb.h>
|
||||
# include <netinet/in.h>
|
||||
# include <arpa/inet.h>
|
||||
#else
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
# ifdef _MSC_VER
|
||||
# pragma comment(lib, "ws2_32")
|
||||
# endif
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
# ifdef _MSC_VER
|
||||
# pragma comment(lib, "ws2_32")
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
@@ -54,11 +55,8 @@ static int close_socket(GIT_SOCKET s)
|
||||
return 0;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
if (SOCKET_ERROR == closesocket(s))
|
||||
return -1;
|
||||
|
||||
if (0 != WSACleanup()) {
|
||||
git_error_set(GIT_ERROR_OS, "winsock cleanup failed");
|
||||
if (closesocket(s) != 0) {
|
||||
net_set_error("could not close socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -77,23 +75,6 @@ static int socket_connect(git_stream *stream)
|
||||
GIT_SOCKET s = INVALID_SOCKET;
|
||||
int ret;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
/* on win32, the WSA context needs to be initialized
|
||||
* before any socket calls can be performed */
|
||||
WSADATA wsd;
|
||||
|
||||
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
|
||||
git_error_set(GIT_ERROR_OS, "winsock init failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
|
||||
WSACleanup();
|
||||
git_error_set(GIT_ERROR_OS, "winsock init failed");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(&hints, 0x0, sizeof(struct addrinfo));
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
@@ -240,3 +221,42 @@ int git_socket_stream_new(
|
||||
|
||||
return init(out, host, port);
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
|
||||
static void socket_stream_global_shutdown(void)
|
||||
{
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
int git_socket_stream_global_init(void)
|
||||
{
|
||||
WORD winsock_version;
|
||||
WSADATA wsa_data;
|
||||
|
||||
winsock_version = MAKEWORD(2, 2);
|
||||
|
||||
if (WSAStartup(winsock_version, &wsa_data) != 0) {
|
||||
git_error_set(GIT_ERROR_OS, "could not initialize Windows Socket Library");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (LOBYTE(wsa_data.wVersion) != 2 ||
|
||||
HIBYTE(wsa_data.wVersion) != 2) {
|
||||
git_error_set(GIT_ERROR_SSL, "Windows Socket Library does not support Winsock 2.2");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return git_runtime_shutdown_register(socket_stream_global_shutdown);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include "stream.h"
|
||||
|
||||
int git_socket_stream_global_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,4 +20,6 @@ typedef struct {
|
||||
|
||||
extern int git_socket_stream_new(git_stream **out, const char *host, const char *port);
|
||||
|
||||
extern int git_socket_stream_global_init(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "streams/mbedtls.h"
|
||||
#include "streams/openssl.h"
|
||||
#include "streams/stransport.h"
|
||||
#include "streams/schannel.h"
|
||||
|
||||
int git_tls_stream_new(git_stream **out, const char *host, const char *port)
|
||||
{
|
||||
@@ -33,6 +34,8 @@ int git_tls_stream_new(git_stream **out, const char *host, const char *port)
|
||||
init = git_openssl_stream_new;
|
||||
#elif defined(GIT_MBEDTLS)
|
||||
init = git_mbedtls_stream_new;
|
||||
#elif defined(GIT_SCHANNEL)
|
||||
init = git_schannel_stream_new;
|
||||
#endif
|
||||
} else {
|
||||
return error;
|
||||
@@ -63,6 +66,8 @@ int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host)
|
||||
wrap = git_openssl_stream_wrap;
|
||||
#elif defined(GIT_MBEDTLS)
|
||||
wrap = git_mbedtls_stream_wrap;
|
||||
#elif defined(GIT_SCHANNEL)
|
||||
wrap = git_schannel_stream_wrap;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
#include <krb5.h>
|
||||
#endif
|
||||
|
||||
static gss_OID_desc negotiate_oid_spnego =
|
||||
static gss_OID_desc gssapi_oid_spnego =
|
||||
{ 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
|
||||
static gss_OID_desc negotiate_oid_krb5 =
|
||||
static gss_OID_desc gssapi_oid_krb5 =
|
||||
{ 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
|
||||
|
||||
static gss_OID negotiate_oids[] =
|
||||
{ &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
|
||||
static gss_OID gssapi_oids[] =
|
||||
{ &gssapi_oid_spnego, &gssapi_oid_krb5, NULL };
|
||||
|
||||
typedef struct {
|
||||
git_http_auth_context parent;
|
||||
@@ -36,9 +36,9 @@ typedef struct {
|
||||
char *challenge;
|
||||
gss_ctx_id_t gss_context;
|
||||
gss_OID oid;
|
||||
} http_auth_negotiate_context;
|
||||
} http_auth_gssapi_context;
|
||||
|
||||
static void negotiate_err_set(
|
||||
static void gssapi_err_set(
|
||||
OM_uint32 status_major,
|
||||
OM_uint32 status_minor,
|
||||
const char *message)
|
||||
@@ -58,11 +58,11 @@ static void negotiate_err_set(
|
||||
}
|
||||
}
|
||||
|
||||
static int negotiate_set_challenge(
|
||||
static int gssapi_set_challenge(
|
||||
git_http_auth_context *c,
|
||||
const char *challenge)
|
||||
{
|
||||
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
|
||||
http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
|
||||
|
||||
GIT_ASSERT_ARG(ctx);
|
||||
GIT_ASSERT_ARG(challenge);
|
||||
@@ -76,7 +76,7 @@ static int negotiate_set_challenge(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
|
||||
static void gssapi_context_dispose(http_auth_gssapi_context *ctx)
|
||||
{
|
||||
OM_uint32 status_minor;
|
||||
|
||||
@@ -92,12 +92,12 @@ static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
|
||||
ctx->challenge = NULL;
|
||||
}
|
||||
|
||||
static int negotiate_next_token(
|
||||
static int gssapi_next_token(
|
||||
git_str *buf,
|
||||
git_http_auth_context *c,
|
||||
git_credential *cred)
|
||||
{
|
||||
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
|
||||
http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
|
||||
OM_uint32 status_major, status_minor;
|
||||
gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
|
||||
input_token = GSS_C_EMPTY_BUFFER,
|
||||
@@ -126,7 +126,7 @@ static int negotiate_next_token(
|
||||
GSS_C_NT_HOSTBASED_SERVICE, &server);
|
||||
|
||||
if (GSS_ERROR(status_major)) {
|
||||
negotiate_err_set(status_major, status_minor,
|
||||
gssapi_err_set(status_major, status_minor,
|
||||
"could not parse principal");
|
||||
error = -1;
|
||||
goto done;
|
||||
@@ -152,10 +152,10 @@ static int negotiate_next_token(
|
||||
input_token.length = input_buf.size;
|
||||
input_token_ptr = &input_token;
|
||||
} else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
|
||||
negotiate_context_dispose(ctx);
|
||||
gssapi_context_dispose(ctx);
|
||||
}
|
||||
|
||||
mech = &negotiate_oid_spnego;
|
||||
mech = &gssapi_oid_spnego;
|
||||
|
||||
status_major = gss_init_sec_context(
|
||||
&status_minor,
|
||||
@@ -173,14 +173,14 @@ static int negotiate_next_token(
|
||||
NULL);
|
||||
|
||||
if (GSS_ERROR(status_major)) {
|
||||
negotiate_err_set(status_major, status_minor, "negotiate failure");
|
||||
gssapi_err_set(status_major, status_minor, "negotiate failure");
|
||||
error = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* This message merely told us auth was complete; we do not respond. */
|
||||
if (status_major == GSS_S_COMPLETE) {
|
||||
negotiate_context_dispose(ctx);
|
||||
gssapi_context_dispose(ctx);
|
||||
ctx->complete = 1;
|
||||
goto done;
|
||||
}
|
||||
@@ -204,20 +204,20 @@ done:
|
||||
return error;
|
||||
}
|
||||
|
||||
static int negotiate_is_complete(git_http_auth_context *c)
|
||||
static int gssapi_is_complete(git_http_auth_context *c)
|
||||
{
|
||||
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
|
||||
http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
|
||||
|
||||
GIT_ASSERT_ARG(ctx);
|
||||
|
||||
return (ctx->complete == 1);
|
||||
}
|
||||
|
||||
static void negotiate_context_free(git_http_auth_context *c)
|
||||
static void gssapi_context_free(git_http_auth_context *c)
|
||||
{
|
||||
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
|
||||
http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
|
||||
|
||||
negotiate_context_dispose(ctx);
|
||||
gssapi_context_dispose(ctx);
|
||||
|
||||
ctx->configured = 0;
|
||||
ctx->complete = 0;
|
||||
@@ -226,8 +226,8 @@ static void negotiate_context_free(git_http_auth_context *c)
|
||||
git__free(ctx);
|
||||
}
|
||||
|
||||
static int negotiate_init_context(
|
||||
http_auth_negotiate_context *ctx,
|
||||
static int gssapi_init_context(
|
||||
http_auth_gssapi_context *ctx,
|
||||
const git_net_url *url)
|
||||
{
|
||||
OM_uint32 status_major, status_minor;
|
||||
@@ -239,13 +239,13 @@ static int negotiate_init_context(
|
||||
status_major = gss_indicate_mechs(&status_minor, &mechanism_list);
|
||||
|
||||
if (GSS_ERROR(status_major)) {
|
||||
negotiate_err_set(status_major, status_minor,
|
||||
gssapi_err_set(status_major, status_minor,
|
||||
"could not query mechanisms");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mechanism_list) {
|
||||
for (oid = negotiate_oids; *oid; oid++) {
|
||||
for (oid = gssapi_oids; *oid; oid++) {
|
||||
for (i = 0; i < mechanism_list->count; i++) {
|
||||
item = &mechanism_list->elements[i];
|
||||
|
||||
@@ -285,14 +285,14 @@ int git_http_auth_negotiate(
|
||||
git_http_auth_context **out,
|
||||
const git_net_url *url)
|
||||
{
|
||||
http_auth_negotiate_context *ctx;
|
||||
http_auth_gssapi_context *ctx;
|
||||
|
||||
*out = NULL;
|
||||
|
||||
ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
|
||||
ctx = git__calloc(1, sizeof(http_auth_gssapi_context));
|
||||
GIT_ERROR_CHECK_ALLOC(ctx);
|
||||
|
||||
if (negotiate_init_context(ctx, url) < 0) {
|
||||
if (gssapi_init_context(ctx, url) < 0) {
|
||||
git__free(ctx);
|
||||
return -1;
|
||||
}
|
||||
@@ -300,10 +300,10 @@ int git_http_auth_negotiate(
|
||||
ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE;
|
||||
ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
|
||||
ctx->parent.connection_affinity = 1;
|
||||
ctx->parent.set_challenge = negotiate_set_challenge;
|
||||
ctx->parent.next_token = negotiate_next_token;
|
||||
ctx->parent.is_complete = negotiate_is_complete;
|
||||
ctx->parent.free = negotiate_context_free;
|
||||
ctx->parent.set_challenge = gssapi_set_challenge;
|
||||
ctx->parent.next_token = gssapi_next_token;
|
||||
ctx->parent.is_complete = gssapi_is_complete;
|
||||
ctx->parent.free = gssapi_context_free;
|
||||
|
||||
*out = (git_http_auth_context *)ctx;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/* 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
|
||||
|
||||
@@ -23,7 +23,7 @@ typedef struct {
|
||||
bool complete;
|
||||
} http_auth_ntlm_context;
|
||||
|
||||
static int ntlm_set_challenge(
|
||||
static int ntlmclient_set_challenge(
|
||||
git_http_auth_context *c,
|
||||
const char *challenge)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ static int ntlm_set_challenge(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred)
|
||||
static int ntlmclient_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred)
|
||||
{
|
||||
git_credential_userpass_plaintext *cred;
|
||||
const char *sep, *username;
|
||||
@@ -76,7 +76,7 @@ done:
|
||||
return error;
|
||||
}
|
||||
|
||||
static int ntlm_next_token(
|
||||
static int ntlmclient_next_token(
|
||||
git_str *buf,
|
||||
git_http_auth_context *c,
|
||||
git_credential *cred)
|
||||
@@ -104,7 +104,7 @@ static int ntlm_next_token(
|
||||
*/
|
||||
ctx->complete = true;
|
||||
|
||||
if (cred && ntlm_set_credentials(ctx, cred) != 0)
|
||||
if (cred && ntlmclient_set_credentials(ctx, cred) != 0)
|
||||
goto done;
|
||||
|
||||
if (challenge_len < 4) {
|
||||
@@ -162,7 +162,7 @@ done:
|
||||
return error;
|
||||
}
|
||||
|
||||
static int ntlm_is_complete(git_http_auth_context *c)
|
||||
static int ntlmclient_is_complete(git_http_auth_context *c)
|
||||
{
|
||||
http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
|
||||
|
||||
@@ -170,7 +170,7 @@ static int ntlm_is_complete(git_http_auth_context *c)
|
||||
return (ctx->complete == true);
|
||||
}
|
||||
|
||||
static void ntlm_context_free(git_http_auth_context *c)
|
||||
static void ntlmclient_context_free(git_http_auth_context *c)
|
||||
{
|
||||
http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
|
||||
|
||||
@@ -179,7 +179,7 @@ static void ntlm_context_free(git_http_auth_context *c)
|
||||
git__free(ctx);
|
||||
}
|
||||
|
||||
static int ntlm_init_context(
|
||||
static int ntlmclient_init_context(
|
||||
http_auth_ntlm_context *ctx,
|
||||
const git_net_url *url)
|
||||
{
|
||||
@@ -206,7 +206,7 @@ int git_http_auth_ntlm(
|
||||
ctx = git__calloc(1, sizeof(http_auth_ntlm_context));
|
||||
GIT_ERROR_CHECK_ALLOC(ctx);
|
||||
|
||||
if (ntlm_init_context(ctx, url) < 0) {
|
||||
if (ntlmclient_init_context(ctx, url) < 0) {
|
||||
git__free(ctx);
|
||||
return -1;
|
||||
}
|
||||
@@ -214,10 +214,10 @@ int git_http_auth_ntlm(
|
||||
ctx->parent.type = GIT_HTTP_AUTH_NTLM;
|
||||
ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT;
|
||||
ctx->parent.connection_affinity = 1;
|
||||
ctx->parent.set_challenge = ntlm_set_challenge;
|
||||
ctx->parent.next_token = ntlm_next_token;
|
||||
ctx->parent.is_complete = ntlm_is_complete;
|
||||
ctx->parent.free = ntlm_context_free;
|
||||
ctx->parent.set_challenge = ntlmclient_set_challenge;
|
||||
ctx->parent.next_token = ntlmclient_next_token;
|
||||
ctx->parent.is_complete = ntlmclient_is_complete;
|
||||
ctx->parent.free = ntlmclient_context_free;
|
||||
|
||||
*out = (git_http_auth_context *)ctx;
|
||||
|
||||
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 */
|
||||
@@ -158,10 +158,10 @@ static int apply_userpass_credentials(HINTERNET request, DWORD target, int mecha
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0)
|
||||
if ((error = user_len = git_utf8_to_16_alloc(&user, c->username)) < 0)
|
||||
goto done;
|
||||
|
||||
if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0)
|
||||
if ((error = pass_len = git_utf8_to_16_alloc(&pass, c->password)) < 0)
|
||||
goto done;
|
||||
|
||||
if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) {
|
||||
@@ -242,7 +242,7 @@ static int acquire_fallback_cred(
|
||||
HRESULT hCoInitResult;
|
||||
|
||||
/* Convert URL to wide characters */
|
||||
if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
|
||||
if (git_utf8_to_16_alloc(&wide_url, url) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
|
||||
return -1;
|
||||
}
|
||||
@@ -397,7 +397,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
return -1;
|
||||
|
||||
/* Convert URL to wide characters */
|
||||
if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) {
|
||||
if (git_utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
|
||||
goto on_error;
|
||||
}
|
||||
@@ -473,7 +473,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
}
|
||||
|
||||
/* Convert URL to wide characters */
|
||||
error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
|
||||
error = git_utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
|
||||
git_str_dispose(&processed_url);
|
||||
if (error < 0)
|
||||
goto on_error;
|
||||
@@ -531,7 +531,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
s->service) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
|
||||
if (git_utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters");
|
||||
goto on_error;
|
||||
}
|
||||
@@ -548,7 +548,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
s->service) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
|
||||
if (git_utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters");
|
||||
goto on_error;
|
||||
}
|
||||
@@ -568,7 +568,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
||||
git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]);
|
||||
|
||||
/* Convert header to wide characters */
|
||||
if ((error = git__utf8_to_16_alloc(&custom_header_wide, git_str_cstr(&buf))) < 0)
|
||||
if ((error = git_utf8_to_16_alloc(&custom_header_wide, git_str_cstr(&buf))) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (!WinHttpAddRequestHeaders(s->request, custom_header_wide, (ULONG)-1L,
|
||||
@@ -783,7 +783,7 @@ static int winhttp_connect(
|
||||
}
|
||||
|
||||
/* Prepare host */
|
||||
if (git__utf8_to_16_alloc(&wide_host, host) < 0) {
|
||||
if (git_utf8_to_16_alloc(&wide_host, host) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
|
||||
goto on_error;
|
||||
}
|
||||
@@ -792,7 +792,7 @@ static int winhttp_connect(
|
||||
if (git_http__user_agent(&ua) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
|
||||
if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
|
||||
goto on_error;
|
||||
}
|
||||
@@ -1182,7 +1182,7 @@ replay:
|
||||
}
|
||||
|
||||
/* Convert the Location header to UTF-8 */
|
||||
if (git__utf16_to_8_alloc(&location8, location) < 0) {
|
||||
if (git_utf8_from_16_alloc(&location8, location) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8");
|
||||
git__free(location);
|
||||
return -1;
|
||||
@@ -1254,7 +1254,7 @@ replay:
|
||||
else
|
||||
p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
|
||||
|
||||
if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
|
||||
if (git_utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -2015,7 +2015,7 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
|
||||
git_win32_path fullpath_w, executable_w;
|
||||
int error;
|
||||
|
||||
if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
|
||||
if (git_utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
|
||||
return -1;
|
||||
|
||||
error = git_win32_path_find_executable(fullpath_w, executable_w);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#cmakedefine GIT_OPENSSL_DYNAMIC 1
|
||||
#cmakedefine GIT_SECURE_TRANSPORT 1
|
||||
#cmakedefine GIT_MBEDTLS 1
|
||||
#cmakedefine GIT_SCHANNEL 1
|
||||
|
||||
#cmakedefine GIT_SHA1_COLLISIONDETECT 1
|
||||
#cmakedefine GIT_SHA1_WIN32 1
|
||||
|
||||
@@ -743,7 +743,7 @@ int git__getenv(git_str *out, const char *name)
|
||||
|
||||
git_str_clear(out);
|
||||
|
||||
if (git__utf8_to_16_alloc(&wide_name, name) < 0)
|
||||
if (git_utf8_to_16_alloc(&wide_name, name) < 0)
|
||||
return -1;
|
||||
|
||||
if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) {
|
||||
|
||||
@@ -43,7 +43,7 @@ char *git_win32_get_error_message(DWORD error_code)
|
||||
(LPWSTR)&lpMsgBuf, 0, NULL)) {
|
||||
/* Convert the message to UTF-8. If this fails, we will
|
||||
* return NULL, which is a condition expected by the caller */
|
||||
if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0)
|
||||
if (git_utf8_from_16_alloc(&utf8_msg, lpMsgBuf) < 0)
|
||||
utf8_msg = NULL;
|
||||
|
||||
LocalFree(lpMsgBuf);
|
||||
|
||||
@@ -336,13 +336,13 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
|
||||
|
||||
/* See if this is an absolute path (beginning with a drive letter) */
|
||||
if (git_fs_path_is_absolute(src)) {
|
||||
if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0)
|
||||
if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
/* File-prefixed NT-style paths beginning with \\?\ */
|
||||
else if (path__is_nt_namespace(src)) {
|
||||
/* Skip the NT prefix, the destination already contains it */
|
||||
if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0)
|
||||
if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
/* UNC paths */
|
||||
@@ -351,7 +351,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
|
||||
dest += 4;
|
||||
|
||||
/* Skip the leading "\\" */
|
||||
if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0)
|
||||
if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
/* Absolute paths omitting the drive letter */
|
||||
@@ -365,7 +365,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
|
||||
}
|
||||
|
||||
/* Skip the drive letter specification ("C:") */
|
||||
if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0)
|
||||
if (git_utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
/* Relative paths */
|
||||
@@ -377,7 +377,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
|
||||
|
||||
dest[cwd_len++] = L'\\';
|
||||
|
||||
if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0)
|
||||
if (git_utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ int git_win32_path_relative_from_utf8(git_win32_path out, const char *src)
|
||||
return git_win32_path_from_utf8(out, src);
|
||||
}
|
||||
|
||||
if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0)
|
||||
if ((len = git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0)
|
||||
return -1;
|
||||
|
||||
for (p = dest; p < (dest + len); p++) {
|
||||
@@ -433,7 +433,7 @@ int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
|
||||
}
|
||||
}
|
||||
|
||||
if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0)
|
||||
if ((len = git_utf8_from_16(out, GIT_WIN_PATH_UTF8, src)) < 0)
|
||||
return len;
|
||||
|
||||
git_fs_path_mkposix(dest);
|
||||
@@ -471,7 +471,7 @@ char *git_win32_path_8dot3_name(const char *path)
|
||||
if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL)
|
||||
return NULL;
|
||||
|
||||
if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0)
|
||||
if ((len = git_utf8_from_16(shortname, namelen + 1, start)) < 0)
|
||||
return NULL;
|
||||
|
||||
return shortname;
|
||||
|
||||
@@ -649,7 +649,7 @@ int p_getcwd(char *buffer_out, size_t size)
|
||||
git_win32_path_remove_namespace(cwd, wcslen(cwd));
|
||||
|
||||
/* Convert the working directory back to UTF-8 */
|
||||
if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
|
||||
if (git_utf8_from_16(buffer_out, size, cwd) < 0) {
|
||||
DWORD code = GetLastError();
|
||||
|
||||
if (code == ERROR_INSUFFICIENT_BUFFER)
|
||||
|
||||
@@ -15,108 +15,114 @@ GIT_INLINE(void) git__set_errno(void)
|
||||
errno = EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 string to wide characters.
|
||||
*
|
||||
* @param dest The buffer to receive the wide string.
|
||||
* @param dest_size The size of the buffer, in characters.
|
||||
* @param src The UTF-8 string to convert.
|
||||
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
|
||||
int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
|
||||
{
|
||||
/* Length of -1 indicates NULL termination of the input string. */
|
||||
return git_utf8_to_16_with_len(dest, dest_size, src, -1);
|
||||
}
|
||||
|
||||
int git_utf8_to_16_with_len(
|
||||
wchar_t *dest,
|
||||
size_t _dest_size,
|
||||
const char *src,
|
||||
int src_len)
|
||||
{
|
||||
int dest_size = (int)min(_dest_size, INT_MAX);
|
||||
int len;
|
||||
|
||||
/* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
|
||||
* turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
|
||||
* length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */
|
||||
if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0)
|
||||
/*
|
||||
* Subtract 1 from the result to turn 0 into -1 (an error code) and
|
||||
* to not count the NULL terminator as part of the string's length.
|
||||
* MultiByteToWideChar never returns int's minvalue, so underflow
|
||||
* is not possible.
|
||||
*/
|
||||
len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||
src, src_len, dest, dest_size) - 1;
|
||||
|
||||
if (len < 0)
|
||||
git__set_errno();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a wide string to UTF-8.
|
||||
*
|
||||
* @param dest The buffer to receive the UTF-8 string.
|
||||
* @param dest_size The size of the buffer, in bytes.
|
||||
* @param src The wide string to convert.
|
||||
* @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src)
|
||||
int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src)
|
||||
{
|
||||
/* Length of -1 indicates NULL termination of the input string. */
|
||||
return git_utf8_from_16_with_len(dest, dest_size, src, -1);
|
||||
}
|
||||
|
||||
int git_utf8_from_16_with_len(
|
||||
char *dest,
|
||||
size_t _dest_size,
|
||||
const wchar_t *src,
|
||||
int src_len)
|
||||
{
|
||||
int dest_size = (int)min(_dest_size, INT_MAX);
|
||||
int len;
|
||||
|
||||
/* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
|
||||
* turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
|
||||
* length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */
|
||||
if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0)
|
||||
/*
|
||||
* Subtract 1 from the result to turn 0 into -1 (an error code) and
|
||||
* to not count the NULL terminator as part of the string's length.
|
||||
* WideCharToMultiByte never returns int's minvalue, so underflow
|
||||
* is not possible.
|
||||
*/
|
||||
len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||
src, src_len, dest, dest_size, NULL, NULL) - 1;
|
||||
|
||||
if (len < 0)
|
||||
git__set_errno();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 string to wide characters.
|
||||
* Memory is allocated to hold the converted string.
|
||||
* The caller is responsible for freeing the string with git__free.
|
||||
*
|
||||
* @param dest Receives a pointer to the wide string.
|
||||
* @param src The UTF-8 string to convert.
|
||||
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf8_to_16_alloc(wchar_t **dest, const char *src)
|
||||
int git_utf8_to_16_alloc(wchar_t **dest, const char *src)
|
||||
{
|
||||
/* Length of -1 indicates NULL termination of the input string. */
|
||||
return git_utf8_to_16_alloc_with_len(dest, src, -1);
|
||||
}
|
||||
|
||||
int git_utf8_to_16_alloc_with_len(wchar_t **dest, const char *src, int src_len)
|
||||
{
|
||||
int utf16_size;
|
||||
|
||||
*dest = NULL;
|
||||
|
||||
/* Length of -1 indicates NULL termination of the input string */
|
||||
utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
|
||||
utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||
src, src_len, NULL, 0);
|
||||
|
||||
if (!utf16_size) {
|
||||
git__set_errno();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
*dest = git__mallocarray(utf16_size, sizeof(wchar_t));
|
||||
GIT_ERROR_CHECK_ALLOC(*dest);
|
||||
|
||||
utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size);
|
||||
|
||||
if (!utf16_size) {
|
||||
git__set_errno();
|
||||
utf16_size = git_utf8_to_16_with_len(*dest, (size_t)utf16_size,
|
||||
src, src_len);
|
||||
|
||||
if (utf16_size < 0) {
|
||||
git__free(*dest);
|
||||
*dest = NULL;
|
||||
}
|
||||
|
||||
/* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
|
||||
* terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
|
||||
* so underflow is not possible */
|
||||
return utf16_size - 1;
|
||||
return utf16_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a wide string to UTF-8.
|
||||
* Memory is allocated to hold the converted string.
|
||||
* The caller is responsible for freeing the string with git__free.
|
||||
*
|
||||
* @param dest Receives a pointer to the UTF-8 string.
|
||||
* @param src The wide string to convert.
|
||||
* @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
|
||||
int git_utf8_from_16_alloc(char **dest, const wchar_t *src)
|
||||
{
|
||||
/* Length of -1 indicates NULL termination of the input string. */
|
||||
return git_utf8_from_16_alloc_with_len(dest, src, -1);
|
||||
}
|
||||
|
||||
int git_utf8_from_16_alloc_with_len(char **dest, const wchar_t *src, int src_len)
|
||||
{
|
||||
int utf8_size;
|
||||
|
||||
*dest = NULL;
|
||||
|
||||
/* Length of -1 indicates NULL termination of the input string */
|
||||
utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL);
|
||||
utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||
src, src_len, NULL, 0, NULL, NULL);
|
||||
|
||||
if (!utf8_size) {
|
||||
git__set_errno();
|
||||
@@ -124,23 +130,15 @@ int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
|
||||
}
|
||||
|
||||
*dest = git__malloc(utf8_size);
|
||||
GIT_ERROR_CHECK_ALLOC(*dest);
|
||||
|
||||
if (!*dest) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL);
|
||||
|
||||
if (!utf8_size) {
|
||||
git__set_errno();
|
||||
utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||
src, src_len, *dest, utf8_size, NULL, NULL);
|
||||
|
||||
if (utf8_size < 0) {
|
||||
git__free(*dest);
|
||||
*dest = NULL;
|
||||
}
|
||||
|
||||
/* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
|
||||
* terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
|
||||
* so underflow is not possible */
|
||||
return utf8_size - 1;
|
||||
return utf8_size;
|
||||
}
|
||||
|
||||
@@ -15,15 +15,46 @@
|
||||
# define WC_ERR_INVALID_CHARS 0x80
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Converts a NUL-terminated UTF-8 string to wide characters. This is a
|
||||
* convenience function for `git_utf8_to_16_with_len`.
|
||||
*
|
||||
* @param dest The buffer to receive the wide string.
|
||||
* @param dest_size The size of the buffer, in characters.
|
||||
* @param src The UTF-8 string to convert.
|
||||
* @return The length of the wide string, in characters
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 string to wide characters.
|
||||
*
|
||||
* @param dest The buffer to receive the wide string.
|
||||
* @param dest_size The size of the buffer, in characters.
|
||||
* @param src The UTF-8 string to convert.
|
||||
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
|
||||
* @param src_len The length of the string to convert.
|
||||
* @return The length of the wide string, in characters
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
|
||||
int git_utf8_to_16_with_len(
|
||||
wchar_t *dest,
|
||||
size_t dest_size,
|
||||
const char *src,
|
||||
int src_len);
|
||||
|
||||
/**
|
||||
* Converts a NUL-terminated wide string to UTF-8. This is a convenience
|
||||
* function for `git_utf8_from_16_with_len`.
|
||||
*
|
||||
* @param dest The buffer to receive the UTF-8 string.
|
||||
* @param dest_size The size of the buffer, in bytes.
|
||||
* @param src The wide string to convert.
|
||||
* @param src_len The length of the string to convert.
|
||||
* @return The length of the UTF-8 string, in bytes
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src);
|
||||
|
||||
/**
|
||||
* Converts a wide string to UTF-8.
|
||||
@@ -31,30 +62,66 @@ int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
|
||||
* @param dest The buffer to receive the UTF-8 string.
|
||||
* @param dest_size The size of the buffer, in bytes.
|
||||
* @param src The wide string to convert.
|
||||
* @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
|
||||
* @param src_len The length of the string to convert.
|
||||
* @return The length of the UTF-8 string, in bytes
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src);
|
||||
int git_utf8_from_16_with_len(char *dest, size_t dest_size, const wchar_t *src, int src_len);
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 string to wide characters.
|
||||
* Memory is allocated to hold the converted string.
|
||||
* The caller is responsible for freeing the string with git__free.
|
||||
* Converts a UTF-8 string to wide characters. Memory is allocated to hold
|
||||
* the converted string. The caller is responsible for freeing the string
|
||||
* with git__free.
|
||||
*
|
||||
* @param dest Receives a pointer to the wide string.
|
||||
* @param src The UTF-8 string to convert.
|
||||
* @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
|
||||
* @return The length of the wide string, in characters
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf8_to_16_alloc(wchar_t **dest, const char *src);
|
||||
int git_utf8_to_16_alloc(wchar_t **dest, const char *src);
|
||||
|
||||
/**
|
||||
* Converts a wide string to UTF-8.
|
||||
* Memory is allocated to hold the converted string.
|
||||
* The caller is responsible for freeing the string with git__free.
|
||||
* Converts a UTF-8 string to wide characters. Memory is allocated to hold
|
||||
* the converted string. The caller is responsible for freeing the string
|
||||
* with git__free.
|
||||
*
|
||||
* @param dest Receives a pointer to the wide string.
|
||||
* @param src The UTF-8 string to convert.
|
||||
* @param src_len The length of the string.
|
||||
* @return The length of the wide string, in characters
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git_utf8_to_16_alloc_with_len(
|
||||
wchar_t **dest,
|
||||
const char *src,
|
||||
int src_len);
|
||||
|
||||
/**
|
||||
* Converts a wide string to UTF-8. Memory is allocated to hold the
|
||||
* converted string. The caller is responsible for freeing the string
|
||||
* with git__free.
|
||||
*
|
||||
* @param dest Receives a pointer to the UTF-8 string.
|
||||
* @param src The wide string to convert.
|
||||
* @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
|
||||
* @return The length of the UTF-8 string, in bytes
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git__utf16_to_8_alloc(char **dest, const wchar_t *src);
|
||||
int git_utf8_from_16_alloc(char **dest, const wchar_t *src);
|
||||
|
||||
/**
|
||||
* Converts a wide string to UTF-8. Memory is allocated to hold the
|
||||
* converted string. The caller is responsible for freeing the string
|
||||
* with git__free.
|
||||
*
|
||||
* @param dest Receives a pointer to the UTF-8 string.
|
||||
* @param src The wide string to convert.
|
||||
* @param src_len The length of the wide string.
|
||||
* @return The length of the UTF-8 string, in bytes
|
||||
* (not counting the NULL terminator), or < 0 for failure
|
||||
*/
|
||||
int git_utf8_from_16_alloc_with_len(
|
||||
char **dest,
|
||||
const wchar_t *src,
|
||||
int src_len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -115,7 +115,7 @@ int git_win32__file_attribute_to_stat(
|
||||
|
||||
/* st_size gets the UTF-8 length of the target name, in bytes,
|
||||
* not counting the NULL terminator */
|
||||
if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) {
|
||||
if ((st->st_size = git_utf8_from_16(NULL, 0, target)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -103,10 +103,10 @@ int cl_setenv(const char *name, const char *value)
|
||||
{
|
||||
wchar_t *wide_name, *wide_value = NULL;
|
||||
|
||||
cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
|
||||
cl_assert(git_utf8_to_16_alloc(&wide_name, name) >= 0);
|
||||
|
||||
if (value) {
|
||||
cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0);
|
||||
cl_assert(git_utf8_to_16_alloc(&wide_value, value) >= 0);
|
||||
cl_assert(SetEnvironmentVariableW(wide_name, wide_value));
|
||||
} else {
|
||||
/* Windows XP returns 0 (failed) when passing NULL for lpValue when
|
||||
|
||||
@@ -580,6 +580,17 @@ static int succeed_certificate_check(git_cert *cert, int valid, const char *host
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x509_succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
|
||||
{
|
||||
GIT_UNUSED(valid);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
cl_assert_equal_s("github.com", host);
|
||||
cl_assert_equal_i(GIT_CERT_X509, cert->cert_type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
|
||||
{
|
||||
GIT_UNUSED(cert);
|
||||
@@ -901,7 +912,7 @@ void test_online_clone__certificate_invalid(void)
|
||||
|
||||
void test_online_clone__certificate_valid(void)
|
||||
{
|
||||
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
|
||||
g_options.fetch_opts.callbacks.certificate_check = x509_succeed_certificate_check;
|
||||
|
||||
cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ void test_stream_registration__tls(void)
|
||||
cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL));
|
||||
error = git_tls_stream_new(&stream, "localhost", "443");
|
||||
|
||||
/* We don't have TLS support enabled, or we're on Windows,
|
||||
* which has no arbitrary TLS stream support.
|
||||
/* We don't have TLS support enabled, or we're on Windows
|
||||
* with WinHTTP, which is not actually TLS stream support.
|
||||
*/
|
||||
#if defined(GIT_WIN32) || !defined(GIT_HTTPS)
|
||||
#if defined(GIT_WINHTTP) || !defined(GIT_HTTPS)
|
||||
cl_git_fail_with(-1, error);
|
||||
#else
|
||||
cl_git_pass(error);
|
||||
|
||||
@@ -98,7 +98,7 @@ static void do_junction(const char *old, const char *new)
|
||||
|
||||
git_str_putc(&unparsed_buf, '\\');
|
||||
|
||||
subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf));
|
||||
subst_utf16_len = git_utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf));
|
||||
subst_byte_len = subst_utf16_len * sizeof(WCHAR);
|
||||
|
||||
print_utf16_len = subst_utf16_len - 4;
|
||||
@@ -124,11 +124,11 @@ static void do_junction(const char *old, const char *new)
|
||||
subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer;
|
||||
print_utf16 = subst_utf16 + subst_utf16_len + 1;
|
||||
|
||||
ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1,
|
||||
ret = git_utf8_to_16(subst_utf16, subst_utf16_len + 1,
|
||||
git_str_cstr(&unparsed_buf));
|
||||
cl_assert_equal_i(subst_utf16_len, ret);
|
||||
|
||||
ret = git__utf8_to_16(print_utf16,
|
||||
ret = git_utf8_to_16(print_utf16,
|
||||
print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4);
|
||||
cl_assert_equal_i(print_utf16_len, ret);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user