Commit 43b592ac by Edward Thomson

tls: introduce a wrap function

Introduce `git_tls_stream_wrap` which will take an existing `stream`
with an already connected socket and begin speaking TLS on top of it.
This is useful if you've built a connection to a proxy server and you
wish to begin CONNECT over it to tunnel a TLS connection.

Also update the pluggable TLS stream layer so that it can accept a
registration structure that provides an `init` and `wrap` function,
instead of a single initialization function.
parent 6ba3e6af
......@@ -40,18 +40,48 @@ typedef struct git_stream {
void (*free)(struct git_stream *);
} git_stream;
typedef int (*git_stream_cb)(git_stream **out, const char *host, const char *port);
typedef struct {
/** The `version` field should be set to `GIT_STREAM_VERSION`. */
int version;
/**
* Called to create a new TLS connection to a given host.
*
* @param out The created TLS stream
* @param host The hostname to connect to; may be a hostname or
* IP address
* @param port The port to connect to; may be a port number or
* service name
* @return 0 or an error code
*/
int (*init)(git_stream **out, const char *host, const char *port);
/**
* Called to create a new TLS connection on top of the given
* stream. May be used to proxy a TLS stream over a CONNECT
* session.
*
* @param out The created TLS stream
* @param in An existing stream to add TLS to
* @param host The hostname that the stream is connected to,
* for certificate validation
* @return 0 or an error code
*/
int (*wrap)(git_stream **out, git_stream *in, const char *host);
} git_stream_registration;
/**
* Register a TLS stream constructor for the library to use
* Register TLS stream constructors for the library to use
*
* If a constructor is already set, it will be overwritten. Pass
* `NULL` in order to deregister the current constructor.
* If a registration structure is already set, it will be overwritten.
* Pass `NULL` in order to deregister the current constructor and return
* to the system defaults.
*
* @param ctor the constructor to use
* @param registration the registration data
* @return 0 or an error code
*/
GIT_EXTERN(int) git_stream_register_tls(git_stream_cb ctor);
GIT_EXTERN(int) git_stream_register_tls(
git_stream_registration *registration);
GIT_END_DECL
......
......@@ -12,6 +12,7 @@
#include "sysdir.h"
#include "filter.h"
#include "merge_driver.h"
#include "streams/tls.h"
#include "streams/curl.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
......@@ -67,6 +68,7 @@ static int init_common(void)
(ret = git_filter_global_init()) == 0 &&
(ret = git_merge_driver_global_init()) == 0 &&
(ret = git_transport_ssh_global_init()) == 0 &&
(ret = git_tls_stream_global_init()) == 0 &&
(ret = git_openssl_stream_global_init()) == 0 &&
(ret = git_curl_stream_global_init()) == 0 &&
(ret = git_mbedtls_stream_global_init()) == 0)
......
......@@ -242,6 +242,7 @@ static int verify_server_cert(mbedtls_ssl_context *ssl)
typedef struct {
git_stream parent;
git_stream *io;
int owned;
bool connected;
char *host;
mbedtls_ssl_context *ssl;
......@@ -254,7 +255,7 @@ int mbedtls_connect(git_stream *stream)
int ret;
mbedtls_stream *st = (mbedtls_stream *) stream;
if ((ret = git_stream_connect(st->io)) < 0)
if (st->owned && (ret = git_stream_connect(st->io)) < 0)
return ret;
st->connected = true;
......@@ -345,37 +346,37 @@ int mbedtls_stream_close(git_stream *stream)
st->connected = false;
return git_stream_close(st->io);
return st->owned ? git_stream_close(st->io) : 0;
}
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);
git_stream_free(st->io);
mbedtls_ssl_free(st->ssl);
git__free(st->ssl);
git__free(st);
}
int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
static int mbedtls_stream_wrap(
git_stream **out,
git_stream *in,
const char *host,
int owned)
{
int error;
mbedtls_stream *st;
int error;
st = git__calloc(1, sizeof(mbedtls_stream));
GITERR_CHECK_ALLOC(st);
#ifdef GIT_CURL
error = git_curl_stream_new(&st->io, host, port);
#else
error = git_socket_stream_new(&st->io, host, port);
#endif
if (error < 0)
goto out_err;
st->io = in;
st->owned = owned;
st->ssl = git__malloc(sizeof(mbedtls_ssl_context));
GITERR_CHECK_ALLOC(st->ssl);
......@@ -405,12 +406,48 @@ int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port)
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;
assert(out && host && port);
#ifdef GIT_CURL
error = git_curl_stream_new(&stream, host, port);
#else
error = git_socket_stream_new(&stream, host, port);
#endif
if (error < 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 *path, int is_dir)
{
int ret = 0;
......
......@@ -14,6 +14,7 @@
extern int git_mbedtls_stream_global_init(void);
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);
extern int git_mbedtls__set_cert_location(const char *path, int is_dir);
......
......@@ -569,6 +569,7 @@ cleanup:
typedef struct {
git_stream parent;
git_stream *io;
int owned;
bool connected;
char *host;
SSL *ssl;
......@@ -583,7 +584,7 @@ int openssl_connect(git_stream *stream)
BIO *bio;
openssl_stream *st = (openssl_stream *) stream;
if ((ret = git_stream_connect(st->io)) < 0)
if (st->owned && (ret = git_stream_connect(st->io)) < 0)
return ret;
bio = BIO_new(git_stream_bio_method);
......@@ -682,43 +683,43 @@ int openssl_close(git_stream *stream)
st->connected = false;
return git_stream_close(st->io);
return st->owned ? git_stream_close(st->io) : 0;
}
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_stream_free(st->io);
git__free(st);
}
int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
static int openssl_stream_wrap(
git_stream **out,
git_stream *in,
const char *host,
int owned)
{
int error;
openssl_stream *st;
assert(out && in && host);
st = git__calloc(1, sizeof(openssl_stream));
GITERR_CHECK_ALLOC(st);
st->io = NULL;
#ifdef GIT_CURL
error = git_curl_stream_new(&st->io, host, port);
#else
error = git_socket_stream_new(&st->io, host, port);
#endif
if (error < 0)
goto out_err;
st->io = in;
st->owned = owned;
st->ssl = SSL_new(git__ssl_ctx);
if (st->ssl == NULL) {
giterr_set(GITERR_SSL, "failed to create ssl object");
error = -1;
goto out_err;
git__free(st);
return -1;
}
st->host = git__strdup(host);
......@@ -737,10 +738,33 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
*out = (git_stream *) st;
return 0;
}
out_err:
git_stream_free(st->io);
git__free(st);
int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host)
{
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;
assert(out && host && port);
#ifdef GIT_CURL
error = git_curl_stream_new(&stream, host, port);
#else
error = git_socket_stream_new(&stream, host, port);
#endif
if (error < 0)
return error;
if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) {
git_stream_close(stream);
git_stream_free(stream);
}
return error;
}
......
......@@ -14,6 +14,7 @@
extern int git_openssl_stream_global_init(void);
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);
extern int git_openssl__set_cert_location(const char *file, const char *path);
......
......@@ -34,8 +34,8 @@ static int stransport_error(OSStatus ret)
giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
CFRelease(message);
#else
giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
GIT_UNUSED(message);
giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
GIT_UNUSED(message);
#endif
return -1;
......@@ -44,6 +44,7 @@ static int stransport_error(OSStatus ret)
typedef struct {
git_stream parent;
git_stream *io;
int owned;
SSLContextRef ctx;
CFDataRef der_data;
git_cert_x509 cert_info;
......@@ -57,7 +58,7 @@ static int stransport_connect(git_stream *stream)
SecTrustResultType sec_res;
OSStatus ret;
if ((error = git_stream_connect(st->io)) < 0)
if (st->owned && (error = git_stream_connect(st->io)) < 0)
return error;
ret = SSLHandshake(st->ctx);
......@@ -226,41 +227,38 @@ static int stransport_close(git_stream *stream)
if (ret != noErr && ret != errSSLClosedGraceful)
return stransport_error(ret);
return git_stream_close(st->io);
return st->owned ? git_stream_close(st->io) : 0;
}
static void stransport_free(git_stream *stream)
{
stransport_stream *st = (stransport_stream *) stream;
git_stream_free(st->io);
if (st->owned)
git_stream_free(st->io);
CFRelease(st->ctx);
if (st->der_data)
CFRelease(st->der_data);
git__free(st);
}
int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
static int stransport_wrap(
git_stream **out,
git_stream *in,
const char *host,
int owned)
{
stransport_stream *st;
int error;
OSStatus ret;
assert(out && host);
assert(out && in && host);
st = git__calloc(1, sizeof(stransport_stream));
GITERR_CHECK_ALLOC(st);
#ifdef GIT_CURL
error = git_curl_stream_new(&st->io, host, port);
#else
error = git_socket_stream_new(&st->io, host, port);
#endif
if (error < 0){
git__free(st);
return error;
}
st->io = in;
st->owned = owned;
st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
if (!st->ctx) {
......@@ -295,4 +293,36 @@ int git_stransport_stream_new(git_stream **out, const char *host, const char *po
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;
assert(out && host);
#ifdef GIT_CURL
error = git_curl_stream_new(&stream, host, port);
#else
error = git_socket_stream_new(&stream, host, port);
#endif
if (!error)
error = stransport_wrap(out, stream, host, 1);
if (error < 0 && stream) {
git_stream_close(stream);
git_stream_free(stream);
}
return error;
}
#endif
......@@ -12,5 +12,6 @@
#include "git2/sys/stream.h"
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
......@@ -5,41 +5,124 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "streams/tls.h"
#include "git2/errors.h"
#include "common.h"
#include "global.h"
#include "streams/tls.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "streams/stransport.h"
static git_stream_cb tls_ctor;
struct git_tls_stream_registration {
git_rwlock lock;
git_stream_registration callbacks;
};
static struct git_tls_stream_registration stream_registration;
static void shutdown_ssl(void)
{
git_rwlock_free(&stream_registration.lock);
}
int git_tls_stream_global_init(void)
{
if (git_rwlock_init(&stream_registration.lock) < 0)
return -1;
git__on_shutdown(shutdown_ssl);
return 0;
}
int git_stream_register_tls(git_stream_cb ctor)
int git_stream_register_tls(git_stream_registration *registration)
{
tls_ctor = ctor;
assert(!registration || registration->init);
if (git_rwlock_wrlock(&stream_registration.lock) < 0) {
giterr_set(GITERR_OS, "failed to lock stream registration");
return -1;
}
if (registration)
memcpy(&stream_registration.callbacks, registration,
sizeof(git_stream_registration));
else
memset(&stream_registration.callbacks, 0,
sizeof(git_stream_registration));
git_rwlock_wrunlock(&stream_registration.lock);
return 0;
}
int git_tls_stream_new(git_stream **out, const char *host, const char *port)
{
int (*init)(git_stream **, const char *, const char *) = NULL;
if (tls_ctor)
return tls_ctor(out, host, port);
assert(out && host && port);
if (git_rwlock_rdlock(&stream_registration.lock) < 0) {
giterr_set(GITERR_OS, "failed to lock stream registration");
return -1;
}
if (stream_registration.callbacks.init) {
init = stream_registration.callbacks.init;
} else {
#ifdef GIT_SECURE_TRANSPORT
return git_stransport_stream_new(out, host, port);
init = git_stransport_stream_new;
#elif defined(GIT_OPENSSL)
return git_openssl_stream_new(out, host, port);
init = git_openssl_stream_new;
#elif defined(GIT_MBEDTLS)
return git_mbedtls_stream_new(out, host, port);
#else
GIT_UNUSED(out);
GIT_UNUSED(host);
GIT_UNUSED(port);
giterr_set(GITERR_SSL, "there is no TLS stream available");
return -1;
init = git_mbedtls_stream_new;
#endif
}
if (git_rwlock_rdunlock(&stream_registration.lock) < 0) {
giterr_set(GITERR_OS, "failed to unlock stream registration");
return -1;
}
if (!init) {
giterr_set(GITERR_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;
assert(out && in);
if (git_rwlock_rdlock(&stream_registration.lock) < 0) {
giterr_set(GITERR_OS, "failed to lock stream registration");
return -1;
}
if (stream_registration.callbacks.wrap) {
wrap = stream_registration.callbacks.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 (git_rwlock_rdunlock(&stream_registration.lock) < 0) {
giterr_set(GITERR_OS, "failed to unlock stream registration");
return -1;
}
if (!wrap) {
giterr_set(GITERR_SSL, "there is no TLS stream available");
return -1;
}
return wrap(out, in, host);
}
......@@ -11,13 +11,24 @@
#include "git2/sys/stream.h"
/** Configure TLS stream functions. */
int git_tls_stream_global_init(void);
/**
* Create a TLS stream with the most appropriate backend available for
* the current platform.
*
* This allows us to ask for a SecureTransport or OpenSSL stream
* according to being on general Unix vs OS X.
* 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
......@@ -713,10 +713,31 @@ static int check_certificate(
return error;
}
static int stream_connect(
git_stream *stream,
gitno_connection_data *url,
git_transport_certificate_check_cb cert_cb,
void *cb_payload)
{
int error;
GITERR_CHECK_VERSION(stream, GIT_STREAM_VERSION, "git_stream");
error = git_stream_connect(stream);
if (error && error != GIT_ECERTIFICATE)
return error;
if (git_stream_is_encrypted(stream) && cert_cb != NULL)
error = check_certificate(stream, url, !error, cert_cb, cb_payload);
return error;
}
static int http_connect(http_subtransport *t)
{
gitno_connection_data *url;
git_stream *stream = NULL;
git_stream *proxy_stream = NULL, *stream = NULL;
git_transport_certificate_check_cb cert_cb;
void *cb_payload;
int error;
......@@ -744,8 +765,14 @@ static int http_connect(http_subtransport *t)
}
#ifdef GIT_CURL
error = git_curl_stream_new(&stream,
t->server.url.host, t->server.url.port);
if ((error = git_curl_stream_new(&stream,
t->server.url.host, t->server.url.port)) < 0)
goto on_error;
GITERR_CHECK_VERSION(stream, GIT_STREAM_VERSION, "git_stream");
if ((error = apply_proxy_config_to_stream(stream, &t->proxy_opts)) < 0)
goto on_error;
#else
if (url->use_ssl)
error = git_tls_stream_new(&stream, url->host, url->port);
......@@ -756,20 +783,7 @@ static int http_connect(http_subtransport *t)
if (error < 0)
goto on_error;
GITERR_CHECK_VERSION(stream, GIT_STREAM_VERSION, "git_stream");
if ((error = apply_proxy_config_to_stream(stream, &t->proxy_opts)) < 0)
goto on_error;
error = git_stream_connect(stream);
if (error && error != GIT_ECERTIFICATE)
goto on_error;
if (git_stream_is_encrypted(stream) && cert_cb != NULL)
error = check_certificate(stream, url, !error, cert_cb, cb_payload);
if (error)
if ((error = stream_connect(stream, url, cert_cb, cb_payload)) < 0)
goto on_error;
t->server.stream = stream;
......@@ -782,6 +796,11 @@ on_error:
git_stream_free(stream);
}
if (proxy_stream) {
git_stream_close(proxy_stream);
git_stream_free(proxy_stream);
}
return error;
}
......
......@@ -6,7 +6,7 @@
static git_stream test_stream;
static int ctor_called;
static int test_ctor(git_stream **out, const char *host, const char *port)
static int test_stream_init(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(host);
GIT_UNUSED(port);
......@@ -17,13 +17,29 @@ static int test_ctor(git_stream **out, const char *host, const char *port)
return 0;
}
static int test_stream_wrap(git_stream **out, git_stream *in, const char *host)
{
GIT_UNUSED(in);
GIT_UNUSED(host);
ctor_called = 1;
*out = &test_stream;
return 0;
}
void test_core_stream__register_tls(void)
{
git_stream *stream;
git_stream_registration registration = {0};
int error;
registration.version = 1;
registration.init = test_stream_init;
registration.wrap = test_stream_wrap;
ctor_called = 0;
cl_git_pass(git_stream_register_tls(test_ctor));
cl_git_pass(git_stream_register_tls(&registration));
cl_git_pass(git_tls_stream_new(&stream, "localhost", "443"));
cl_assert_equal_i(1, ctor_called);
cl_assert_equal_p(&test_stream, stream);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment