diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2018-10-25 08:49:01 +0100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2018-11-28 15:46:57 +0000 |
commit | 43b592ac84dbd3d649022ff9503f00ecc83d5278 (patch) | |
tree | aeb52e3f745cf99eb5d7807073dab1d3d7a07709 | |
parent | 6ba3e6affc73b84f6cd2cadf476c0e0c5e58e404 (diff) | |
download | libgit2-43b592ac84dbd3d649022ff9503f00ecc83d5278.tar.gz |
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.
-rw-r--r-- | include/git2/sys/stream.h | 42 | ||||
-rw-r--r-- | src/global.c | 2 | ||||
-rw-r--r-- | src/streams/mbedtls.c | 63 | ||||
-rw-r--r-- | src/streams/mbedtls.h | 1 | ||||
-rw-r--r-- | src/streams/openssl.c | 62 | ||||
-rw-r--r-- | src/streams/openssl.h | 1 | ||||
-rw-r--r-- | src/streams/stransport.c | 66 | ||||
-rw-r--r-- | src/streams/stransport.h | 1 | ||||
-rw-r--r-- | src/streams/tls.c | 117 | ||||
-rw-r--r-- | src/streams/tls.h | 19 | ||||
-rw-r--r-- | src/transports/http.c | 53 | ||||
-rw-r--r-- | tests/core/stream.c | 20 |
12 files changed, 351 insertions, 96 deletions
diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index eeeb68dae..104ec3b5c 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -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 diff --git a/src/global.c b/src/global.c index d33772e91..fabe5f201 100644 --- a/src/global.c +++ b/src/global.c @@ -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) diff --git a/src/streams/mbedtls.c b/src/streams/mbedtls.c index bd7bd3126..27e076cb8 100644 --- a/src/streams/mbedtls.c +++ b/src/streams/mbedtls.c @@ -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; diff --git a/src/streams/mbedtls.h b/src/streams/mbedtls.h index 7283698ff..effe4589d 100644 --- a/src/streams/mbedtls.h +++ b/src/streams/mbedtls.h @@ -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); diff --git a/src/streams/openssl.c b/src/streams/openssl.c index a68f9a9cc..1092c3055 100644 --- a/src/streams/openssl.c +++ b/src/streams/openssl.c @@ -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; } diff --git a/src/streams/openssl.h b/src/streams/openssl.h index 496d7efbf..7296b7a08 100644 --- a/src/streams/openssl.h +++ b/src/streams/openssl.h @@ -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); diff --git a/src/streams/stransport.c b/src/streams/stransport.c index 15b305745..435162389 100644 --- a/src/streams/stransport.c +++ b/src/streams/stransport.c @@ -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 diff --git a/src/streams/stransport.h b/src/streams/stransport.h index 4c02d07e8..7994b3677 100644 --- a/src/streams/stransport.h +++ b/src/streams/stransport.h @@ -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 diff --git a/src/streams/tls.c b/src/streams/tls.c index 1bcb0d984..fe0725272 100644 --- a/src/streams/tls.c +++ b/src/streams/tls.c @@ -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); } diff --git a/src/streams/tls.h b/src/streams/tls.h index 6d110e8ad..00c6e0b56 100644 --- a/src/streams/tls.h +++ b/src/streams/tls.h @@ -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 diff --git a/src/transports/http.c b/src/transports/http.c index 65b5f5f76..77e1f1be1 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -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; } diff --git a/tests/core/stream.c b/tests/core/stream.c index 262888b10..872571f39 100644 --- a/tests/core/stream.c +++ b/tests/core/stream.c @@ -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(®istration)); cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); cl_assert_equal_i(1, ctor_called); cl_assert_equal_p(&test_stream, stream); |