summaryrefslogtreecommitdiff
path: root/src/libgit2/streams
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2021-11-14 08:47:40 -0500
committerEdward Thomson <ethomson@edwardthomson.com>2022-02-22 22:07:44 -0500
commitef4ab2988320005cbcb3db920e6b41f10b3c60cf (patch)
tree6609f213ad9d607e8df1f543919d3154c056c3ef /src/libgit2/streams
parent49e180c862dc7c6d1f62a53bf8756e25b3417968 (diff)
downloadlibgit2-ef4ab2988320005cbcb3db920e6b41f10b3c60cf.tar.gz
refactor: `src` is now `src/libgit2`
Diffstat (limited to 'src/libgit2/streams')
-rw-r--r--src/libgit2/streams/mbedtls.c482
-rw-r--r--src/libgit2/streams/mbedtls.h23
-rw-r--r--src/libgit2/streams/openssl.c747
-rw-r--r--src/libgit2/streams/openssl.h31
-rw-r--r--src/libgit2/streams/openssl_dynamic.c309
-rw-r--r--src/libgit2/streams/openssl_dynamic.h348
-rw-r--r--src/libgit2/streams/openssl_legacy.c203
-rw-r--r--src/libgit2/streams/openssl_legacy.h63
-rw-r--r--src/libgit2/streams/registry.c119
-rw-r--r--src/libgit2/streams/registry.h19
-rw-r--r--src/libgit2/streams/socket.c239
-rw-r--r--src/libgit2/streams/socket.h23
-rw-r--r--src/libgit2/streams/stransport.c326
-rw-r--r--src/libgit2/streams/stransport.h21
-rw-r--r--src/libgit2/streams/tls.c75
-rw-r--r--src/libgit2/streams/tls.h31
16 files changed, 3059 insertions, 0 deletions
diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c
new file mode 100644
index 000000000..0cf5c8af1
--- /dev/null
+++ b/src/libgit2/streams/mbedtls.c
@@ -0,0 +1,482 @@
+/*
+ * 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/mbedtls.h"
+
+#ifdef GIT_MBEDTLS
+
+#include <ctype.h>
+
+#include "runtime.h"
+#include "stream.h"
+#include "streams/socket.h"
+#include "netops.h"
+#include "git2/transport.h"
+#include "util.h"
+
+#ifndef GIT_DEFAULT_CERT_LOCATION
+#define GIT_DEFAULT_CERT_LOCATION NULL
+#endif
+
+/* Work around C90-conformance issues */
+#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
+# if defined(_MSC_VER)
+# define inline __inline
+# elif defined(__GNUC__)
+# define inline __inline__
+# else
+# define inline
+# endif
+#endif
+
+#include <mbedtls/config.h>
+#include <mbedtls/ssl.h>
+#include <mbedtls/error.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/ctr_drbg.h>
+
+#undef inline
+
+#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA"
+#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30
+
+static mbedtls_ssl_config *git__ssl_conf;
+static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT];
+static mbedtls_entropy_context *mbedtls_entropy;
+
+/**
+ * This function aims to clean-up the SSL context which
+ * we allocated.
+ */
+static void shutdown_ssl(void)
+{
+ if (git__ssl_conf) {
+ mbedtls_x509_crt_free(git__ssl_conf->ca_chain);
+ git__free(git__ssl_conf->ca_chain);
+ mbedtls_ctr_drbg_free(git__ssl_conf->p_rng);
+ git__free(git__ssl_conf->p_rng);
+ mbedtls_ssl_config_free(git__ssl_conf);
+ git__free(git__ssl_conf);
+ git__ssl_conf = NULL;
+ }
+ if (mbedtls_entropy) {
+ mbedtls_entropy_free(mbedtls_entropy);
+ git__free(mbedtls_entropy);
+ mbedtls_entropy = NULL;
+ }
+}
+
+int git_mbedtls_stream_global_init(void)
+{
+ int loaded = 0;
+ char *crtpath = GIT_DEFAULT_CERT_LOCATION;
+ struct stat statbuf;
+ mbedtls_ctr_drbg_context *ctr_drbg = NULL;
+
+ size_t ciphers_known = 0;
+ char *cipher_name = NULL;
+ char *cipher_string = NULL;
+ char *cipher_string_tmp = NULL;
+
+ git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config));
+ GIT_ERROR_CHECK_ALLOC(git__ssl_conf);
+
+ mbedtls_ssl_config_init(git__ssl_conf);
+ if (mbedtls_ssl_config_defaults(git__ssl_conf,
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
+ git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS");
+ goto cleanup;
+ }
+
+ /* configure TLSv1 */
+ mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0);
+
+ /* verify_server_cert is responsible for making the check.
+ * OPTIONAL because REQUIRED drops the certificate as soon as the check
+ * is made, so we can never see the certificate and override it. */
+ mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
+
+ /* set the list of allowed ciphersuites */
+ ciphers_known = 0;
+ cipher_string = cipher_string_tmp = git__strdup(GIT_SSL_DEFAULT_CIPHERS);
+ GIT_ERROR_CHECK_ALLOC(cipher_string);
+
+ while ((cipher_name = git__strtok(&cipher_string_tmp, ":")) != NULL) {
+ int cipherid = mbedtls_ssl_get_ciphersuite_id(cipher_name);
+ if (cipherid == 0) continue;
+
+ if (ciphers_known >= ARRAY_SIZE(ciphers_list)) {
+ git_error_set(GIT_ERROR_SSL, "out of cipher list space");
+ goto cleanup;
+ }
+
+ ciphers_list[ciphers_known++] = cipherid;
+ }
+ git__free(cipher_string);
+
+ if (!ciphers_known) {
+ git_error_set(GIT_ERROR_SSL, "no cipher could be enabled");
+ goto cleanup;
+ }
+ mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list);
+
+ /* Seeding the random number generator */
+ mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context));
+ GIT_ERROR_CHECK_ALLOC(mbedtls_entropy);
+
+ mbedtls_entropy_init(mbedtls_entropy);
+
+ ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context));
+ GIT_ERROR_CHECK_ALLOC(ctr_drbg);
+
+ mbedtls_ctr_drbg_init(ctr_drbg);
+
+ if (mbedtls_ctr_drbg_seed(ctr_drbg,
+ mbedtls_entropy_func,
+ mbedtls_entropy, NULL, 0) != 0) {
+ git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool");
+ goto cleanup;
+ }
+
+ mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg);
+
+ /* load default certificates */
+ if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
+ loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0);
+ if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
+ loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0);
+
+ return git_runtime_shutdown_register(shutdown_ssl);
+
+cleanup:
+ mbedtls_ctr_drbg_free(ctr_drbg);
+ git__free(ctr_drbg);
+ mbedtls_ssl_config_free(git__ssl_conf);
+ git__free(git__ssl_conf);
+ git__ssl_conf = NULL;
+
+ return -1;
+}
+
+static int bio_read(void *b, unsigned char *buf, size_t len)
+{
+ git_stream *io = (git_stream *) b;
+ return (int) git_stream_read(io, buf, min(len, INT_MAX));
+}
+
+static int bio_write(void *b, const unsigned char *buf, size_t len)
+{
+ git_stream *io = (git_stream *) b;
+ return (int) git_stream_write(io, (const char *)buf, min(len, INT_MAX), 0);
+}
+
+static int ssl_set_error(mbedtls_ssl_context *ssl, int error)
+{
+ char errbuf[512];
+ int ret = -1;
+
+ GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_READ);
+ GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_WRITE);
+
+ if (error != 0)
+ mbedtls_strerror( error, errbuf, 512 );
+
+ switch(error) {
+ case 0:
+ git_error_set(GIT_ERROR_SSL, "SSL error: unknown error");
+ break;
+
+ case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED:
+ git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf);
+ ret = GIT_ECERTIFICATE;
+ break;
+
+ default:
+ git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf);
+ }
+
+ return ret;
+}
+
+static int ssl_teardown(mbedtls_ssl_context *ssl)
+{
+ int ret = 0;
+
+ ret = mbedtls_ssl_close_notify(ssl);
+ if (ret < 0)
+ ret = ssl_set_error(ssl, ret);
+
+ mbedtls_ssl_free(ssl);
+ return ret;
+}
+
+static int verify_server_cert(mbedtls_ssl_context *ssl)
+{
+ int ret = -1;
+
+ if ((ret = mbedtls_ssl_get_verify_result(ssl)) != 0) {
+ char vrfy_buf[512];
+ int len = mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "", ret);
+ if (len >= 1) vrfy_buf[len - 1] = '\0'; /* Remove trailing \n */
+ git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid: %#04x - %s", ret, vrfy_buf);
+ return GIT_ECERTIFICATE;
+ }
+
+ return 0;
+}
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ int owned;
+ bool connected;
+ char *host;
+ mbedtls_ssl_context *ssl;
+ git_cert_x509 cert_info;
+} mbedtls_stream;
+
+
+static int mbedtls_connect(git_stream *stream)
+{
+ int ret;
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+
+ if (st->owned && (ret = git_stream_connect(st->io)) < 0)
+ return ret;
+
+ st->connected = true;
+
+ mbedtls_ssl_set_hostname(st->ssl, st->host);
+
+ mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL);
+
+ if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0)
+ return ssl_set_error(st->ssl, ret);
+
+ return verify_server_cert(st->ssl);
+}
+
+static int mbedtls_certificate(git_cert **out, git_stream *stream)
+{
+ unsigned char *encoded_cert;
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+
+ const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(st->ssl);
+ if (!cert) {
+ git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate");
+ return -1;
+ }
+
+ /* Retrieve the length of the certificate first */
+ if (cert->raw.len == 0) {
+ git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information");
+ return -1;
+ }
+
+ encoded_cert = git__malloc(cert->raw.len);
+ GIT_ERROR_CHECK_ALLOC(encoded_cert);
+ memcpy(encoded_cert, cert->raw.p, cert->raw.len);
+
+ st->cert_info.parent.cert_type = GIT_CERT_X509;
+ st->cert_info.data = encoded_cert;
+ st->cert_info.len = cert->raw.len;
+
+ *out = &st->cert_info.parent;
+
+ return 0;
+}
+
+static int mbedtls_set_proxy(git_stream *stream, const git_proxy_options *proxy_options)
+{
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy_options);
+}
+
+static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+ int written;
+
+ GIT_UNUSED(flags);
+
+ /*
+ * `mbedtls_ssl_write` can only represent INT_MAX bytes
+ * written via its return value. We thus need to clamp
+ * the maximum number of bytes written.
+ */
+ len = min(len, INT_MAX);
+
+ if ((written = mbedtls_ssl_write(st->ssl, (const unsigned char *)data, len)) <= 0)
+ return ssl_set_error(st->ssl, written);
+
+ return written;
+}
+
+static ssize_t mbedtls_stream_read(git_stream *stream, void *data, size_t len)
+{
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+ int ret;
+
+ if ((ret = mbedtls_ssl_read(st->ssl, (unsigned char *)data, len)) <= 0)
+ ssl_set_error(st->ssl, ret);
+
+ return ret;
+}
+
+static int mbedtls_stream_close(git_stream *stream)
+{
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+ int ret = 0;
+
+ if (st->connected && (ret = ssl_teardown(st->ssl)) != 0)
+ return -1;
+
+ st->connected = false;
+
+ return st->owned ? git_stream_close(st->io) : 0;
+}
+
+static void mbedtls_stream_free(git_stream *stream)
+{
+ mbedtls_stream *st = (mbedtls_stream *) stream;
+
+ if (st->owned)
+ git_stream_free(st->io);
+
+ git__free(st->host);
+ git__free(st->cert_info.data);
+ mbedtls_ssl_free(st->ssl);
+ git__free(st->ssl);
+ git__free(st);
+}
+
+static int mbedtls_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
+{
+ mbedtls_stream *st;
+ int error;
+
+ st = git__calloc(1, sizeof(mbedtls_stream));
+ GIT_ERROR_CHECK_ALLOC(st);
+
+ st->io = in;
+ st->owned = owned;
+
+ st->ssl = git__malloc(sizeof(mbedtls_ssl_context));
+ GIT_ERROR_CHECK_ALLOC(st->ssl);
+ mbedtls_ssl_init(st->ssl);
+ if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) {
+ git_error_set(GIT_ERROR_SSL, "failed to create ssl object");
+ error = -1;
+ goto out_err;
+ }
+
+ 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 = mbedtls_connect;
+ st->parent.certificate = mbedtls_certificate;
+ st->parent.set_proxy = mbedtls_set_proxy;
+ st->parent.read = mbedtls_stream_read;
+ st->parent.write = mbedtls_stream_write;
+ st->parent.close = mbedtls_stream_close;
+ st->parent.free = mbedtls_stream_free;
+
+ *out = (git_stream *) st;
+ return 0;
+
+out_err:
+ mbedtls_ssl_free(st->ssl);
+ git_stream_close(st->io);
+ git_stream_free(st->io);
+ git__free(st);
+
+ return error;
+}
+
+int git_mbedtls_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host)
+{
+ return mbedtls_stream_wrap(out, in, host, 0);
+}
+
+int git_mbedtls_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 = mbedtls_stream_wrap(out, stream, host, 1)) < 0) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ return error;
+}
+
+int git_mbedtls__set_cert_location(const char *file, const char *path)
+{
+ int ret = 0;
+ char errbuf[512];
+ mbedtls_x509_crt *cacert;
+
+ GIT_ASSERT_ARG(file || path);
+
+ cacert = git__malloc(sizeof(mbedtls_x509_crt));
+ GIT_ERROR_CHECK_ALLOC(cacert);
+
+ mbedtls_x509_crt_init(cacert);
+ if (file)
+ ret = mbedtls_x509_crt_parse_file(cacert, file);
+ if (ret >= 0 && path)
+ ret = mbedtls_x509_crt_parse_path(cacert, path);
+ /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */
+ if (ret < 0) {
+ mbedtls_x509_crt_free(cacert);
+ git__free(cacert);
+ mbedtls_strerror( ret, errbuf, 512 );
+ git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf);
+ return -1;
+ }
+
+ mbedtls_x509_crt_free(git__ssl_conf->ca_chain);
+ git__free(git__ssl_conf->ca_chain);
+ mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL);
+
+ return 0;
+}
+
+#else
+
+#include "stream.h"
+
+int git_mbedtls_stream_global_init(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/src/libgit2/streams/mbedtls.h b/src/libgit2/streams/mbedtls.h
new file mode 100644
index 000000000..bcca6dd40
--- /dev/null
+++ b/src/libgit2/streams/mbedtls.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_mbedtls_h__
+#define INCLUDE_steams_mbedtls_h__
+
+#include "common.h"
+
+#include "git2/sys/stream.h"
+
+extern int git_mbedtls_stream_global_init(void);
+
+#ifdef GIT_MBEDTLS
+extern int git_mbedtls__set_cert_location(const char *file, const char *path);
+
+extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host);
+#endif
+
+#endif
diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c
new file mode 100644
index 000000000..89c96780c
--- /dev/null
+++ b/src/libgit2/streams/openssl.c
@@ -0,0 +1,747 @@
+/*
+ * 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/openssl.h"
+#include "streams/openssl_legacy.h"
+#include "streams/openssl_dynamic.h"
+
+#ifdef GIT_OPENSSL
+
+#include <ctype.h>
+
+#include "common.h"
+#include "runtime.h"
+#include "settings.h"
+#include "posix.h"
+#include "stream.h"
+#include "streams/socket.h"
+#include "netops.h"
+#include "git2/transport.h"
+#include "git2/sys/openssl.h"
+
+#ifndef GIT_WIN32
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+#endif
+
+#ifndef GIT_OPENSSL_DYNAMIC
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+# include <openssl/bio.h>
+#endif
+
+SSL_CTX *git__ssl_ctx;
+
+#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA"
+
+
+static BIO_METHOD *git_stream_bio_method;
+static int init_bio_method(void);
+
+/**
+ * This function aims to clean-up the SSL context which
+ * we allocated.
+ */
+static void shutdown_ssl(void)
+{
+ if (git_stream_bio_method) {
+ BIO_meth_free(git_stream_bio_method);
+ git_stream_bio_method = NULL;
+ }
+
+ if (git__ssl_ctx) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ }
+}
+
+#ifdef VALGRIND
+# if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC)
+
+static void *git_openssl_malloc(size_t bytes, const char *file, int line)
+{
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+ return git__calloc(1, bytes);
+}
+
+static void *git_openssl_realloc(void *mem, size_t size, const char *file, int line)
+{
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+ return git__realloc(mem, size);
+}
+
+static void git_openssl_free(void *mem, const char *file, int line)
+{
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+ git__free(mem);
+}
+# else /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */
+static void *git_openssl_malloc(size_t bytes)
+{
+ return git__calloc(1, bytes);
+}
+
+static void *git_openssl_realloc(void *mem, size_t size)
+{
+ return git__realloc(mem, size);
+}
+
+static void git_openssl_free(void *mem)
+{
+ git__free(mem);
+}
+# endif /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */
+#endif /* VALGRIND */
+
+static int openssl_init(void)
+{
+ long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+ const char *ciphers = git_libgit2__ssl_ciphers();
+#ifdef VALGRIND
+ static bool allocators_initialized = false;
+#endif
+
+ /* Older OpenSSL and MacOS OpenSSL doesn't have this */
+#ifdef SSL_OP_NO_COMPRESSION
+ ssl_opts |= SSL_OP_NO_COMPRESSION;
+#endif
+
+#ifdef VALGRIND
+ /*
+ * Swap in our own allocator functions that initialize
+ * allocated memory to avoid spurious valgrind warnings.
+ * Don't error on failure; many builds of OpenSSL do not
+ * allow you to set these functions.
+ */
+ if (!allocators_initialized) {
+ CRYPTO_set_mem_functions(git_openssl_malloc,
+ git_openssl_realloc,
+ git_openssl_free);
+ allocators_initialized = true;
+ }
+#endif
+
+ OPENSSL_init_ssl(0, NULL);
+
+ /*
+ * Load SSLv{2,3} and TLSv1 so that we can talk with servers
+ * which use the SSL hellos, which are often used for
+ * compatibility. We then disable SSL so we only allow OpenSSL
+ * to speak TLSv1 to perform the encryption itself.
+ */
+ if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method())))
+ goto error;
+
+ SSL_CTX_set_options(git__ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx))
+ goto error;
+
+ if (!ciphers)
+ ciphers = GIT_SSL_DEFAULT_CIPHERS;
+
+ if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers))
+ goto error;
+
+ if (init_bio_method() < 0)
+ goto error;
+
+ return git_runtime_shutdown_register(shutdown_ssl);
+
+error:
+ git_error_set(GIT_ERROR_NET, "could not initialize openssl: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ return -1;
+}
+
+/*
+ * When we use dynamic loading, we defer OpenSSL initialization until
+ * it's first used. `openssl_ensure_initialized` will do the work
+ * under a mutex.
+ */
+git_mutex openssl_mutex;
+bool openssl_initialized;
+
+int git_openssl_stream_global_init(void)
+{
+#ifndef GIT_OPENSSL_DYNAMIC
+ return openssl_init();
+#else
+ if (git_mutex_init(&openssl_mutex) != 0)
+ return -1;
+
+ return 0;
+#endif
+}
+
+static int openssl_ensure_initialized(void)
+{
+#ifdef GIT_OPENSSL_DYNAMIC
+ int error = 0;
+
+ if (git_mutex_lock(&openssl_mutex) != 0)
+ return -1;
+
+ if (!openssl_initialized) {
+ if ((error = git_openssl_stream_dynamic_init()) == 0)
+ error = openssl_init();
+
+ openssl_initialized = true;
+ }
+
+ error |= git_mutex_unlock(&openssl_mutex);
+ return error;
+
+#else
+ return 0;
+#endif
+}
+
+#if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC)
+int git_openssl_set_locking(void)
+{
+# ifdef GIT_THREADS
+ return 0;
+# else
+ git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads");
+ return -1;
+# endif
+}
+#endif
+
+
+static int bio_create(BIO *b)
+{
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+
+ return 1;
+}
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+ BIO_set_data(b, NULL);
+
+ return 1;
+}
+
+static int bio_read(BIO *b, char *buf, int len)
+{
+ git_stream *io = (git_stream *) BIO_get_data(b);
+
+ return (int) git_stream_read(io, buf, len);
+}
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+ git_stream *io = (git_stream *) BIO_get_data(b);
+ return (int) git_stream_write(io, buf, len, 0);
+}
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+ GIT_UNUSED(b);
+ GIT_UNUSED(num);
+ GIT_UNUSED(ptr);
+
+ if (cmd == BIO_CTRL_FLUSH)
+ return 1;
+
+ return 0;
+}
+
+static int bio_gets(BIO *b, char *buf, int len)
+{
+ GIT_UNUSED(b);
+ GIT_UNUSED(buf);
+ GIT_UNUSED(len);
+ return -1;
+}
+
+static int bio_puts(BIO *b, const char *str)
+{
+ return bio_write(b, str, strlen(str));
+}
+
+static int init_bio_method(void)
+{
+ /* Set up the BIO_METHOD we use for wrapping our own stream implementations */
+ git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream");
+ GIT_ERROR_CHECK_ALLOC(git_stream_bio_method);
+
+ BIO_meth_set_write(git_stream_bio_method, bio_write);
+ BIO_meth_set_read(git_stream_bio_method, bio_read);
+ BIO_meth_set_puts(git_stream_bio_method, bio_puts);
+ BIO_meth_set_gets(git_stream_bio_method, bio_gets);
+ BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl);
+ BIO_meth_set_create(git_stream_bio_method, bio_create);
+ BIO_meth_set_destroy(git_stream_bio_method, bio_destroy);
+
+ return 0;
+}
+
+static int ssl_set_error(SSL *ssl, int error)
+{
+ int err;
+ unsigned long e;
+
+ err = SSL_get_error(ssl, error);
+
+ GIT_ASSERT(err != SSL_ERROR_WANT_READ);
+ GIT_ASSERT(err != SSL_ERROR_WANT_WRITE);
+
+ switch (err) {
+ case SSL_ERROR_WANT_CONNECT:
+ case SSL_ERROR_WANT_ACCEPT:
+ git_error_set(GIT_ERROR_SSL, "SSL error: connection failure");
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ git_error_set(GIT_ERROR_SSL, "SSL error: x509 error");
+ break;
+ case SSL_ERROR_SYSCALL:
+ e = ERR_get_error();
+ if (e > 0) {
+ char errmsg[256];
+ ERR_error_string_n(e, errmsg, sizeof(errmsg));
+ git_error_set(GIT_ERROR_NET, "SSL error: %s", errmsg);
+ break;
+ } else if (error < 0) {
+ git_error_set(GIT_ERROR_OS, "SSL error: syscall failure");
+ break;
+ }
+ git_error_set(GIT_ERROR_SSL, "SSL error: received early EOF");
+ return GIT_EEOF;
+ break;
+ case SSL_ERROR_SSL:
+ {
+ char errmsg[256];
+ e = ERR_get_error();
+ ERR_error_string_n(e, errmsg, sizeof(errmsg));
+ git_error_set(GIT_ERROR_SSL, "SSL error: %s", errmsg);
+ break;
+ }
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ git_error_set(GIT_ERROR_SSL, "SSL error: unknown error");
+ break;
+ }
+ return -1;
+}
+
+static int ssl_teardown(SSL *ssl)
+{
+ int ret;
+
+ ret = SSL_shutdown(ssl);
+ if (ret < 0)
+ ret = ssl_set_error(ssl, ret);
+ else
+ ret = 0;
+
+ return ret;
+}
+
+static int check_host_name(const char *name, const char *host)
+{
+ if (!strcasecmp(name, host))
+ return 0;
+
+ if (gitno__match_host(name, host) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int verify_server_cert(SSL *ssl, const char *host)
+{
+ X509 *cert = NULL;
+ X509_NAME *peer_name;
+ ASN1_STRING *str;
+ unsigned char *peer_cn = NULL;
+ int matched = -1, type = GEN_DNS;
+ GENERAL_NAMES *alts;
+ struct in6_addr addr6;
+ struct in_addr addr4;
+ void *addr = NULL;
+ int i = -1, j, error = 0;
+
+ if (SSL_get_verify_result(ssl) != X509_V_OK) {
+ git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid");
+ return GIT_ECERTIFICATE;
+ }
+
+ /* Try to parse the host as an IP address to see if it is */
+ if (p_inet_pton(AF_INET, host, &addr4)) {
+ type = GEN_IPADD;
+ addr = &addr4;
+ } else {
+ if (p_inet_pton(AF_INET6, host, &addr6)) {
+ type = GEN_IPADD;
+ addr = &addr6;
+ }
+ }
+
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (!cert) {
+ error = -1;
+ git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate");
+ goto cleanup;
+ }
+
+ /* Check the alternative names */
+ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ if (alts) {
+ int num;
+
+ num = sk_GENERAL_NAME_num(alts);
+ for (i = 0; i < num && matched != 1; i++) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
+ const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5);
+ size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
+
+ /* Skip any names of a type we're not looking for */
+ if (gn->type != type)
+ continue;
+
+ if (type == GEN_DNS) {
+ /* If it contains embedded NULs, don't even try */
+ if (memchr(name, '\0', namelen))
+ continue;
+
+ if (check_host_name(name, host) < 0)
+ matched = 0;
+ else
+ matched = 1;
+ } else if (type == GEN_IPADD) {
+ /* Here name isn't so much a name but a binary representation of the IP */
+ matched = addr && !!memcmp(name, addr, namelen);
+ }
+ }
+ }
+ GENERAL_NAMES_free(alts);
+
+ if (matched == 0)
+ goto cert_fail_name;
+
+ if (matched == 1) {
+ goto cleanup;
+ }
+
+ /* If no alternative names are available, check the common name */
+ peer_name = X509_get_subject_name(cert);
+ if (peer_name == NULL)
+ goto on_error;
+
+ if (peer_name) {
+ /* Get the index of the last CN entry */
+ while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
+ i = j;
+ }
+
+ if (i < 0)
+ goto on_error;
+
+ str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
+ if (str == NULL)
+ goto on_error;
+
+ /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
+ if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
+ int size = ASN1_STRING_length(str);
+
+ if (size > 0) {
+ peer_cn = OPENSSL_malloc(size + 1);
+ GIT_ERROR_CHECK_ALLOC(peer_cn);
+ memcpy(peer_cn, ASN1_STRING_get0_data(str), size);
+ peer_cn[size] = '\0';
+ } else {
+ goto cert_fail_name;
+ }
+ } else {
+ int size = ASN1_STRING_to_UTF8(&peer_cn, str);
+ GIT_ERROR_CHECK_ALLOC(peer_cn);
+ if (memchr(peer_cn, '\0', size))
+ goto cert_fail_name;
+ }
+
+ if (check_host_name((char *)peer_cn, host) < 0)
+ goto cert_fail_name;
+
+ goto cleanup;
+
+cert_fail_name:
+ error = GIT_ECERTIFICATE;
+ git_error_set(GIT_ERROR_SSL, "hostname does not match certificate");
+ goto cleanup;
+
+on_error:
+ error = ssl_set_error(ssl, 0);
+ goto cleanup;
+
+cleanup:
+ X509_free(cert);
+ OPENSSL_free(peer_cn);
+ return error;
+}
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ int owned;
+ bool connected;
+ char *host;
+ SSL *ssl;
+ git_cert_x509 cert_info;
+} openssl_stream;
+
+static int openssl_connect(git_stream *stream)
+{
+ int ret;
+ BIO *bio;
+ openssl_stream *st = (openssl_stream *) stream;
+
+ if (st->owned && (ret = git_stream_connect(st->io)) < 0)
+ return ret;
+
+ bio = BIO_new(git_stream_bio_method);
+ GIT_ERROR_CHECK_ALLOC(bio);
+
+ BIO_set_data(bio, st->io);
+ SSL_set_bio(st->ssl, bio, bio);
+
+ /* specify the host in case SNI is needed */
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ SSL_set_tlsext_host_name(st->ssl, st->host);
+#endif
+
+ if ((ret = SSL_connect(st->ssl)) <= 0)
+ return ssl_set_error(st->ssl, ret);
+
+ st->connected = true;
+
+ return verify_server_cert(st->ssl, st->host);
+}
+
+static int openssl_certificate(git_cert **out, git_stream *stream)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ X509 *cert = SSL_get_peer_certificate(st->ssl);
+ unsigned char *guard, *encoded_cert = NULL;
+ int error, len;
+
+ /* Retrieve the length of the certificate first */
+ len = i2d_X509(cert, NULL);
+ if (len < 0) {
+ git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information");
+ error = -1;
+ goto out;
+ }
+
+ encoded_cert = git__malloc(len);
+ GIT_ERROR_CHECK_ALLOC(encoded_cert);
+ /* i2d_X509 makes 'guard' point to just after the data */
+ guard = encoded_cert;
+
+ len = i2d_X509(cert, &guard);
+ if (len < 0) {
+ git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information");
+ error = -1;
+ goto out;
+ }
+
+ st->cert_info.parent.cert_type = GIT_CERT_X509;
+ st->cert_info.data = encoded_cert;
+ st->cert_info.len = len;
+ encoded_cert = NULL;
+
+ *out = &st->cert_info.parent;
+ error = 0;
+
+out:
+ git__free(encoded_cert);
+ X509_free(cert);
+ return error;
+}
+
+static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy_opts);
+}
+
+static ssize_t openssl_write(git_stream *stream, const char *data, size_t data_len, int flags)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int ret, len = min(data_len, INT_MAX);
+
+ GIT_UNUSED(flags);
+
+ if ((ret = SSL_write(st->ssl, data, len)) <= 0)
+ return ssl_set_error(st->ssl, ret);
+
+ return ret;
+}
+
+static ssize_t openssl_read(git_stream *stream, void *data, size_t len)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int ret;
+
+ if ((ret = SSL_read(st->ssl, data, len)) <= 0)
+ return ssl_set_error(st->ssl, ret);
+
+ return ret;
+}
+
+static int openssl_close(git_stream *stream)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+ int ret;
+
+ if (st->connected && (ret = ssl_teardown(st->ssl)) < 0)
+ return -1;
+
+ st->connected = false;
+
+ return st->owned ? git_stream_close(st->io) : 0;
+}
+
+static void openssl_free(git_stream *stream)
+{
+ openssl_stream *st = (openssl_stream *) stream;
+
+ if (st->owned)
+ git_stream_free(st->io);
+
+ SSL_free(st->ssl);
+ git__free(st->host);
+ git__free(st->cert_info.data);
+ git__free(st);
+}
+
+static int openssl_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
+{
+ openssl_stream *st;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(in);
+ GIT_ASSERT_ARG(host);
+
+ st = git__calloc(1, sizeof(openssl_stream));
+ GIT_ERROR_CHECK_ALLOC(st);
+
+ st->io = in;
+ st->owned = owned;
+
+ st->ssl = SSL_new(git__ssl_ctx);
+ if (st->ssl == NULL) {
+ git_error_set(GIT_ERROR_SSL, "failed to create ssl object");
+ git__free(st);
+ return -1;
+ }
+
+ 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 = openssl_connect;
+ st->parent.certificate = openssl_certificate;
+ st->parent.set_proxy = openssl_set_proxy;
+ st->parent.read = openssl_read;
+ st->parent.write = openssl_write;
+ st->parent.close = openssl_close;
+ st->parent.free = openssl_free;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host)
+{
+ if (openssl_ensure_initialized() < 0)
+ return -1;
+
+ return openssl_stream_wrap(out, in, host, 0);
+}
+
+int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
+{
+ git_stream *stream = NULL;
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(host);
+ GIT_ASSERT_ARG(port);
+
+ if (openssl_ensure_initialized() < 0)
+ return -1;
+
+ if ((error = git_socket_stream_new(&stream, host, port)) < 0)
+ return error;
+
+ if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ return error;
+}
+
+int git_openssl__set_cert_location(const char *file, const char *path)
+{
+ if (openssl_ensure_initialized() < 0)
+ return -1;
+
+ if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) {
+ char errmsg[256];
+
+ ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg));
+ git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to load certificates: %s",
+ errmsg);
+
+ return -1;
+ }
+ return 0;
+}
+
+#else
+
+#include "stream.h"
+#include "git2/sys/openssl.h"
+
+int git_openssl_stream_global_init(void)
+{
+ return 0;
+}
+
+int git_openssl_set_locking(void)
+{
+ git_error_set(GIT_ERROR_SSL, "libgit2 was not built with OpenSSL support");
+ return -1;
+}
+
+#endif
diff --git a/src/libgit2/streams/openssl.h b/src/libgit2/streams/openssl.h
new file mode 100644
index 000000000..89fb60a82
--- /dev/null
+++ b/src/libgit2/streams/openssl.h
@@ -0,0 +1,31 @@
+/*
+ * 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_streams_openssl_h__
+#define INCLUDE_streams_openssl_h__
+
+#include "common.h"
+#include "streams/openssl_legacy.h"
+#include "streams/openssl_dynamic.h"
+
+#include "git2/sys/stream.h"
+
+extern int git_openssl_stream_global_init(void);
+
+#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC)
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+# include <openssl/bio.h>
+# endif
+
+#ifdef GIT_OPENSSL
+extern int git_openssl__set_cert_location(const char *file, const char *path);
+extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host);
+#endif
+
+#endif
diff --git a/src/libgit2/streams/openssl_dynamic.c b/src/libgit2/streams/openssl_dynamic.c
new file mode 100644
index 000000000..da16b6ed7
--- /dev/null
+++ b/src/libgit2/streams/openssl_dynamic.c
@@ -0,0 +1,309 @@
+/*
+ * 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/openssl.h"
+#include "streams/openssl_dynamic.h"
+
+#if defined(GIT_OPENSSL) && defined(GIT_OPENSSL_DYNAMIC)
+
+#include "runtime.h"
+
+#include <dlfcn.h>
+
+unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x);
+const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x);
+int (*ASN1_STRING_length)(const ASN1_STRING *x);
+int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in);
+int (*ASN1_STRING_type)(const ASN1_STRING *x);
+
+void *(*BIO_get_data)(BIO *a);
+int (*BIO_get_new_index)(void);
+int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings);
+void (*BIO_meth_free)(BIO_METHOD *biom);
+int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *));
+int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *));
+int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *));
+int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int));
+int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *));
+int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int));
+int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int));
+BIO_METHOD *(*BIO_meth_new)(int type, const char *name);
+BIO *(*BIO_new)(const BIO_METHOD *type);
+void (*BIO_set_data)(BIO *a, void *ptr);
+void (*BIO_set_init)(BIO *a, int init);
+
+void (*CRYPTO_free)(void *ptr, const char *file, int line);
+void *(*CRYPTO_malloc)(size_t num, const char *file, int line);
+int (*CRYPTO_num_locks)(void);
+void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line));
+int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem));
+int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id));
+void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val);
+
+char *(*ERR_error_string)(unsigned long e, char *buf);
+void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len);
+unsigned long (*ERR_get_error)(void);
+
+int (*SSL_connect)(SSL *ssl);
+long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg);
+void (*SSL_free)(SSL *ssl);
+int (*SSL_get_error)(SSL *ssl, int ret);
+X509 *(*SSL_get_peer_certificate)(const SSL *ssl);
+long (*SSL_get_verify_result)(const SSL *ssl);
+int (*SSL_library_init)(void);
+void (*SSL_load_error_strings)(void);
+SSL *(*SSL_new)(SSL_CTX *ctx);
+int (*SSL_read)(SSL *ssl, const void *buf, int num);
+void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio);
+int (*SSL_shutdown)(SSL *ssl);
+int (*SSL_write)(SSL *ssl, const void *buf, int num);
+
+long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg);
+void (*SSL_CTX_free)(SSL_CTX *ctx);
+SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method);
+int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str);
+int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx);
+long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options);
+void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *));
+int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath);
+
+const SSL_METHOD *(*SSLv23_method)(void);
+const SSL_METHOD *(*TLS_method)(void);
+
+ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne);
+X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc);
+int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos);
+void (*X509_free)(X509 *a);
+void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx);
+X509_NAME *(*X509_get_subject_name)(const X509 *x);
+
+int (*i2d_X509)(X509 *a, unsigned char **ppout);
+
+int (*OPENSSL_sk_num)(const void *sk);
+void *(*OPENSSL_sk_value)(const void *sk, int i);
+void (*OPENSSL_sk_free)(void *sk);
+
+int (*sk_num)(const void *sk);
+void *(*sk_value)(const void *sk, int i);
+void (*sk_free)(void *sk);
+
+void *openssl_handle;
+
+GIT_INLINE(void *) openssl_sym(int *err, const char *name, bool required)
+{
+ void *symbol;
+
+ /* if we've seen an err, noop to retain it */
+ if (*err)
+ return NULL;
+
+
+ if ((symbol = dlsym(openssl_handle, name)) == NULL && required) {
+ const char *msg = dlerror();
+ git_error_set(GIT_ERROR_SSL, "could not load ssl function '%s': %s", name, msg ? msg : "unknown error");
+ *err = -1;
+ }
+
+ return symbol;
+}
+
+static void dynamic_shutdown(void)
+{
+ dlclose(openssl_handle);
+ openssl_handle = NULL;
+}
+
+int git_openssl_stream_dynamic_init(void)
+{
+ int err = 0;
+
+ if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL &&
+ (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL) {
+ git_error_set(GIT_ERROR_SSL, "could not load ssl libraries");
+ return -1;
+ }
+
+ ASN1_STRING_data = (unsigned char *(*)(ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_data", false);
+ ASN1_STRING_get0_data = (const unsigned char *(*)(const ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_get0_data", false);
+ ASN1_STRING_length = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_length", true);
+ ASN1_STRING_to_UTF8 = (int (*)(unsigned char **, const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_to_UTF8", true);
+ ASN1_STRING_type = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_type", true);
+
+ BIO_get_data = (void *(*)(BIO *))openssl_sym(&err, "BIO_get_data", false);
+ BIO_get_new_index = (int (*)(void))openssl_sym(&err, "BIO_get_new_index", false);
+ BIO_meth_free = (void (*)(BIO_METHOD *))openssl_sym(&err, "BIO_meth_free", false);
+ BIO_meth_new = (BIO_METHOD *(*)(int, const char *))openssl_sym(&err, "BIO_meth_new", false);
+ BIO_meth_set_create = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_create", false);
+ BIO_meth_set_ctrl = (int (*)(BIO_METHOD *, long (*)(BIO *, int, long, void *)))openssl_sym(&err, "BIO_meth_set_ctrl", false);
+ BIO_meth_set_destroy = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_destroy", false);
+ BIO_meth_set_gets = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_gets", false);
+ BIO_meth_set_puts = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *)))openssl_sym(&err, "BIO_meth_set_puts", false);
+ BIO_meth_set_read = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_read", false);
+ BIO_meth_set_write = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *, int)))openssl_sym(&err, "BIO_meth_set_write", false);
+ BIO_new = (BIO *(*)(const BIO_METHOD *))openssl_sym(&err, "BIO_new", true);
+ BIO_set_data = (void (*)(BIO *a, void *))openssl_sym(&err, "BIO_set_data", false);
+ BIO_set_init = (void (*)(BIO *a, int))openssl_sym(&err, "BIO_set_init", false);
+
+ CRYPTO_free = (void (*)(void *, const char *, int))openssl_sym(&err, "CRYPTO_free", true);
+ CRYPTO_malloc = (void *(*)(size_t, const char *, int))openssl_sym(&err, "CRYPTO_malloc", true);
+ CRYPTO_num_locks = (int (*)(void))openssl_sym(&err, "CRYPTO_num_locks", false);
+ CRYPTO_set_locking_callback = (void (*)(void (*)(int, int, const char *, int)))openssl_sym(&err, "CRYPTO_set_locking_callback", false);
+ CRYPTO_set_mem_functions = (int (*)(void *(*)(size_t), void *(*)(void *, size_t), void (*f)(void *)))openssl_sym(&err, "CRYPTO_set_mem_functions", true);
+
+ CRYPTO_THREADID_set_callback = (int (*)(void (*)(CRYPTO_THREADID *)))openssl_sym(&err, "CRYPTO_THREADID_set_callback", false);
+ CRYPTO_THREADID_set_numeric = (void (*)(CRYPTO_THREADID *, unsigned long))openssl_sym(&err, "CRYPTO_THREADID_set_numeric", false);
+
+ ERR_error_string = (char *(*)(unsigned long, char *))openssl_sym(&err, "ERR_error_string", true);
+ ERR_error_string_n = (void (*)(unsigned long, char *, size_t))openssl_sym(&err, "ERR_error_string_n", true);
+ ERR_get_error = (unsigned long (*)(void))openssl_sym(&err, "ERR_get_error", true);
+
+ OPENSSL_init_ssl = (int (*)(uint64_t opts, const void *settings))openssl_sym(&err, "OPENSSL_init_ssl", false);
+ OPENSSL_sk_num = (int (*)(const void *))openssl_sym(&err, "OPENSSL_sk_num", false);
+ OPENSSL_sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "OPENSSL_sk_value", false);
+ OPENSSL_sk_free = (void (*)(void *))openssl_sym(&err, "OPENSSL_sk_free", false);
+
+ sk_num = (int (*)(const void *))openssl_sym(&err, "sk_num", false);
+ sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "sk_value", false);
+ sk_free = (void (*)(void *))openssl_sym(&err, "sk_free", false);
+
+ SSL_connect = (int (*)(SSL *))openssl_sym(&err, "SSL_connect", true);
+ SSL_ctrl = (long (*)(SSL *, int, long, void *))openssl_sym(&err, "SSL_ctrl", true);
+ SSL_get_peer_certificate = (X509 *(*)(const SSL *))openssl_sym(&err, "SSL_get_peer_certificate", true);
+ SSL_library_init = (int (*)(void))openssl_sym(&err, "SSL_library_init", false);
+ SSL_free = (void (*)(SSL *))openssl_sym(&err, "SSL_free", true);
+ SSL_get_error = (int (*)(SSL *, int))openssl_sym(&err, "SSL_get_error", true);
+ SSL_get_verify_result = (long (*)(const SSL *ssl))openssl_sym(&err, "SSL_get_verify_result", true);
+ SSL_load_error_strings = (void (*)(void))openssl_sym(&err, "SSL_load_error_strings", false);
+ SSL_new = (SSL *(*)(SSL_CTX *))openssl_sym(&err, "SSL_new", true);
+ SSL_read = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_read", true);
+ SSL_set_bio = (void (*)(SSL *, BIO *, BIO *))openssl_sym(&err, "SSL_set_bio", true);
+ SSL_shutdown = (int (*)(SSL *ssl))openssl_sym(&err, "SSL_shutdown", true);
+ SSL_write = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_write", true);
+
+ SSL_CTX_ctrl = (long (*)(SSL_CTX *, int, long, void *))openssl_sym(&err, "SSL_CTX_ctrl", true);
+ SSL_CTX_free = (void (*)(SSL_CTX *))openssl_sym(&err, "SSL_CTX_free", true);
+ SSL_CTX_new = (SSL_CTX *(*)(const SSL_METHOD *))openssl_sym(&err, "SSL_CTX_new", true);
+ SSL_CTX_set_cipher_list = (int (*)(SSL_CTX *, const char *))openssl_sym(&err, "SSL_CTX_set_cipher_list", true);
+ SSL_CTX_set_default_verify_paths = (int (*)(SSL_CTX *ctx))openssl_sym(&err, "SSL_CTX_set_default_verify_paths", true);
+ SSL_CTX_set_options = (long (*)(SSL_CTX *, long))openssl_sym(&err, "SSL_CTX_set_options", false);
+ SSL_CTX_set_verify = (void (*)(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)))openssl_sym(&err, "SSL_CTX_set_verify", true);
+ SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *))openssl_sym(&err, "SSL_CTX_load_verify_locations", true);
+
+ SSLv23_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "SSLv23_method", false);
+ TLS_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "TLS_method", false);
+
+ X509_NAME_ENTRY_get_data = (ASN1_STRING *(*)(const X509_NAME_ENTRY *))openssl_sym(&err, "X509_NAME_ENTRY_get_data", true);
+ X509_NAME_get_entry = (X509_NAME_ENTRY *(*)(X509_NAME *, int))openssl_sym(&err, "X509_NAME_get_entry", true);
+ X509_NAME_get_index_by_NID = (int (*)(X509_NAME *, int, int))openssl_sym(&err, "X509_NAME_get_index_by_NID", true);
+ X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true);
+ X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true);
+ X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true);
+
+ i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true);
+
+ if (err)
+ goto on_error;
+
+ /* Add legacy functionality */
+ if (!OPENSSL_init_ssl) {
+ OPENSSL_init_ssl = OPENSSL_init_ssl__legacy;
+
+ if (!SSL_library_init ||
+ !SSL_load_error_strings ||
+ !CRYPTO_num_locks ||
+ !CRYPTO_set_locking_callback ||
+ !CRYPTO_THREADID_set_callback ||
+ !CRYPTO_THREADID_set_numeric) {
+ git_error_set(GIT_ERROR_SSL, "could not load legacy openssl initialization functions");
+ goto on_error;
+ }
+ }
+
+ if (!SSL_CTX_set_options)
+ SSL_CTX_set_options = SSL_CTX_set_options__legacy;
+
+ if (TLS_method)
+ SSLv23_method = TLS_method;
+
+ if (!BIO_meth_new) {
+ BIO_meth_new = BIO_meth_new__legacy;
+ BIO_meth_new = BIO_meth_new__legacy;
+ BIO_meth_free = BIO_meth_free__legacy;
+ BIO_meth_set_write = BIO_meth_set_write__legacy;
+ BIO_meth_set_read = BIO_meth_set_read__legacy;
+ BIO_meth_set_puts = BIO_meth_set_puts__legacy;
+ BIO_meth_set_gets = BIO_meth_set_gets__legacy;
+ BIO_meth_set_ctrl = BIO_meth_set_ctrl__legacy;
+ BIO_meth_set_create = BIO_meth_set_create__legacy;
+ BIO_meth_set_destroy = BIO_meth_set_destroy__legacy;
+ BIO_get_new_index = BIO_get_new_index__legacy;
+ BIO_set_data = BIO_set_data__legacy;
+ BIO_set_init = BIO_set_init__legacy;
+ BIO_get_data = BIO_get_data__legacy;
+ }
+
+ if (!ASN1_STRING_get0_data) {
+ if (!ASN1_STRING_data) {
+ git_error_set(GIT_ERROR_SSL, "could not load legacy openssl string function");
+ goto on_error;
+ }
+
+ ASN1_STRING_get0_data = ASN1_STRING_get0_data__legacy;
+ }
+
+ if ((!OPENSSL_sk_num && !sk_num) ||
+ (!OPENSSL_sk_value && !sk_value) ||
+ (!OPENSSL_sk_free && !sk_free)) {
+ git_error_set(GIT_ERROR_SSL, "could not load legacy openssl stack functions");
+ goto on_error;
+ }
+
+ if (git_runtime_shutdown_register(dynamic_shutdown) != 0)
+ goto on_error;
+
+ return 0;
+
+on_error:
+ dlclose(openssl_handle);
+ return -1;
+}
+
+
+int sk_GENERAL_NAME_num(const GENERAL_NAME *sk)
+{
+ if (OPENSSL_sk_num)
+ return OPENSSL_sk_num(sk);
+ else if (sk_num)
+ return sk_num(sk);
+
+ GIT_ASSERT_WITH_RETVAL(false, 0);
+ return 0;
+}
+
+GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i)
+{
+ if (OPENSSL_sk_value)
+ return OPENSSL_sk_value(sk, i);
+ else if (sk_value)
+ return sk_value(sk, i);
+
+ GIT_ASSERT_WITH_RETVAL(false, NULL);
+ return NULL;
+}
+
+void GENERAL_NAMES_free(GENERAL_NAME *sk)
+{
+ if (OPENSSL_sk_free)
+ OPENSSL_sk_free(sk);
+ else if (sk_free)
+ sk_free(sk);
+}
+
+#endif /* GIT_OPENSSL && GIT_OPENSSL_DYNAMIC */
diff --git a/src/libgit2/streams/openssl_dynamic.h b/src/libgit2/streams/openssl_dynamic.h
new file mode 100644
index 000000000..a99691910
--- /dev/null
+++ b/src/libgit2/streams/openssl_dynamic.h
@@ -0,0 +1,348 @@
+/*
+ * 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.
+ */
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are adhered to. The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code. The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * "This product includes cryptographic software written by
+ * Eric Young (eay@cryptsoft.com)"
+ * The word 'cryptographic' can be left out if the routines from the library
+ * being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ * the apps directory (application code) you must include an acknowledgement:
+ * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publicly available version or
+ * derivative of this code cannot be changed. i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+/* ====================================================================
+ * Copyright (c) 1998-2007 The OpenSSL Project. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the OpenSSL Project
+ * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ * nor may "OpenSSL" appear in their names without prior written
+ * permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the OpenSSL Project
+ * for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com). This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com).
+ *
+ */
+/* ====================================================================
+ * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
+ * ECC cipher suite support in OpenSSL originally developed by
+ * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project.
+ */
+/* ====================================================================
+ * Copyright 2005 Nokia. All rights reserved.
+ *
+ * The portions of the attached software ("Contribution") is developed by
+ * Nokia Corporation and is licensed pursuant to the OpenSSL open source
+ * license.
+ *
+ * The Contribution, originally written by Mika Kousa and Pasi Eronen of
+ * Nokia Corporation, consists of the "PSK" (Pre-Shared Key) ciphersuites
+ * support (see RFC 4279) to OpenSSL.
+ *
+ * No patent licenses or other rights except those expressly stated in
+ * the OpenSSL open source license shall be deemed granted or received
+ * expressly, by implication, estoppel, or otherwise.
+ *
+ * No assurances are provided by Nokia that the Contribution does not
+ * infringe the patent or other intellectual property rights of any third
+ * party or that the license provides you with all the necessary rights
+ * to make use of the Contribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN
+ * ADDITION TO THE DISCLAIMERS INCLUDED IN THE LICENSE, NOKIA
+ * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY
+ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
+ * OTHERWISE.
+ */
+
+#ifndef INCLUDE_streams_openssl_dynamic_h__
+#define INCLUDE_streams_openssl_dynamic_h__
+
+#ifdef GIT_OPENSSL_DYNAMIC
+
+# define BIO_CTRL_FLUSH 11
+
+# define BIO_TYPE_SOURCE_SINK 0x0400
+
+# define CRYPTO_LOCK 1
+
+# define GEN_DNS 2
+# define GEN_IPADD 7
+
+# define NID_commonName 13
+# define NID_subject_alt_name 85
+
+# define SSL_VERIFY_NONE 0x00
+
+# define SSL_CTRL_OPTIONS 32
+# define SSL_CTRL_MODE 33
+# define SSL_CTRL_SET_TLSEXT_HOSTNAME 55
+
+# define SSL_ERROR_NONE 0
+# define SSL_ERROR_SSL 1
+# define SSL_ERROR_WANT_READ 2
+# define SSL_ERROR_WANT_WRITE 3
+# define SSL_ERROR_WANT_X509_LOOKUP 4
+# define SSL_ERROR_SYSCALL 5
+# define SSL_ERROR_ZERO_RETURN 6
+# define SSL_ERROR_WANT_CONNECT 7
+# define SSL_ERROR_WANT_ACCEPT 8
+
+# define SSL_OP_NO_COMPRESSION 0x00020000L
+# define SSL_OP_NO_SSLv2 0x01000000L
+# define SSL_OP_NO_SSLv3 0x02000000L
+
+# define SSL_MODE_AUTO_RETRY 0x00000004L
+
+# define TLSEXT_NAMETYPE_host_name 0
+
+# define V_ASN1_UTF8STRING 12
+
+# define X509_V_OK 0
+
+/* Most of the OpenSSL types are mercifully opaque, so we can treat them like `void *` */
+typedef struct bio_st BIO;
+typedef struct bio_method_st BIO_METHOD;
+typedef void bio_info_cb;
+typedef void * CRYPTO_EX_DATA;
+typedef void CRYPTO_THREADID;
+typedef void GENERAL_NAMES;
+typedef void SSL;
+typedef void SSL_CTX;
+typedef void SSL_METHOD;
+typedef void X509;
+typedef void X509_NAME;
+typedef void X509_NAME_ENTRY;
+typedef void X509_STORE_CTX;
+
+typedef struct {
+ int length;
+ int type;
+ unsigned char *data;
+ long flags;
+} ASN1_STRING;
+
+typedef struct {
+ int type;
+ union {
+ char *ptr;
+ ASN1_STRING *ia5;
+ } d;
+} GENERAL_NAME;
+
+struct bio_st {
+ BIO_METHOD *method;
+ /* bio, mode, argp, argi, argl, ret */
+ long (*callback) (struct bio_st *, int, const char *, int, long, long);
+ char *cb_arg; /* first argument for the callback */
+ int init;
+ int shutdown;
+ int flags; /* extra storage */
+ int retry_reason;
+ int num;
+ void *ptr;
+ struct bio_st *next_bio; /* used by filter BIOs */
+ struct bio_st *prev_bio; /* used by filter BIOs */
+ int references;
+ unsigned long num_read;
+ unsigned long num_write;
+ CRYPTO_EX_DATA ex_data;
+};
+
+struct bio_method_st {
+ int type;
+ const char *name;
+ int (*bwrite) (BIO *, const char *, int);
+ int (*bread) (BIO *, char *, int);
+ int (*bputs) (BIO *, const char *);
+ int (*bgets) (BIO *, char *, int);
+ long (*ctrl) (BIO *, int, long, void *);
+ int (*create) (BIO *);
+ int (*destroy) (BIO *);
+ long (*callback_ctrl) (BIO *, int, bio_info_cb *);
+};
+
+extern unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x);
+extern const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x);
+extern int (*ASN1_STRING_length)(const ASN1_STRING *x);
+extern int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in);
+extern int (*ASN1_STRING_type)(const ASN1_STRING *x);
+
+extern void *(*BIO_get_data)(BIO *a);
+extern int (*BIO_get_new_index)(void);
+extern int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings);
+extern void (*BIO_meth_free)(BIO_METHOD *biom);
+extern int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *));
+extern int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *));
+extern int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *));
+extern int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int));
+extern int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *));
+extern int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int));
+extern int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int));
+extern BIO_METHOD *(*BIO_meth_new)(int type, const char *name);
+extern BIO *(*BIO_new)(const BIO_METHOD *type);
+extern void (*BIO_set_data)(BIO *a, void *ptr);
+extern void (*BIO_set_init)(BIO *a, int init);
+
+extern void (*CRYPTO_free)(void *ptr, const char *file, int line);
+extern void *(*CRYPTO_malloc)(size_t num, const char *file, int line);
+extern int (*CRYPTO_num_locks)(void);
+extern void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line));
+extern int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem));
+extern int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id));
+extern void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val);
+
+extern char *(*ERR_error_string)(unsigned long e, char *buf);
+extern void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len);
+extern unsigned long (*ERR_get_error)(void);
+
+# define OPENSSL_malloc(num) CRYPTO_malloc(num, __FILE__, __LINE__)
+# define OPENSSL_free(addr) CRYPTO_free(addr, __FILE__, __LINE__)
+
+extern int (*SSL_connect)(SSL *ssl);
+extern long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg);
+extern void (*SSL_free)(SSL *ssl);
+extern int (*SSL_get_error)(SSL *ssl, int ret);
+extern X509 *(*SSL_get_peer_certificate)(const SSL *ssl);
+extern long (*SSL_get_verify_result)(const SSL *ssl);
+extern int (*SSL_library_init)(void);
+extern void (*SSL_load_error_strings)(void);
+extern SSL *(*SSL_new)(SSL_CTX *ctx);
+extern int (*SSL_read)(SSL *ssl, const void *buf, int num);
+extern void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio);
+extern int (*SSL_shutdown)(SSL *ssl);
+extern int (*SSL_write)(SSL *ssl, const void *buf, int num);
+
+# define SSL_set_tlsext_host_name(s, name) SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (char *)(name));
+
+extern long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg);
+extern void (*SSL_CTX_free)(SSL_CTX *ctx);
+extern SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method);
+extern int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str);
+extern int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx);
+extern long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options);
+extern void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *));
+extern int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath);
+
+# define SSL_CTX_set_mode(ctx, mode) SSL_CTX_ctrl((ctx), SSL_CTRL_MODE, (mode), NULL);
+
+extern const SSL_METHOD *(*SSLv23_method)(void);
+extern const SSL_METHOD *(*TLS_method)(void);
+
+extern ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne);
+extern X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc);
+extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos);
+extern void (*X509_free)(X509 *a);
+extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx);
+extern X509_NAME *(*X509_get_subject_name)(const X509 *x);
+
+extern int (*i2d_X509)(X509 *a, unsigned char **ppout);
+
+extern int (*OPENSSL_sk_num)(const void *sk);
+extern void *(*OPENSSL_sk_value)(const void *sk, int i);
+extern void (*OPENSSL_sk_free)(void *sk);
+
+extern int (*sk_num)(const void *sk);
+extern void *(*sk_value)(const void *sk, int i);
+extern void (*sk_free)(void *sk);
+
+extern int sk_GENERAL_NAME_num(const GENERAL_NAME *sk);
+extern GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i);
+extern void GENERAL_NAMES_free(GENERAL_NAME *sk);
+
+extern int git_openssl_stream_dynamic_init(void);
+
+#endif /* GIT_OPENSSL_DYNAMIC */
+
+#endif
diff --git a/src/libgit2/streams/openssl_legacy.c b/src/libgit2/streams/openssl_legacy.c
new file mode 100644
index 000000000..e61e6efbb
--- /dev/null
+++ b/src/libgit2/streams/openssl_legacy.c
@@ -0,0 +1,203 @@
+/*
+ * 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/openssl.h"
+#include "streams/openssl_legacy.h"
+
+#include "runtime.h"
+#include "git2/sys/openssl.h"
+
+#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC)
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+# include <openssl/bio.h>
+#endif
+
+#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC)
+
+/*
+ * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it
+ * which do not exist in previous versions. We define these inline functions so
+ * we can program against the interface instead of littering the implementation
+ * with ifdefs. We do the same for OPENSSL_init_ssl.
+ */
+
+int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings)
+{
+ GIT_UNUSED(opts);
+ GIT_UNUSED(settings);
+ SSL_load_error_strings();
+ SSL_library_init();
+ return 0;
+}
+
+BIO_METHOD *BIO_meth_new__legacy(int type, const char *name)
+{
+ BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD));
+ if (!meth) {
+ return NULL;
+ }
+
+ meth->type = type;
+ meth->name = name;
+
+ return meth;
+}
+
+void BIO_meth_free__legacy(BIO_METHOD *biom)
+{
+ git__free(biom);
+}
+
+int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int))
+{
+ biom->bwrite = write;
+ return 1;
+}
+
+int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int))
+{
+ biom->bread = read;
+ return 1;
+}
+
+int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *))
+{
+ biom->bputs = puts;
+ return 1;
+}
+
+int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int))
+
+{
+ biom->bgets = gets;
+ return 1;
+}
+
+int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *))
+{
+ biom->ctrl = ctrl;
+ return 1;
+}
+
+int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *))
+{
+ biom->create = create;
+ return 1;
+}
+
+int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *))
+{
+ biom->destroy = destroy;
+ return 1;
+}
+
+int BIO_get_new_index__legacy(void)
+{
+ /* This exists as of 1.1 so before we'd just have 0 */
+ return 0;
+}
+
+void BIO_set_init__legacy(BIO *b, int init)
+{
+ b->init = init;
+}
+
+void BIO_set_data__legacy(BIO *a, void *ptr)
+{
+ a->ptr = ptr;
+}
+
+void *BIO_get_data__legacy(BIO *a)
+{
+ return a->ptr;
+}
+
+const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x)
+{
+ return ASN1_STRING_data((ASN1_STRING *)x);
+}
+
+long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op)
+{
+ return SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, NULL);
+}
+
+# if defined(GIT_THREADS)
+static git_mutex *openssl_locks;
+
+static void openssl_locking_function(int mode, int n, const char *file, int line)
+{
+ int lock;
+
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+ lock = mode & CRYPTO_LOCK;
+
+ if (lock)
+ (void)git_mutex_lock(&openssl_locks[n]);
+ else
+ git_mutex_unlock(&openssl_locks[n]);
+}
+
+static void shutdown_ssl_locking(void)
+{
+ int num_locks, i;
+
+ num_locks = CRYPTO_num_locks();
+ CRYPTO_set_locking_callback(NULL);
+
+ for (i = 0; i < num_locks; ++i)
+ git_mutex_free(&openssl_locks[i]);
+ git__free(openssl_locks);
+}
+
+static void threadid_cb(CRYPTO_THREADID *threadid)
+{
+ GIT_UNUSED(threadid);
+ CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid());
+}
+
+int git_openssl_set_locking(void)
+{
+ int num_locks, i;
+
+#ifndef GIT_THREADS
+ git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads");
+ return -1;
+#endif
+
+#ifdef GIT_OPENSSL_DYNAMIC
+ /*
+ * This function is required on legacy versions of OpenSSL; when building
+ * with dynamically-loaded OpenSSL, we detect whether we loaded it or not.
+ */
+ if (!CRYPTO_set_locking_callback)
+ return 0;
+#endif
+
+ CRYPTO_THREADID_set_callback(threadid_cb);
+
+ num_locks = CRYPTO_num_locks();
+ openssl_locks = git__calloc(num_locks, sizeof(git_mutex));
+ GIT_ERROR_CHECK_ALLOC(openssl_locks);
+
+ for (i = 0; i < num_locks; i++) {
+ if (git_mutex_init(&openssl_locks[i]) != 0) {
+ git_error_set(GIT_ERROR_SSL, "failed to initialize openssl locks");
+ return -1;
+ }
+ }
+
+ CRYPTO_set_locking_callback(openssl_locking_function);
+ return git_runtime_shutdown_register(shutdown_ssl_locking);
+}
+#endif /* GIT_THREADS */
+
+#endif /* GIT_OPENSSL_LEGACY || GIT_OPENSSL_DYNAMIC */
diff --git a/src/libgit2/streams/openssl_legacy.h b/src/libgit2/streams/openssl_legacy.h
new file mode 100644
index 000000000..e6dae9572
--- /dev/null
+++ b/src/libgit2/streams/openssl_legacy.h
@@ -0,0 +1,63 @@
+/*
+ * 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_streams_openssl_legacy_h__
+#define INCLUDE_streams_openssl_legacy_h__
+
+#include "streams/openssl_dynamic.h"
+
+#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC)
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+# include <openssl/bio.h>
+
+# if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
+# define GIT_OPENSSL_LEGACY
+# endif
+#endif
+
+#if defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC)
+# define OPENSSL_init_ssl OPENSSL_init_ssl__legacy
+# define BIO_meth_new BIO_meth_new__legacy
+# define BIO_meth_free BIO_meth_free__legacy
+# define BIO_meth_set_write BIO_meth_set_write__legacy
+# define BIO_meth_set_read BIO_meth_set_read__legacy
+# define BIO_meth_set_puts BIO_meth_set_puts__legacy
+# define BIO_meth_set_gets BIO_meth_set_gets__legacy
+# define BIO_meth_set_ctrl BIO_meth_set_ctrl__legacy
+# define BIO_meth_set_create BIO_meth_set_create__legacy
+# define BIO_meth_set_destroy BIO_meth_set_destroy__legacy
+# define BIO_get_new_index BIO_get_new_index__legacy
+# define BIO_set_data BIO_set_data__legacy
+# define BIO_set_init BIO_set_init__legacy
+# define BIO_get_data BIO_get_data__legacy
+# define ASN1_STRING_get0_data ASN1_STRING_get0_data__legacy
+#endif
+
+#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC)
+
+extern int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings);
+extern BIO_METHOD *BIO_meth_new__legacy(int type, const char *name);
+extern void BIO_meth_free__legacy(BIO_METHOD *biom);
+extern int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int));
+extern int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int));
+extern int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *));
+extern int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int));
+extern int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *));
+extern int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *));
+extern int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *));
+extern int BIO_get_new_index__legacy(void);
+extern void BIO_set_data__legacy(BIO *a, void *ptr);
+extern void BIO_set_init__legacy(BIO *b, int init);
+extern void *BIO_get_data__legacy(BIO *a);
+extern const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x);
+extern long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op);
+
+#endif
+
+#endif
diff --git a/src/libgit2/streams/registry.c b/src/libgit2/streams/registry.c
new file mode 100644
index 000000000..e60e1cd63
--- /dev/null
+++ b/src/libgit2/streams/registry.c
@@ -0,0 +1,119 @@
+/*
+ * 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 "common.h"
+
+#include "streams/registry.h"
+
+#include "runtime.h"
+#include "streams/tls.h"
+#include "streams/mbedtls.h"
+#include "streams/openssl.h"
+#include "streams/stransport.h"
+
+struct stream_registry {
+ git_rwlock lock;
+ git_stream_registration callbacks;
+ git_stream_registration tls_callbacks;
+};
+
+static struct stream_registry stream_registry;
+
+static void shutdown_stream_registry(void)
+{
+ git_rwlock_free(&stream_registry.lock);
+}
+
+int git_stream_registry_global_init(void)
+{
+ if (git_rwlock_init(&stream_registry.lock) < 0)
+ return -1;
+
+ return git_runtime_shutdown_register(shutdown_stream_registry);
+}
+
+GIT_INLINE(void) stream_registration_cpy(
+ git_stream_registration *target,
+ git_stream_registration *src)
+{
+ if (src)
+ memcpy(target, src, sizeof(git_stream_registration));
+ else
+ memset(target, 0, sizeof(git_stream_registration));
+}
+
+int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type)
+{
+ git_stream_registration *target;
+ int error = GIT_ENOTFOUND;
+
+ GIT_ASSERT_ARG(out);
+
+ switch(type) {
+ case GIT_STREAM_STANDARD:
+ target = &stream_registry.callbacks;
+ break;
+ case GIT_STREAM_TLS:
+ target = &stream_registry.tls_callbacks;
+ break;
+ default:
+ git_error_set(GIT_ERROR_INVALID, "invalid stream type");
+ return -1;
+ }
+
+ if (git_rwlock_rdlock(&stream_registry.lock) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to lock stream registry");
+ return -1;
+ }
+
+ if (target->init) {
+ stream_registration_cpy(out, target);
+ error = 0;
+ }
+
+ git_rwlock_rdunlock(&stream_registry.lock);
+ return error;
+}
+
+int git_stream_register(git_stream_t type, git_stream_registration *registration)
+{
+ GIT_ASSERT(!registration || registration->init);
+
+ GIT_ERROR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration");
+
+ if (git_rwlock_wrlock(&stream_registry.lock) < 0) {
+ git_error_set(GIT_ERROR_OS, "failed to lock stream registry");
+ return -1;
+ }
+
+ if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD)
+ stream_registration_cpy(&stream_registry.callbacks, registration);
+
+ if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS)
+ stream_registration_cpy(&stream_registry.tls_callbacks, registration);
+
+ git_rwlock_wrunlock(&stream_registry.lock);
+ return 0;
+}
+
+#ifndef GIT_DEPRECATE_HARD
+int git_stream_register_tls(
+ int GIT_CALLBACK(ctor)(git_stream **out, const char *host, const char *port))
+{
+ git_stream_registration registration = {0};
+
+ if (ctor) {
+ registration.version = GIT_STREAM_VERSION;
+ registration.init = ctor;
+ registration.wrap = NULL;
+
+ return git_stream_register(GIT_STREAM_TLS, &registration);
+ } else {
+ return git_stream_register(GIT_STREAM_TLS, NULL);
+ }
+}
+#endif
diff --git a/src/libgit2/streams/registry.h b/src/libgit2/streams/registry.h
new file mode 100644
index 000000000..adc2b8bdf
--- /dev/null
+++ b/src/libgit2/streams/registry.h
@@ -0,0 +1,19 @@
+/*
+ * 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_streams_registry_h__
+#define INCLUDE_streams_registry_h__
+
+#include "common.h"
+#include "git2/sys/stream.h"
+
+/** Configure stream registry. */
+int git_stream_registry_global_init(void);
+
+/** Lookup a stream registration. */
+extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type);
+
+#endif
diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c
new file mode 100644
index 000000000..9415fe892
--- /dev/null
+++ b/src/libgit2/streams/socket.c
@@ -0,0 +1,239 @@
+/*
+ * 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/socket.h"
+
+#include "posix.h"
+#include "netops.h"
+#include "registry.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>
+#else
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# ifdef _MSC_VER
+# pragma comment(lib, "ws2_32")
+# endif
+#endif
+
+#ifdef GIT_WIN32
+static void net_set_error(const char *str)
+{
+ int error = WSAGetLastError();
+ char * win32_error = git_win32_get_error_message(error);
+
+ if (win32_error) {
+ git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error);
+ git__free(win32_error);
+ } else {
+ git_error_set(GIT_ERROR_NET, "%s", str);
+ }
+}
+#else
+static void net_set_error(const char *str)
+{
+ git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno));
+}
+#endif
+
+static int close_socket(GIT_SOCKET s)
+{
+ if (s == INVALID_SOCKET)
+ return 0;
+
+#ifdef GIT_WIN32
+ if (SOCKET_ERROR == closesocket(s))
+ return -1;
+
+ if (0 != WSACleanup()) {
+ git_error_set(GIT_ERROR_OS, "winsock cleanup failed");
+ return -1;
+ }
+
+ return 0;
+#else
+ return close(s);
+#endif
+
+}
+
+static int socket_connect(git_stream *stream)
+{
+ struct addrinfo *info = NULL, *p;
+ struct addrinfo hints;
+ git_socket_stream *st = (git_socket_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;
+
+ if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) {
+ git_error_set(GIT_ERROR_NET,
+ "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret));
+ return -1;
+ }
+
+ for (p = info; p != NULL; p = p->ai_next) {
+ s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol);
+
+ if (s == INVALID_SOCKET)
+ continue;
+
+ if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
+ break;
+
+ /* If we can't connect, try the next one */
+ close_socket(s);
+ s = INVALID_SOCKET;
+ }
+
+ /* Oops, we couldn't connect to any address */
+ if (s == INVALID_SOCKET && p == NULL) {
+ git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host);
+ p_freeaddrinfo(info);
+ return -1;
+ }
+
+ st->s = s;
+ p_freeaddrinfo(info);
+ return 0;
+}
+
+static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ git_socket_stream *st = (git_socket_stream *) stream;
+ ssize_t written;
+
+ errno = 0;
+
+ if ((written = p_send(st->s, data, len, flags)) < 0) {
+ net_set_error("error sending data");
+ return -1;
+ }
+
+ return written;
+}
+
+static ssize_t socket_read(git_stream *stream, void *data, size_t len)
+{
+ ssize_t ret;
+ git_socket_stream *st = (git_socket_stream *) stream;
+
+ if ((ret = p_recv(st->s, data, len, 0)) < 0)
+ net_set_error("error receiving socket data");
+
+ return ret;
+}
+
+static int socket_close(git_stream *stream)
+{
+ git_socket_stream *st = (git_socket_stream *) stream;
+ int error;
+
+ error = close_socket(st->s);
+ st->s = INVALID_SOCKET;
+
+ return error;
+}
+
+static void socket_free(git_stream *stream)
+{
+ git_socket_stream *st = (git_socket_stream *) stream;
+
+ git__free(st->host);
+ git__free(st->port);
+ git__free(st);
+}
+
+static int default_socket_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port)
+{
+ git_socket_stream *st;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(host);
+ GIT_ASSERT_ARG(port);
+
+ st = git__calloc(1, sizeof(git_socket_stream));
+ GIT_ERROR_CHECK_ALLOC(st);
+
+ st->host = git__strdup(host);
+ GIT_ERROR_CHECK_ALLOC(st->host);
+
+ if (port) {
+ st->port = git__strdup(port);
+ GIT_ERROR_CHECK_ALLOC(st->port);
+ }
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.connect = socket_connect;
+ st->parent.write = socket_write;
+ st->parent.read = socket_read;
+ st->parent.close = socket_close;
+ st->parent.free = socket_free;
+ st->s = INVALID_SOCKET;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+int git_socket_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port)
+{
+ int (*init)(git_stream **, const char *, const char *) = NULL;
+ git_stream_registration custom = {0};
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(host);
+ GIT_ASSERT_ARG(port);
+
+ if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0)
+ init = custom.init;
+ else if (error == GIT_ENOTFOUND)
+ init = default_socket_stream_new;
+ else
+ return error;
+
+ if (!init) {
+ git_error_set(GIT_ERROR_NET, "there is no socket stream available");
+ return -1;
+ }
+
+ return init(out, host, port);
+}
diff --git a/src/libgit2/streams/socket.h b/src/libgit2/streams/socket.h
new file mode 100644
index 000000000..3235f3167
--- /dev/null
+++ b/src/libgit2/streams/socket.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_streams_socket_h__
+#define INCLUDE_streams_socket_h__
+
+#include "common.h"
+
+#include "netops.h"
+
+typedef struct {
+ git_stream parent;
+ char *host;
+ char *port;
+ GIT_SOCKET s;
+} git_socket_stream;
+
+extern int git_socket_stream_new(git_stream **out, const char *host, const char *port);
+
+#endif
diff --git a/src/libgit2/streams/stransport.c b/src/libgit2/streams/stransport.c
new file mode 100644
index 000000000..3f31d2541
--- /dev/null
+++ b/src/libgit2/streams/stransport.c
@@ -0,0 +1,326 @@
+/*
+ * 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/stransport.h"
+
+#ifdef GIT_SECURE_TRANSPORT
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/SecureTransport.h>
+#include <Security/SecCertificate.h>
+
+#include "git2/transport.h"
+
+#include "streams/socket.h"
+
+static int stransport_error(OSStatus ret)
+{
+ CFStringRef message;
+
+ if (ret == noErr || ret == errSSLClosedGraceful) {
+ git_error_clear();
+ return 0;
+ }
+
+#if !TARGET_OS_IPHONE
+ message = SecCopyErrorMessageString(ret, NULL);
+ GIT_ERROR_CHECK_ALLOC(message);
+
+ git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
+ CFRelease(message);
+#else
+ git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
+ GIT_UNUSED(message);
+#endif
+
+ return -1;
+}
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ int owned;
+ SSLContextRef ctx;
+ CFDataRef der_data;
+ git_cert_x509 cert_info;
+} stransport_stream;
+
+static int stransport_connect(git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ int error;
+ SecTrustRef trust = NULL;
+ SecTrustResultType sec_res;
+ OSStatus ret;
+
+ if (st->owned && (error = git_stream_connect(st->io)) < 0)
+ return error;
+
+ ret = SSLHandshake(st->ctx);
+ if (ret != errSSLServerAuthCompleted) {
+ git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret);
+ return -1;
+ }
+
+ if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
+ goto on_error;
+
+ if (!trust)
+ return GIT_ECERTIFICATE;
+
+ if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr)
+ goto on_error;
+
+ CFRelease(trust);
+
+ if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) {
+ git_error_set(GIT_ERROR_SSL, "internal security trust error");
+ return -1;
+ }
+
+ if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure ||
+ sec_res == kSecTrustResultFatalTrustFailure) {
+ git_error_set(GIT_ERROR_SSL, "untrusted connection error");
+ return GIT_ECERTIFICATE;
+ }
+
+ return 0;
+
+on_error:
+ if (trust)
+ CFRelease(trust);
+
+ return stransport_error(ret);
+}
+
+static int stransport_certificate(git_cert **out, git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ SecTrustRef trust = NULL;
+ SecCertificateRef sec_cert;
+ OSStatus ret;
+
+ if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
+ return stransport_error(ret);
+
+ sec_cert = SecTrustGetCertificateAtIndex(trust, 0);
+ st->der_data = SecCertificateCopyData(sec_cert);
+ CFRelease(trust);
+
+ if (st->der_data == NULL) {
+ git_error_set(GIT_ERROR_SSL, "retrieved invalid certificate data");
+ return -1;
+ }
+
+ st->cert_info.parent.cert_type = GIT_CERT_X509;
+ st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data);
+ st->cert_info.len = CFDataGetLength(st->der_data);
+
+ *out = (git_cert *)&st->cert_info;
+ return 0;
+}
+
+static int stransport_set_proxy(
+ git_stream *stream,
+ const git_proxy_options *proxy_opts)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+
+ return git_stream_set_proxy(st->io, proxy_opts);
+}
+
+/*
+ * Contrary to typical network IO callbacks, Secure Transport write callback is
+ * expected to write *all* passed data, not just as much as it can, and any
+ * other case would be considered a failure.
+ *
+ * This behavior is actually not specified in the Apple documentation, but is
+ * required for things to work correctly (and incidentally, that's also how
+ * Apple implements it in its projects at opensource.apple.com).
+ *
+ * Libgit2 streams happen to already have this very behavior so this is just
+ * passthrough.
+ */
+static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ git_stream *io = (git_stream *) conn;
+
+ if (git_stream__write_full(io, data, *len, 0) < 0)
+ return -36; /* "ioErr" from MacErrors.h which is not available on iOS */
+
+ return noErr;
+}
+
+static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ size_t data_len, processed;
+ OSStatus ret;
+
+ GIT_UNUSED(flags);
+
+ data_len = min(len, SSIZE_MAX);
+ if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr)
+ return stransport_error(ret);
+
+ GIT_ASSERT(processed < SSIZE_MAX);
+ return (ssize_t)processed;
+}
+
+/*
+ * Contrary to typical network IO callbacks, Secure Transport read callback is
+ * expected to read *exactly* the requested number of bytes, not just as much
+ * as it can, and any other case would be considered a failure.
+ *
+ * This behavior is actually not specified in the Apple documentation, but is
+ * required for things to work correctly (and incidentally, that's also how
+ * Apple implements it in its projects at opensource.apple.com).
+ */
+static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len)
+{
+ git_stream *io = (git_stream *) conn;
+ OSStatus error = noErr;
+ size_t off = 0;
+ ssize_t ret;
+
+ do {
+ ret = git_stream_read(io, data + off, *len - off);
+ if (ret < 0) {
+ error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */
+ break;
+ }
+ if (ret == 0) {
+ error = errSSLClosedGraceful;
+ break;
+ }
+
+ off += ret;
+ } while (off < *len);
+
+ *len = off;
+ return error;
+}
+
+static ssize_t stransport_read(git_stream *stream, void *data, size_t len)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ size_t processed;
+ OSStatus ret;
+
+ if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr)
+ return stransport_error(ret);
+
+ return processed;
+}
+
+static int stransport_close(git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+ OSStatus ret;
+
+ ret = SSLClose(st->ctx);
+ if (ret != noErr && ret != errSSLClosedGraceful)
+ return stransport_error(ret);
+
+ return st->owned ? git_stream_close(st->io) : 0;
+}
+
+static void stransport_free(git_stream *stream)
+{
+ stransport_stream *st = (stransport_stream *) stream;
+
+ if (st->owned)
+ git_stream_free(st->io);
+
+ CFRelease(st->ctx);
+ if (st->der_data)
+ CFRelease(st->der_data);
+ git__free(st);
+}
+
+static int stransport_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
+{
+ stransport_stream *st;
+ OSStatus ret;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(in);
+ GIT_ASSERT_ARG(host);
+
+ st = git__calloc(1, sizeof(stransport_stream));
+ GIT_ERROR_CHECK_ALLOC(st);
+
+ st->io = in;
+ st->owned = owned;
+
+ st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!st->ctx) {
+ git_error_set(GIT_ERROR_NET, "failed to create SSL context");
+ git__free(st);
+ return -1;
+ }
+
+ if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr ||
+ (ret = SSLSetConnection(st->ctx, st->io)) != noErr ||
+ (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr ||
+ (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr ||
+ (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr ||
+ (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) {
+ CFRelease(st->ctx);
+ git__free(st);
+ return stransport_error(ret);
+ }
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.encrypted = 1;
+ st->parent.proxy_support = git_stream_supports_proxy(st->io);
+ st->parent.connect = stransport_connect;
+ st->parent.certificate = stransport_certificate;
+ st->parent.set_proxy = stransport_set_proxy;
+ st->parent.read = stransport_read;
+ st->parent.write = stransport_write;
+ st->parent.close = stransport_close;
+ st->parent.free = stransport_free;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+int git_stransport_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host)
+{
+ return stransport_wrap(out, in, host, 0);
+}
+
+int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
+{
+ git_stream *stream = NULL;
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(host);
+
+ error = git_socket_stream_new(&stream, host, port);
+
+ if (!error)
+ error = stransport_wrap(out, stream, host, 1);
+
+ if (error < 0 && stream) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ return error;
+}
+
+#endif
diff --git a/src/libgit2/streams/stransport.h b/src/libgit2/streams/stransport.h
new file mode 100644
index 000000000..1026e204b
--- /dev/null
+++ b/src/libgit2/streams/stransport.h
@@ -0,0 +1,21 @@
+/*
+ * 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_streams_stransport_h__
+#define INCLUDE_streams_stransport_h__
+
+#include "common.h"
+
+#include "git2/sys/stream.h"
+
+#ifdef GIT_SECURE_TRANSPORT
+
+extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_stransport_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
new file mode 100644
index 000000000..e063a33f9
--- /dev/null
+++ b/src/libgit2/streams/tls.c
@@ -0,0 +1,75 @@
+/*
+ * 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 "git2/errors.h"
+
+#include "common.h"
+#include "streams/registry.h"
+#include "streams/tls.h"
+#include "streams/mbedtls.h"
+#include "streams/openssl.h"
+#include "streams/stransport.h"
+
+int git_tls_stream_new(git_stream **out, const char *host, const char *port)
+{
+ int (*init)(git_stream **, const char *, const char *) = NULL;
+ git_stream_registration custom = {0};
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(host);
+ GIT_ASSERT_ARG(port);
+
+ if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) {
+ init = custom.init;
+ } else if (error == GIT_ENOTFOUND) {
+#ifdef GIT_SECURE_TRANSPORT
+ init = git_stransport_stream_new;
+#elif defined(GIT_OPENSSL)
+ init = git_openssl_stream_new;
+#elif defined(GIT_MBEDTLS)
+ init = git_mbedtls_stream_new;
+#endif
+ } else {
+ return error;
+ }
+
+ if (!init) {
+ git_error_set(GIT_ERROR_SSL, "there is no TLS stream available");
+ return -1;
+ }
+
+ return init(out, host, port);
+}
+
+int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host)
+{
+ int (*wrap)(git_stream **, git_stream *, const char *) = NULL;
+ git_stream_registration custom = {0};
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(in);
+
+ if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) {
+ wrap = custom.wrap;
+ } else {
+#ifdef GIT_SECURE_TRANSPORT
+ wrap = git_stransport_stream_wrap;
+#elif defined(GIT_OPENSSL)
+ wrap = git_openssl_stream_wrap;
+#elif defined(GIT_MBEDTLS)
+ wrap = git_mbedtls_stream_wrap;
+#endif
+ }
+
+ if (!wrap) {
+ git_error_set(GIT_ERROR_SSL, "there is no TLS stream available");
+ return -1;
+ }
+
+ return wrap(out, in, host);
+}
diff --git a/src/libgit2/streams/tls.h b/src/libgit2/streams/tls.h
new file mode 100644
index 000000000..465a6ea89
--- /dev/null
+++ b/src/libgit2/streams/tls.h
@@ -0,0 +1,31 @@
+/*
+ * 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_streams_tls_h__
+#define INCLUDE_streams_tls_h__
+
+#include "common.h"
+
+#include "git2/sys/stream.h"
+
+/**
+ * Create a TLS stream with the most appropriate backend available for
+ * the current platform, whether that's SecureTransport on macOS,
+ * OpenSSL or mbedTLS on other Unixes, or something else entirely.
+ */
+extern int git_tls_stream_new(git_stream **out, const char *host, const char *port);
+
+/**
+ * Create a TLS stream on top of an existing insecure stream, using
+ * the most appropriate backend available for the current platform.
+ *
+ * This allows us to create a CONNECT stream on top of a proxy;
+ * using SecureTransport on macOS, OpenSSL or mbedTLS on other
+ * Unixes, or something else entirely.
+ */
+extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host);
+
+#endif