summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2023-03-06 15:19:29 -0800
committerEdward Thomson <ethomson@edwardthomson.com>2023-03-06 15:19:29 -0800
commitc799fa21db8d6bb34ce195ef41b5e3ddffc888ea (patch)
tree6ed55f8bc0625c5528e74d768017bab1e3815088
parent8164b4816230711f1aeb8e474011f496f3bfdb98 (diff)
downloadlibgit2-ethomson/schannel.tar.gz
-rw-r--r--CMakeLists.txt6
-rw-r--r--cmake/SelectHTTPSBackend.cmake7
-rw-r--r--cmake/SelectHashes.cmake4
-rw-r--r--src/libgit2/libgit2.c2
-rw-r--r--src/libgit2/streams/schannel.c544
-rw-r--r--src/libgit2/streams/schannel.h23
-rw-r--r--src/libgit2/streams/tls.c5
-rw-r--r--src/util/git2_features.h.in1
8 files changed, 586 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 710915da1..7aed74df6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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()
diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake
index 20221bf9f..2d0b6d6da 100644
--- a/cmake/SelectHTTPSBackend.cmake
+++ b/cmake/SelectHTTPSBackend.cmake
@@ -19,6 +19,8 @@ if(USE_HTTPS)
message(STATUS "Security framework is too old, falling back to OpenSSL")
set(USE_HTTPS "OpenSSL")
endif()
+ elseif(USE_SCHANNEL)
+ set(USE_HTTPS "Schannel")
elseif(USE_WINHTTP)
set(USE_HTTPS "WinHTTP")
elseif(OPENSSL_FOUND)
@@ -106,6 +108,11 @@ 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
elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic")
diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake
index faf9e2ea3..5c007e587 100644
--- a/cmake/SelectHashes.cmake
+++ b/cmake/SelectHashes.cmake
@@ -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)
diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c
index f225122e5..6405574c7 100644
--- a/src/libgit2/libgit2.c
+++ b/src/libgit2/libgit2.c
@@ -30,6 +30,7 @@
#include "streams/registry.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
+#include "streams/schannel.h"
#include "transports/smart.h"
#include "transports/http.h"
#include "transports/ssh.h"
@@ -80,6 +81,7 @@ int git_libgit2_init(void)
git_stream_registry_global_init,
git_openssl_stream_global_init,
git_mbedtls_stream_global_init,
+ git_schannel_stream_global_init,
git_mwindow_global_init,
git_pool_global_init,
git_libgit2_settings_global_init
diff --git a/src/libgit2/streams/schannel.c b/src/libgit2/streams/schannel.c
new file mode 100644
index 000000000..89f240dfa
--- /dev/null
+++ b/src/libgit2/streams/schannel.c
@@ -0,0 +1,544 @@
+/*
+ * 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 "runtime.h"
+#include "stream.h"
+#include "streams/socket.h"
+
+static void schannel_global_shutdown(void)
+{
+ WSACleanup();
+}
+
+int git_schannel_stream_global_init(void)
+{
+ WORD tls_version;
+ WSADATA wsa_data;
+
+ /* TODO: this is process global; allow callers to configure it so it doesn't overwrite their existing settings. */
+
+ tls_version = MAKEWORD(2, 2);
+
+ if (WSAStartup(tls_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(schannel_global_shutdown);
+}
+
+typedef enum {
+ STATE_NONE = 0,
+ STATE_CRED = 1,
+ STATE_CONTEXT = 2,
+} schannel_state;
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ int owned;
+ bool connected;
+ char *host;
+
+ schannel_state state;
+
+ CredHandle cred;
+ CtxtHandle context;
+ SecPkgContext_StreamSizes stream_sizes;
+
+ git_str plaintext_in;
+ git_str ciphertext_in;
+} schannel_stream;
+
+static int schannel_connect(git_stream *stream)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+ SCHANNEL_CRED cred = { 0 };
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+ DWORD context_flags;
+ static size_t MAX_RETRIES = 1024;
+ // TODO: sanity check this - probably 4096 is sane
+ // to get it all in a single read. but 16k is the max tls packet size.
+ static size_t READ_BLOCKSIZE = 4096;
+ 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_NO_DEFAULT_CREDS;
+ cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_3_CLIENT;
+ cred.dwMinimumCipherStrength = 128; // TODO
+
+ // TODO: handle st->state != 0
+
+ /* TODO: do we need to pass an expiry timestamp? */
+ 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;
+
+ /* TODO: ensure that caller isn't reentering - we need st->context = NULL here */
+ git_str_clear(&st->ciphertext_in);
+
+ /* TODO: convert host to wchar and use W method */
+ /* TODO: do we need to pass an expiry timestamp? (last arg) */
+ 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 };
+
+ printf("input data: %d\n", st->ciphertext_in.size);
+
+ status = InitializeSecurityContextA(
+ &st->cred, retries ? &st->context : NULL, st->host,
+ 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);
+
+ printf("remain: %d\n", input_buf[1].cbBuffer);
+
+ git_str_consume_bytes(
+ &st->ciphertext_in,
+ st->ciphertext_in.size -
+ input_buf[1].cbBuffer);
+
+ printf("new len: %d\n", st->ciphertext_in.size);
+ } else {
+ printf("clearning...\n");
+ git_str_clear(&st->ciphertext_in);
+ printf("len: %d\n", st->ciphertext_in.size);
+ }
+
+
+
+
+
+ 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;
+ }
+
+ printf(" pre-read: input data: %d\n",
+ st->ciphertext_in.size);
+
+ 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;
+ printf(" read: %d\n", read_len);
+ printf("post-read: input data: %d\n",
+ st->ciphertext_in.size);
+
+ } else {
+ printf("yikes: %x\n", status);
+
+ 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;
+ }
+ }
+
+ printf("error is: %d, status is: %d (%d)\n", error, status, (status == SEC_I_CONTINUE_NEEDED));
+
+ st->connected = (error == 0);
+ return error;
+}
+
+static int schannel_certificate(git_cert **out, git_stream *stream)
+{
+ 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 };
+ ssize_t total_len = 0;
+
+ /* TODO: use a git_str on the stream */
+ git_str ciphertext_out = GIT_STR_INIT;
+
+ GIT_UNUSED(flags);
+
+ if (data_len > SSIZE_MAX)
+ data_len = SSIZE_MAX;
+
+
+ // TODO: should this move?
+ 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:
+ 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;
+ const size_t READ_BLOCKSIZE = (16 * 1024);
+ bool has_read = false;
+ ssize_t chunk_len, total_len = 0;
+
+ if (data_len > SSIZE_MAX)
+ data_len = SSIZE_MAX;
+
+ 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 (!has_read) {
+ 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;
+
+ has_read = true;
+ continue;
+ }
+
+ break;
+ }
+
+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_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);
+ 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;
+
+ st->host = git__strdup(host);
+ GIT_ERROR_CHECK_ALLOC(st->host);
+
+ 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
diff --git a/src/libgit2/streams/schannel.h b/src/libgit2/streams/schannel.h
new file mode 100644
index 000000000..7ed89d1a3
--- /dev/null
+++ b/src/libgit2/streams/schannel.h
@@ -0,0 +1,23 @@
+/*
+ * 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"
+
+extern int git_schannel_stream_global_init(void);
+
+#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
diff --git a/src/libgit2/streams/tls.c b/src/libgit2/streams/tls.c
index e063a33f9..246ac9ca7 100644
--- a/src/libgit2/streams/tls.c
+++ b/src/libgit2/streams/tls.c
@@ -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
}
diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in
index fbf0cab60..1575be641 100644
--- a/src/util/git2_features.h.in
+++ b/src/util/git2_features.h.in
@@ -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