diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index 56f1555f1..99f51b096 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -3,6 +3,7 @@ include(SanitizeBool) # We try to find any packages our backends might use find_package(OpenSSL) find_package(mbedTLS) + if(CMAKE_SYSTEM_NAME MATCHES "Darwin") find_package(Security) find_package(CoreFoundation) diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index 34dbbcf7d..0f7e74c10 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -15,6 +15,35 @@ GIT_BEGIN_DECL #define GIT_STREAM_VERSION 1 +typedef struct { + unsigned int version; + + /** + * Timeout for read and write operations; can be set to `0` to + * block indefinitely. + */ + int timeout; + + /** + * Timeout to connect to the remote server; can be set to `0` + * to use the system defaults. This can be shorter than the + * system default - often 75 seconds - but cannot be longer. + */ + int connect_timeout; +} git_stream_connect_options; + +#define GIT_STREAM_CONNECT_OPTIONS_VERSION 1 +#define GIT_STREAM_CONNECT_OPTIONS_INIT \ + { GIT_STREAM_CONNECT_OPTIONS_VERSION } + +#ifdef GIT_WIN32 +typedef SOCKET git_socket_t; +# define GIT_SOCKET_INVALID INVALID_SOCKET +#else +typedef int git_socket_t; +# define GIT_SOCKET_INVALID -1 +#endif + /** * Every stream must have this struct as its first element, so the * API can talk to it. You'd define your stream as @@ -29,22 +58,21 @@ GIT_BEGIN_DECL typedef struct git_stream { int version; + /** + * Nonzero if this is a TLS stream; zero if this is plain socket. + */ int encrypted : 1; - /** - * Timeout for read and write operations; can be set to `0` to - * block indefinitely. - */ - int timeout; - - /** - * Timeout to connect to the remote server; can be set to `0` - * to use the system defaults. This can be shorter than the - * system default - often 75 seconds - but cannot be longer. - */ - int connect_timeout; - - int GIT_CALLBACK(connect)(struct git_stream *); + int GIT_CALLBACK(connect)( + struct git_stream *, + const char *host, + const char *port, + const git_stream_connect_options *opts); + int GIT_CALLBACK(wrap)( + struct git_stream *, + struct git_stream *in, + const char *host); + git_socket_t GIT_CALLBACK(get_socket)(struct git_stream *); int GIT_CALLBACK(certificate)(git_cert **, struct git_stream *); ssize_t GIT_CALLBACK(read)(struct git_stream *, void *, size_t); ssize_t GIT_CALLBACK(write)(struct git_stream *, const char *, size_t, int); @@ -60,27 +88,9 @@ typedef struct { * Called to create a new connection to a given host. * * @param out The created 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 GIT_CALLBACK(init)(git_stream **out, const char *host, const char *port); - - /** - * Called to create a new connection on top of the given stream. If - * this is a TLS stream, then this function may be used to proxy a - * TLS stream over an HTTP CONNECT session. If this is unset, then - * HTTP CONNECT proxies will not be supported. - * - * @param out The created 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 GIT_CALLBACK(wrap)(git_stream **out, git_stream *in, const char *host); + int GIT_CALLBACK(init)(git_stream **out); } git_stream_registration; /** @@ -126,7 +136,7 @@ GIT_EXTERN(int) git_stream_register( * @deprecated Provide a git_stream_registration to git_stream_register * @see git_stream_registration */ -typedef int GIT_CALLBACK(git_stream_cb)(git_stream **out, const char *host, const char *port); +typedef int GIT_CALLBACK(git_stream_cb)(git_stream **out); /** * Register a TLS stream constructor for the library to use. This stream diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 84b6c1901..fa16f36af 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -45,7 +45,7 @@ set_target_properties(git2_cli PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) ide_split_sources(git2_cli) -target_include_directories(git2_cli PRIVATE ${CLI_INCLUDES}) +target_include_directories(git2_cli PRIVATE ${CLI_INCLUDES} ${LIBGIT2_SYSTEM_INCLUDES}) if(MSVC_IDE) # Precompiled headers diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c index ece0a2688..3b79eff77 100644 --- a/src/libgit2/libgit2.c +++ b/src/libgit2/libgit2.c @@ -47,16 +47,14 @@ extern size_t git_indexer__max_objects; extern bool git_disable_pack_keep_file_checks; extern int git_odb__packed_priority; extern int git_odb__loose_priority; -extern int git_stream_socket__connect_timeout; -extern int git_stream_socket__timeout; -char *git__user_agent; -char *git__ssl_ciphers; +int git_transport__connect_timeout; +int git_transport__timeout; +char *git_http__user_agent; static void libgit2_settings_global_shutdown(void) { - git__free(git__user_agent); - git__free(git__ssl_ciphers); + git__free(git_http__user_agent); git_repository__free_extensions(); } @@ -159,16 +157,6 @@ static int config_level_to_sysdir(int *out, int config_level) return -1; } -const char *git_libgit2__user_agent(void) -{ - return git__user_agent; -} - -const char *git_libgit2__ssl_ciphers(void) -{ - return git__ssl_ciphers; -} - int git_libgit2_opts(int key, ...) { int error = 0; @@ -287,9 +275,9 @@ int git_libgit2_opts(int key, ...) #endif break; case GIT_OPT_SET_USER_AGENT: - git__free(git__user_agent); - git__user_agent = git__strdup(va_arg(ap, const char *)); - if (!git__user_agent) { + git__free(git_http__user_agent); + git_http__user_agent = git__strdup(va_arg(ap, const char *)); + if (!git_http__user_agent) { git_error_set_oom(); error = -1; } @@ -305,15 +293,9 @@ int git_libgit2_opts(int key, ...) break; case GIT_OPT_SET_SSL_CIPHERS: -#if (GIT_OPENSSL || GIT_MBEDTLS) - { - git__free(git__ssl_ciphers); - git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); - if (!git__ssl_ciphers) { - git_error_set_oom(); - error = -1; - } - } +#if (GIT_OPENSSL) + if (git_openssl__set_ciphers(va_arg(ap, const char *)) < 0) + error = -1; #else git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); error = -1; @@ -326,7 +308,7 @@ int git_libgit2_opts(int key, ...) git_str str = GIT_STR_INIT; if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_str_puts(&str, git__user_agent)) < 0) + (error = git_str_puts(&str, git_http__user_agent)) < 0) break; error = git_buf_fromstr(out, &str); @@ -439,7 +421,7 @@ int git_libgit2_opts(int key, ...) break; case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: - *(va_arg(ap, int *)) = git_stream_socket__connect_timeout; + *(va_arg(ap, int *)) = git_transport__connect_timeout; break; case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: @@ -450,13 +432,13 @@ int git_libgit2_opts(int key, ...) git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); error = -1; } else { - git_stream_socket__connect_timeout = timeout; + git_transport__connect_timeout = timeout; } } break; case GIT_OPT_GET_SERVER_TIMEOUT: - *(va_arg(ap, int *)) = git_stream_socket__timeout; + *(va_arg(ap, int *)) = git_transport__timeout; break; case GIT_OPT_SET_SERVER_TIMEOUT: @@ -467,7 +449,7 @@ int git_libgit2_opts(int key, ...) git_error_set(GIT_ERROR_INVALID, "invalid timeout"); error = -1; } else { - git_stream_socket__timeout = timeout; + git_transport__timeout = timeout; } } break; diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h deleted file mode 100644 index dc42ce939..000000000 --- a/src/libgit2/settings.h +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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. - */ - -extern int git_settings_global_init(void); - -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); diff --git a/src/libgit2/transport.h b/src/libgit2/transport.h new file mode 100644 index 000000000..043aac322 --- /dev/null +++ b/src/libgit2/transport.h @@ -0,0 +1,22 @@ +/* + * 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_transport_h__ +#define INCLUDE_transport_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +extern int git_transport__timeout; +extern int git_transport__connect_timeout; + +GIT_INLINE(void) git_transport__set_connect_opts(git_stream_connect_options *opts) +{ + opts->timeout = git_transport__timeout; + opts->connect_timeout = git_transport__connect_timeout; +} + +#endif diff --git a/src/libgit2/transports/git.c b/src/libgit2/transports/git.c index f083af158..a78f56946 100644 --- a/src/libgit2/transports/git.c +++ b/src/libgit2/transports/git.c @@ -150,8 +150,6 @@ static int git_proto_stream_alloc( git_subtransport *t, const char *url, const char *cmd, - const char *host, - const char *port, git_smart_subtransport_stream **stream) { git_proto_stream *s; @@ -175,7 +173,7 @@ static int git_proto_stream_alloc( return -1; } - if ((git_stream_socket_new(&s->io, host, port)) < 0) + if ((git_stream_socket_new(&s->io)) < 0) return -1; GIT_ERROR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); @@ -206,24 +204,26 @@ static int _git_uploadpack_ls( host = urldata.host; port = urldata.port ? urldata.port : GIT_DEFAULT_PORT; - error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); - - git_net_url_dispose(&urldata); + error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, stream); if (error < 0) { git_proto_stream_free(*stream); - return error; + goto done; } s = (git_proto_stream *) *stream; - if ((error = git_stream_connect(s->io)) < 0) { + + if ((error = git_stream_connect(s->io, host, port, NULL)) < 0) { git_proto_stream_free(*stream); - return error; + goto done; } t->current_stream = s; + error = 0; - return 0; +done: + git_net_url_dispose(&urldata); + return error; } static int _git_uploadpack( @@ -259,23 +259,25 @@ static int _git_receivepack_ls( if ((error = git_net_url_parse(&urldata, url)) < 0) return error; - error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); + error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, stream); - git_net_url_dispose(&urldata); if (error < 0) { git_proto_stream_free(*stream); - return error; + goto done; } s = (git_proto_stream *) *stream; - if ((error = git_stream_connect(s->io)) < 0) - return error; + if ((error = git_stream_connect(s->io, urldata.host, urldata.port, NULL)) < 0) + goto done; t->current_stream = s; + error = 0; - return 0; +done: + git_net_url_dispose(&urldata); + return error; } static int _git_receivepack( diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index 5f26d3b5e..1e9739eeb 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -14,6 +14,7 @@ #include "smart.h" #include "http.h" #include "trace.h" +#include "transport.h" #include "streams/tls.h" #include "streams/socket.h" #include "net/auth.h" @@ -25,6 +26,9 @@ bool git_http__expect_continue = false; +extern int git_transport__timeout; +extern int git_transport__connect_timeout; + typedef enum { HTTP_STATE_NONE = 0, HTTP_STATE_SENDING_REQUEST, @@ -692,6 +696,10 @@ static int http_action( GIT_ERROR_CHECK_ALLOC(stream); opts.user_agent = transport->user_agent.ptr; + + opts.timeout = git_transport__timeout; + opts.connect_timeout = git_transport__connect_timeout; + opts.server_certificate_check_cb = connect_opts->callbacks.certificate_check; opts.server_certificate_check_payload = connect_opts->callbacks.payload; opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check; @@ -759,7 +767,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own transport = git__calloc(sizeof(http_subtransport), 1); GIT_ERROR_CHECK_ALLOC(transport); - if (git_http__user_agent(&transport->user_agent) < 0) + if (git_http__append_user_agent(&transport->user_agent) < 0) return -1; transport->owner = (transport_smart *)owner; diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h index e6f42ef05..d3bf60e7d 100644 --- a/src/libgit2/transports/http.h +++ b/src/libgit2/transports/http.h @@ -8,16 +8,16 @@ #ifndef INCLUDE_transports_http_h__ #define INCLUDE_transports_http_h__ -#include "settings.h" #include "net/httpclient.h" #define GIT_HTTP_REPLAY_MAX 15 extern bool git_http__expect_continue; +extern char *git_http__user_agent; -GIT_INLINE(int) git_http__user_agent(git_str *buf) +GIT_INLINE(int) git_http__append_user_agent(git_str *buf) { - const char *ua = git_libgit2__user_agent(); + const char *ua = git_http__user_agent; if (!ua) ua = "libgit2 " LIBGIT2_VERSION; diff --git a/src/libgit2/transports/ssh_libssh2.c b/src/libgit2/transports/ssh_libssh2.c index 4738fd08e..e98f226d0 100644 --- a/src/libgit2/transports/ssh_libssh2.c +++ b/src/libgit2/transports/ssh_libssh2.c @@ -13,6 +13,7 @@ #include "runtime.h" #include "smart.h" +#include "transport.h" #include "streams/socket.h" #include "sysdir.h" #include "net/url.h" @@ -521,16 +522,22 @@ static int _git_ssh_session_create( LIBSSH2_KNOWNHOSTS **hosts, const char *hostname, int port, - git_stream *io) + git_stream *stream) { - git_stream_socket *socket = GIT_CONTAINER_OF(io, git_stream_socket, parent); LIBSSH2_SESSION *s; LIBSSH2_KNOWNHOSTS *known_hosts; + git_socket_t socket; git_str prefs = GIT_STR_INIT; int rc = 0; GIT_ASSERT_ARG(session); GIT_ASSERT_ARG(hosts); + GIT_ASSERT_ARG(stream); + + if ((socket = git_stream_get_socket(stream)) == GIT_SOCKET_INVALID) { + git_error_set(GIT_ERROR_NET, "could not get socket"); + return -1; + } s = libssh2_session_init(); if (!s) { @@ -557,7 +564,7 @@ static int _git_ssh_session_create( git_str_dispose(&prefs); do { - rc = libssh2_session_handshake(s, socket->s); + rc = libssh2_session_handshake(s, socket); } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); if (rc != LIBSSH2_ERROR_NONE) { @@ -766,9 +773,10 @@ static int _git_ssh_setup_conn( int auth_methods, error = 0, port; ssh_stream *s; git_credential *cred = NULL; - LIBSSH2_SESSION *session=NULL; - LIBSSH2_CHANNEL *channel=NULL; + LIBSSH2_SESSION *session = NULL; + LIBSSH2_CHANNEL *channel = NULL; LIBSSH2_KNOWNHOSTS *known_hosts = NULL; + git_stream_connect_options opts = GIT_STREAM_CONNECT_OPTIONS_INIT; t->current_stream = NULL; @@ -780,6 +788,8 @@ static int _git_ssh_setup_conn( s->session = NULL; s->channel = NULL; + git_transport__set_connect_opts(&opts); + if (git_net_str_is_url(url)) error = git_net_url_parse(&s->url, url); else @@ -788,8 +798,8 @@ static int _git_ssh_setup_conn( if (error < 0) goto done; - if ((error = git_stream_socket_new(&s->io, s->url.host, s->url.port)) < 0 || - (error = git_stream_connect(s->io)) < 0) + if ((error = git_stream_socket_new(&s->io) < 0) || + (error = git_stream_connect(s->io, s->url.host, s->url.port, &opts)) < 0) goto done; /* diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index ae572c56d..c4ccfbcaf 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -788,7 +788,7 @@ static int winhttp_connect( } - if (git_http__user_agent(&ua) < 0) + if (git_http__append_user_agent(&ua) < 0) goto on_error; if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { diff --git a/src/util/net/httpclient.c b/src/util/net/httpclient.c index b98fe1e72..2d15a7da2 100644 --- a/src/util/net/httpclient.c +++ b/src/util/net/httpclient.c @@ -817,12 +817,22 @@ static int check_certificate( static int server_connect_stream( git_http_server *server, - git_transport_certificate_check_cb cert_cb, - void *cb_payload) + git_stream *proxy_stream, + git_http_client_options *opts) { + git_stream_connect_options connect_opts = GIT_STREAM_CONNECT_OPTIONS_INIT; + git_transport_certificate_check_cb cert_cb = opts->server_certificate_check_cb; + void *cb_payload = opts->server_certificate_check_payload; + git_net_url *url = &server->url; int error; - error = git_stream_connect(server->stream); + connect_opts.connect_timeout = opts->connect_timeout; + connect_opts.timeout = opts->timeout; + + if (proxy_stream) + error = git_stream_wrap(server->stream, proxy_stream, url->host); + else + error = git_stream_connect(server->stream, url->host, url->port, &connect_opts); if (error && error != GIT_ECERTIFICATE) return error; @@ -929,9 +939,9 @@ GIT_INLINE(int) server_create_stream(git_http_server *server) git_net_url *url = &server->url; if (strcasecmp(url->scheme, "https") == 0) - return git_stream_tls_new(&server->stream, url->host, url->port); + return git_stream_tls_new(&server->stream); else if (strcasecmp(url->scheme, "http") == 0) - return git_stream_socket_new(&server->stream, url->host, url->port); + return git_stream_socket_new(&server->stream); git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme); return -1; @@ -961,8 +971,7 @@ static int proxy_connect( if ((error = server_create_stream(&client->proxy)) < 0 || (error = server_connect_stream(&client->proxy, - client->opts.proxy_certificate_check_cb, - client->opts.proxy_certificate_check_payload)) < 0) + NULL, &client->opts)) < 0) goto done; client->proxy_connected = 1; @@ -1004,25 +1013,14 @@ done: static int server_connect(git_http_client *client) { - git_net_url *url = &client->server.url; - git_transport_certificate_check_cb cert_cb; - void *cert_payload; int error; client->current_server = SERVER; - if (client->proxy.stream) - error = git_stream_tls_wrap(&client->server.stream, client->proxy.stream, url->host); - else - error = server_create_stream(&client->server); - - if (error < 0) + if ((error = server_create_stream(&client->server)) < 0) goto done; - cert_cb = client->opts.server_certificate_check_cb; - cert_payload = client->opts.server_certificate_check_payload; - - error = server_connect_stream(&client->server, cert_cb, cert_payload); + error = server_connect_stream(&client->server, client->proxy.stream, &client->opts); done: return error; diff --git a/src/util/net/httpclient.h b/src/util/net/httpclient.h index 843b9d5dc..16543d1f8 100644 --- a/src/util/net/httpclient.h +++ b/src/util/net/httpclient.h @@ -72,6 +72,12 @@ typedef struct { /** The value of the `User-Agent` header to send */ const char *user_agent; + /** Timeout for connecting */ + int connect_timeout; + + /** Timeout for I/O */ + int timeout; + /** Certificate check callback for the remote */ git_transport_certificate_check_cb server_certificate_check_cb; void *server_certificate_check_payload; diff --git a/src/util/stream.h b/src/util/stream.h index 130110a8d..968c63324 100644 --- a/src/util/stream.h +++ b/src/util/stream.h @@ -10,9 +10,21 @@ #include "git2_util.h" #include "git2/sys/stream.h" -GIT_INLINE(int) git_stream_connect(git_stream *st) +GIT_INLINE(int) git_stream_connect( + git_stream *st, + const char *host, + const char *port, + const git_stream_connect_options *opts) { - return st->connect(st); + return st->connect(st, host, port, opts); +} + +GIT_INLINE(int) git_stream_wrap( + git_stream *st, + git_stream *in, + const char *host) +{ + return st->wrap(st, in, host); } GIT_INLINE(int) git_stream_is_encrypted(git_stream *st) @@ -20,6 +32,11 @@ GIT_INLINE(int) git_stream_is_encrypted(git_stream *st) return st->encrypted; } +GIT_INLINE(GIT_SOCKET) git_stream_get_socket(git_stream *st) +{ + return st->get_socket(st); +} + GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) { if (!st->encrypted) { diff --git a/src/util/streams/mbedtls.c b/src/util/streams/mbedtls.c index b466762d0..e7d8a5681 100644 --- a/src/util/streams/mbedtls.c +++ b/src/util/streams/mbedtls.c @@ -220,10 +220,16 @@ 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); + char buf[512]; + int len = mbedtls_x509_crt_verify_info(buf, sizeof(buf), + "", ret); + + if (len >= 1) { + buf[0] = tolower(buf[0]); + buf[len - 1] = '\0'; /* Remove trailing \n */ + } + + git_error_set(GIT_ERROR_SSL, "%s", buf); return GIT_ECERTIFICATE; } @@ -236,29 +242,60 @@ typedef struct { int owned; bool connected; char *host; - mbedtls_ssl_context *ssl; + mbedtls_ssl_context ssl; git_cert_x509 cert_info; } mbedtls_stream; - -static int mbedtls_connect(git_stream *stream) +static int mbedtls_setup(mbedtls_stream *st, const char *host) { int ret; + + 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"); + return -1; + } + + st->connected = 1; + + mbedtls_ssl_set_bio(&st->ssl, st->io, bio_write, bio_read, NULL); + + if ((ret = mbedtls_ssl_set_hostname(&st->ssl, host) != 0) || + (ret = mbedtls_ssl_handshake(&st->ssl)) != 0) + return ssl_set_error(&st->ssl, ret); + + return verify_server_cert(&st->ssl); +} + +static int mbedtls_connect( + git_stream *stream, + const char *host, + const char *port, + const git_stream_connect_options *opts) +{ mbedtls_stream *st = (mbedtls_stream *) stream; - if (st->owned && (ret = git_stream_connect(st->io)) < 0) - return ret; + st->owned = 1; - st->connected = true; + if (git_stream_socket_new(&st->io) < 0 || + git_stream_connect(st->io, host, port, opts) < 0) + return -1; - mbedtls_ssl_set_hostname(st->ssl, st->host); + return mbedtls_setup(st, host); +} - mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL); +static int mbedtls_wrap( + git_stream *stream, + git_stream *in, + const char *host) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; - if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0) - return ssl_set_error(st->ssl, ret); + st->io = in; + st->owned = 0; - return verify_server_cert(st->ssl); + return mbedtls_setup(st, host); } static int mbedtls_certificate(git_cert **out, git_stream *stream) @@ -266,7 +303,7 @@ 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); + 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; @@ -305,8 +342,8 @@ static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t */ 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); + if ((written = mbedtls_ssl_write(&st->ssl, (const unsigned char *)data, len)) <= 0) + return ssl_set_error(&st->ssl, written); return written; } @@ -316,8 +353,8 @@ 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); + if ((ret = mbedtls_ssl_read(&st->ssl, (unsigned char *)data, len)) <= 0) + ssl_set_error(&st->ssl, ret); return ret; } @@ -327,10 +364,10 @@ 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) + if (st->connected && (ret = ssl_teardown(&st->ssl)) != 0) return -1; - st->connected = false; + st->connected = 0; return st->owned ? git_stream_close(st->io) : 0; } @@ -342,90 +379,30 @@ static void mbedtls_stream_free(git_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); + mbedtls_ssl_free(&st->ssl); git__free(st); } -static int mbedtls_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) +int git_stream_mbedtls_new(git_stream **out) { 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.connect = mbedtls_connect; + st->parent.wrap = mbedtls_wrap; st->parent.certificate = mbedtls_certificate; 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; + *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_stream_mbedtls_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return mbedtls_stream_wrap(out, in, host, 0); -} - -int git_stream_mbedtls_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_stream_socket_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) diff --git a/src/util/streams/mbedtls.h b/src/util/streams/mbedtls.h index 5a14cac1b..6e130e073 100644 --- a/src/util/streams/mbedtls.h +++ b/src/util/streams/mbedtls.h @@ -13,11 +13,12 @@ extern int git_stream_mbedtls_global_init(void); -#ifdef GIT_HTTPS_MBEDTLS +# ifdef GIT_HTTPS_MBEDTLS + extern int git_mbedtls__set_cert_location(const char *file, const char *path); -extern int git_stream_mbedtls_new(git_stream **out, const char *host, const char *port); -extern int git_stream_mbedtls_wrap(git_stream **out, git_stream *in, const char *host); -#endif +extern int git_stream_mbedtls_new(git_stream **out); + +# endif #endif diff --git a/src/util/streams/openssl.c b/src/util/streams/openssl.c index 55ae7c2db..e5aac231b 100644 --- a/src/util/streams/openssl.c +++ b/src/util/streams/openssl.c @@ -15,10 +15,9 @@ #include "git2_util.h" #include "runtime.h" -#include "settings.h" #include "posix.h" #include "stream.h" -#include "net.h" +#include "net/url.h" #include "streams/socket.h" #include "git2/transport.h" #include "git2/sys/openssl.h" @@ -36,7 +35,7 @@ # include #endif -SSL_CTX *git__ssl_ctx; +SSL_CTX *openssl_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" @@ -55,9 +54,9 @@ static void shutdown_ssl(void) git_stream_bio_method = NULL; } - if (git__ssl_ctx) { - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; + if (openssl_ctx) { + SSL_CTX_free(openssl_ctx); + openssl_ctx = NULL; } } @@ -105,7 +104,6 @@ static void git_openssl_free(void *mem) 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 @@ -138,20 +136,18 @@ static int openssl_init(void) * 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()))) + if (!(openssl_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)) + SSL_CTX_set_options(openssl_ctx, ssl_opts); + SSL_CTX_set_mode(openssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(openssl_ctx, SSL_VERIFY_NONE, NULL); + + if (!SSL_CTX_set_default_verify_paths(openssl_ctx)) goto error; - if (!ciphers) - ciphers = GIT_SSL_DEFAULT_CIPHERS; - - if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) - goto error; + if (!SSL_CTX_set_cipher_list(openssl_ctx, GIT_SSL_DEFAULT_CIPHERS)) + return -1; if (init_bio_method() < 0) goto error; @@ -161,8 +157,8 @@ static int openssl_init(void) 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; + SSL_CTX_free(openssl_ctx); + openssl_ctx = NULL; return -1; } @@ -503,14 +499,20 @@ typedef struct { git_cert_x509 cert_info; } openssl_stream; -static int openssl_connect(git_stream *stream) +static int openssl_create_session(openssl_stream *st, const char *host) { - int ret; BIO *bio; - openssl_stream *st = (openssl_stream *) stream; + int ret; - if (st->owned && (ret = git_stream_connect(st->io)) < 0) - return ret; + st->ssl = SSL_new(openssl_ctx); + + if (st->ssl == NULL) { + git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); + return -1; + } + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); bio = BIO_new(git_stream_bio_method); GIT_ERROR_CHECK_ALLOC(bio); @@ -531,6 +533,33 @@ static int openssl_connect(git_stream *stream) return verify_server_cert(st->ssl, st->host); } +static int openssl_connect( + git_stream *stream, + const char *host, + const char *port, + const git_stream_connect_options *opts) +{ + openssl_stream *st = (openssl_stream *)stream; + + if (git_stream_socket_new(&st->io) < 0 || + git_stream_connect(st->io, host, port, opts) < 0) + return -1; + + st->owned = 1; + + return openssl_create_session(st, host); +} + +static int openssl_wrap(git_stream *stream, git_stream *in, const char *host) +{ + openssl_stream *st = (openssl_stream *)stream; + + st->io = in; + st->owned = 0; + + return openssl_create_session(st, host); +} + static int openssl_certificate(git_cert **out, git_stream *stream) { openssl_stream *st = (openssl_stream *) stream; @@ -622,37 +651,28 @@ static void openssl_free(git_stream *stream) git__free(st); } -static int openssl_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) +int git_stream_openssl_new(git_stream **out) { openssl_stream *st; GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(host); + + if (openssl_ensure_initialized() < 0) + return -1; 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) { + if ((st->ssl = SSL_new(openssl_ctx)) == 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.connect = openssl_connect; + st->parent.wrap = openssl_wrap; st->parent.certificate = openssl_certificate; st->parent.read = openssl_read; st->parent.write = openssl_write; @@ -663,43 +683,12 @@ static int openssl_stream_wrap( return 0; } -int git_stream_openssl_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_stream_openssl_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_stream_socket_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) { + if (SSL_CTX_load_verify_locations(openssl_ctx, file, path) == 0) { char errmsg[256]; ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); @@ -711,6 +700,20 @@ int git_openssl__set_cert_location(const char *file, const char *path) return 0; } +int git_openssl__set_ciphers(const char *ciphers) +{ + if (!ciphers) + ciphers = GIT_SSL_DEFAULT_CIPHERS; + + if (openssl_ensure_initialized() < 0) + return -1; + + if(!SSL_CTX_set_cipher_list(openssl_ctx, ciphers)) + return -1; + + return 0; +} + #else #include "stream.h" diff --git a/src/util/streams/openssl.h b/src/util/streams/openssl.h index 4f2ac3e2d..c5c11c1b3 100644 --- a/src/util/streams/openssl.h +++ b/src/util/streams/openssl.h @@ -23,9 +23,9 @@ extern int git_stream_openssl_global_init(void); # endif #ifdef GIT_HTTPS_OPENSSL +extern int git_openssl__set_ciphers(const char *ciphers); extern int git_openssl__set_cert_location(const char *file, const char *path); -extern int git_stream_openssl_new(git_stream **out, const char *host, const char *port); -extern int git_stream_openssl_wrap(git_stream **out, git_stream *in, const char *host); +extern int git_stream_openssl_new(git_stream **out); #endif #endif diff --git a/src/util/streams/registry.c b/src/util/streams/registry.c index 70d4830fe..00d109302 100644 --- a/src/util/streams/registry.c +++ b/src/util/streams/registry.c @@ -101,14 +101,13 @@ int git_stream_register(git_stream_t type, git_stream_registration *registration #ifndef GIT_DEPRECATE_HARD int git_stream_register_tls( - int GIT_CALLBACK(ctor)(git_stream **out, const char *host, const char *port)) + int GIT_CALLBACK(ctor)(git_stream **out)) { 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, ®istration); } else { diff --git a/src/util/streams/schannel.c b/src/util/streams/schannel.c index dec24642e..bc2475675 100644 --- a/src/util/streams/schannel.c +++ b/src/util/streams/schannel.c @@ -42,8 +42,8 @@ typedef enum { typedef struct { git_stream parent; git_stream *io; - int owned; - bool connected; + int owned : 1, + connected : 1; wchar_t *host_w; schannel_state state; @@ -70,9 +70,6 @@ static int connect_context(schannel_stream *st) ssize_t read_len; int error = 0; - if (st->owned && (error = git_stream_connect(st->io)) < 0) - return error; - cred.dwVersion = SCHANNEL_CRED_VERSION; cred.dwFlags = SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE | @@ -357,14 +354,23 @@ static int check_certificate(schannel_stream* st) return 0; } -static int schannel_connect(git_stream *stream) +static int schannel_connect( + git_stream *stream, + const char *host, + const char *port, + const git_stream_connect_options *opts) { schannel_stream *st = (schannel_stream *)stream; int error; GIT_ASSERT(st->state == STATE_NONE); - if ((error = connect_context(st)) < 0 || + st->owned = 1; + + if ((error = git_stream_socket_new(&st->io)) < 0 || + (error = git_stream_connect(st->io, host, port, opts)) < 0 || + (error = git_utf8_to_16_alloc(&st->host_w, host)) < 0 || + (error = connect_context(st)) < 0 || (error = check_certificate(st)) < 0) return error; @@ -372,6 +378,24 @@ static int schannel_connect(git_stream *stream) return 0; } +static int schannel_wrap(git_stream *stream, git_stream *in, const char *host) +{ + schannel_stream *st = (schannel_stream *)stream; + + GIT_ASSERT(st->state == STATE_NONE); + + st->io = in; + st->owned = 0; + + if (git_utf8_to_16_alloc(&st->host_w, host) < 0 || + connect_context(st) < 0 || + check_certificate(st) < 0) + return -1; + + st->connected = 1; + return 0; +} + static int schannel_certificate(git_cert **out, git_stream *stream) { schannel_stream *st = (schannel_stream *)stream; @@ -603,7 +627,7 @@ static int schannel_close(git_stream *stream) } } - st->connected = false; + st->connected = 0; if (st->owned && git_stream_close(st->io) < 0) error = -1; @@ -639,28 +663,19 @@ static void schannel_free(git_stream *stream) git__free(st); } -static int schannel_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) +extern int git_stream_schannel_new(git_stream **out) { schannel_stream *st; + GIT_ASSERT_ARG(out); + st = git__calloc(1, sizeof(schannel_stream)); GIT_ERROR_CHECK_ALLOC(st); - st->io = in; - st->owned = owned; - - if (git_utf8_to_16_alloc(&st->host_w, host) < 0) { - git__free(st); - return -1; - } - st->parent.version = GIT_STREAM_VERSION; st->parent.encrypted = 1; st->parent.connect = schannel_connect; + st->parent.wrap = schannel_wrap; st->parent.certificate = schannel_certificate; st->parent.read = schannel_read; st->parent.write = schannel_write; @@ -671,35 +686,4 @@ static int schannel_stream_wrap( return 0; } -extern int git_stream_schannel_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_stream_socket_new(&stream, host, port)) < 0) - return error; - - if ((error = schannel_stream_wrap(out, stream, host, 1)) < 0) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -extern int git_stream_schannel_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return schannel_stream_wrap(out, in, host, 0); -} - #endif diff --git a/src/util/streams/schannel.h b/src/util/streams/schannel.h index 295dda45d..6447a3376 100644 --- a/src/util/streams/schannel.h +++ b/src/util/streams/schannel.h @@ -13,15 +13,7 @@ #ifdef GIT_HTTPS_SCHANNEL -extern int git_stream_schannel_new( - git_stream **out, - const char *host, - const char *port); - -extern int git_stream_schannel_wrap( - git_stream **out, - git_stream *in, - const char *host); +extern int git_stream_schannel_new(git_stream **out); #endif diff --git a/src/util/streams/securetransport.c b/src/util/streams/securetransport.c index 358e38665..979da8d1f 100644 --- a/src/util/streams/securetransport.c +++ b/src/util/streams/securetransport.c @@ -50,57 +50,6 @@ typedef struct { git_cert_x509 cert_info; } securetransport_stream; -static int securetransport_connect(git_stream *stream) -{ - securetransport_stream *st = (securetransport_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 && st->error != 0) - return -1; - else 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 securetransport_error(ret); -} - static int securetransport_certificate(git_cert **out, git_stream *stream) { securetransport_stream *st = (securetransport_stream *) stream; @@ -160,7 +109,11 @@ static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) return noErr; } -static ssize_t securetransport_write(git_stream *stream, const char *data, size_t len, int flags) +static ssize_t securetransport_write( + git_stream *stream, + const char *data, + size_t len, + int flags) { securetransport_stream *st = (securetransport_stream *) stream; size_t data_len, processed; @@ -220,7 +173,10 @@ static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) return error; } -static ssize_t securetransport_read(git_stream *stream, void *data, size_t len) +static ssize_t securetransport_read( + git_stream *stream, + void *data, + size_t len) { securetransport_stream *st = (securetransport_stream *)stream; size_t processed; @@ -236,12 +192,137 @@ static ssize_t securetransport_read(git_stream *stream, void *data, size_t len) return processed; } +static int securetransport_create_context( + securetransport_stream *st, + const char *host) +{ + SecTrustRef trust = NULL; + SecTrustResultType sec_res; + OSStatus ret; + int error = -1; + + st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); + + if (!st->ctx) { + git_error_set(GIT_ERROR_NET, "failed to create SSL context"); + return -1; + } + + /* Set up context */ + + if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || + (ret = SSLSetConnection(st->ctx, st)) != 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) { + error = securetransport_error(ret); + goto on_error; + } + + /* Connect */ + + ret = SSLHandshake(st->ctx); + + if (ret != errSSLServerAuthCompleted && st->error != 0) { + error = -1; + goto on_error; + } else if (ret != errSSLServerAuthCompleted) { + git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret); + error = -1; + goto on_error; + } + + if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) { + error = securetransport_error(ret); + goto on_error; + } + + if (!trust) + return GIT_ECERTIFICATE; + + if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr) { + error = securetransport_error(ret); + goto on_error; + } + + CFRelease(trust); + + if (sec_res == kSecTrustResultInvalid || + sec_res == kSecTrustResultOtherError) { + git_error_set(GIT_ERROR_SSL, "internal security trust error"); + error = -1; + goto on_error; + } + + 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); + + if (st->ctx) { + CFRelease(st->ctx); + st->ctx = NULL; + } + + return error; +} + +int securetransport_connect( + git_stream *stream, + const char *host, + const char *port, + const git_stream_connect_options *opts) +{ + securetransport_stream *st = (securetransport_stream *)stream; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if (git_stream_socket_new(&st->io) < 0) + return -1; + + st->owned = 1; + + if (git_stream_connect(st->io, host, port, opts) < 0) + return -1; + + return securetransport_create_context(st, host); +} + +int securetransport_wrap( + git_stream *stream, + git_stream *in, + const char *host) +{ + securetransport_stream *st = (securetransport_stream *)stream; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(host); + + st->io = in; + st->owned = 0; + + return securetransport_create_context(st, host); +} + static int securetransport_close(git_stream *stream) { - securetransport_stream *st = (securetransport_stream *) stream; + securetransport_stream *st = (securetransport_stream *)stream; OSStatus ret; ret = SSLClose(st->ctx); + if (ret != noErr && ret != errSSLClosedGraceful) return securetransport_error(ret); @@ -250,94 +331,41 @@ static int securetransport_close(git_stream *stream) static void securetransport_free(git_stream *stream) { - securetransport_stream *st = (securetransport_stream *) stream; + securetransport_stream *st = (securetransport_stream *)stream; if (st->owned) git_stream_free(st->io); - CFRelease(st->ctx); + if (st->ctx) + CFRelease(st->ctx); + if (st->der_data) CFRelease(st->der_data); + git__free(st); } -static int securetransport_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) +int git_stream_securetransport_new(git_stream **out) { securetransport_stream *st; - OSStatus ret; GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(host); st = git__calloc(1, sizeof(securetransport_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)) != 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 securetransport_error(ret); - } - st->parent.version = GIT_STREAM_VERSION; st->parent.encrypted = 1; st->parent.connect = securetransport_connect; + st->parent.wrap = securetransport_wrap; st->parent.certificate = securetransport_certificate; st->parent.read = securetransport_read; st->parent.write = securetransport_write; st->parent.close = securetransport_close; st->parent.free = securetransport_free; - *out = (git_stream *) st; + *out = (git_stream *)st; return 0; } -int git_stream_securetransport_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return securetransport_wrap(out, in, host, 0); -} - -int git_stream_securetransport_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_stream_socket_new(&stream, host, port); - - if (!error) - error = securetransport_wrap(out, stream, host, 1); - - if (error < 0 && stream) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - #endif diff --git a/src/util/streams/securetransport.h b/src/util/streams/securetransport.h index 00f3f6ea9..6083750b8 100644 --- a/src/util/streams/securetransport.h +++ b/src/util/streams/securetransport.h @@ -13,8 +13,7 @@ #ifdef GIT_HTTPS_SECURETRANSPORT -extern int git_stream_securetransport_new(git_stream **out, const char *host, const char *port); -extern int git_stream_securetransport_wrap(git_stream **out, git_stream *in, const char *host); +extern int git_stream_securetransport_new(git_stream **out); #endif diff --git a/src/util/streams/socket.c b/src/util/streams/socket.c index 1805f27f3..73c5d1d7a 100644 --- a/src/util/streams/socket.c +++ b/src/util/streams/socket.c @@ -28,11 +28,10 @@ # endif #endif -int git_stream_socket__connect_timeout = 0; -int git_stream_socket__timeout = 0; - typedef struct { git_stream parent; + int connect_timeout; + int timeout; char *host; char *port; GIT_SOCKET s; @@ -173,7 +172,11 @@ static int connect_with_timeout( return 0; } -static int socket_connect(git_stream *stream) +static int socket_connect( + git_stream *stream, + const char *host, + const char *port, + const git_stream_connect_options *opts) { git_stream_socket *st = (git_stream_socket *) stream; GIT_SOCKET s = INVALID_SOCKET; @@ -181,14 +184,23 @@ static int socket_connect(git_stream *stream) struct addrinfo hints; int error; + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if (opts) { + st->timeout = opts->timeout; + st->connect_timeout = opts->connect_timeout; + } + memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; - if ((error = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { + if ((error = p_getaddrinfo(host, port, &hints, &info)) != 0) { git_error_set(GIT_ERROR_NET, "failed to resolve address for %s: %s", - st->host, p_gai_strerror(error)); + host, p_gai_strerror(error)); return -1; } @@ -200,7 +212,7 @@ static int socket_connect(git_stream *stream) error = connect_with_timeout(s, p->ai_addr, (socklen_t)p->ai_addrlen, - st->parent.connect_timeout); + st->connect_timeout); if (error == 0) break; @@ -216,14 +228,14 @@ static int socket_connect(git_stream *stream) /* Oops, we couldn't connect to any address */ if (s == INVALID_SOCKET) { if (error == GIT_TIMEOUT) - git_error_set(GIT_ERROR_NET, "failed to connect to %s: Operation timed out", st->host); + git_error_set(GIT_ERROR_NET, "failed to connect to %s: Operation timed out", host); else - git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); + git_error_set(GIT_ERROR_OS, "failed to connect to %s", host); error = -1; goto done; } - if (st->parent.timeout && !st->parent.connect_timeout && + if (st->timeout && !st->connect_timeout && (error = set_nonblocking(s)) < 0) return error; @@ -235,6 +247,22 @@ done: return error; } +static int socket_wrap(git_stream *stream, git_stream *in, const char *host) +{ + GIT_UNUSED(stream); + GIT_UNUSED(in); + GIT_UNUSED(host); + + git_error_set(GIT_ERROR_NET, "cannot wrap a plaintext socket"); + return -1; +} + +static git_socket_t socket_get(git_stream *stream) +{ + git_stream_socket *st = (git_stream_socket *) stream; + return st->s; +} + static ssize_t socket_write( git_stream *stream, const char *data, @@ -250,13 +278,13 @@ static ssize_t socket_write( ret = p_send(st->s, data, len, 0); - if (st->parent.timeout && ret < 0 && + if (st->timeout && ret < 0 && (errno == EAGAIN || errno != EWOULDBLOCK)) { fd.fd = st->s; fd.events = POLLOUT; fd.revents = 0; - ret = p_poll(&fd, 1, st->parent.timeout); + ret = p_poll(&fd, 1, st->timeout); if (ret == 1) { ret = p_send(st->s, data, len, 0); @@ -286,13 +314,13 @@ static ssize_t socket_read( ret = p_recv(st->s, data, len, 0); - if (st->parent.timeout && ret < 0 && + if (st->timeout && ret < 0 && (errno == EAGAIN || errno != EWOULDBLOCK)) { fd.fd = st->s; fd.events = POLLIN; fd.revents = 0; - ret = p_poll(&fd, 1, st->parent.timeout); + ret = p_poll(&fd, 1, st->timeout); if (ret == 1) { ret = p_recv(st->s, data, len, 0); @@ -331,32 +359,19 @@ static void socket_free(git_stream *stream) git__free(st); } -static int default_socket_stream_new( - git_stream **out, - const char *host, - const char *port) +static int default_socket_stream_new(git_stream **out) { git_stream_socket *st; GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); st = git__calloc(1, sizeof(git_stream_socket)); 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.timeout = git_stream_socket__timeout; - st->parent.connect_timeout = git_stream_socket__connect_timeout; st->parent.connect = socket_connect; + st->parent.wrap = socket_wrap; + st->parent.get_socket = socket_get; st->parent.write = socket_write; st->parent.read = socket_read; st->parent.close = socket_close; @@ -367,18 +382,13 @@ static int default_socket_stream_new( return 0; } -int git_stream_socket_new( - git_stream **out, - const char *host, - const char *port) +int git_stream_socket_new(git_stream **out) { - int (*init)(git_stream **, const char *, const char *) = NULL; + int (*init)(git_stream **) = 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; @@ -392,7 +402,7 @@ int git_stream_socket_new( return -1; } - return init(out, host, port); + return init(out); } #ifdef GIT_WIN32 diff --git a/src/util/streams/socket.h b/src/util/streams/socket.h index e5d6b1249..dbc4aa3a5 100644 --- a/src/util/streams/socket.h +++ b/src/util/streams/socket.h @@ -11,7 +11,7 @@ #include "stream.h" -extern int git_stream_socket_new(git_stream **out, const char *host, const char *port); +extern int git_stream_socket_new(git_stream **out); extern int git_stream_socket_global_init(void); diff --git a/src/util/streams/tls.c b/src/util/streams/tls.c index d49d1145a..d19369643 100644 --- a/src/util/streams/tls.c +++ b/src/util/streams/tls.c @@ -15,15 +15,13 @@ #include "streams/securetransport.h" #include "streams/schannel.h" -int git_stream_tls_new(git_stream **out, const char *host, const char *port) +int git_stream_tls_new(git_stream **out) { - int (*init)(git_stream **, const char *, const char *) = NULL; + int (*init)(git_stream **) = 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; @@ -46,35 +44,5 @@ int git_stream_tls_new(git_stream **out, const char *host, const char *port) return -1; } - return init(out, host, port); -} - -int git_stream_tls_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 { -#if defined(GIT_HTTPS_SECURETRANSPORT) - wrap = git_stream_securetransport_wrap; -#elif defined(GIT_HTTPS_OPENSSL) - wrap = git_stream_openssl_wrap; -#elif defined(GIT_HTTPS_MBEDTLS) - wrap = git_stream_mbedtls_wrap; -#elif defined(GIT_HTTPS_SCHANNEL) - wrap = git_stream_schannel_wrap; -#endif - } - - if (!wrap) { - git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); - return -1; - } - - return wrap(out, in, host); + return init(out); } diff --git a/src/util/streams/tls.h b/src/util/streams/tls.h index e4cffecac..20bdcbb45 100644 --- a/src/util/streams/tls.h +++ b/src/util/streams/tls.h @@ -16,16 +16,6 @@ * the current platform, whether that's SecureTransport on macOS, * OpenSSL or mbedTLS on other Unixes, or something else entirely. */ -extern int git_stream_tls_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 upgrade an existing socket to add TLS -- for - * example, creating a CONNECT stream on top of an existing HTTP - * connection. - */ -extern int git_stream_tls_wrap(git_stream **out, git_stream *in, const char *host); +extern int git_stream_tls_new(git_stream **out); #endif diff --git a/tests/libgit2/core/useragent.c b/tests/libgit2/core/useragent.c index a4ece902f..4f69ba7e7 100644 --- a/tests/libgit2/core/useragent.c +++ b/tests/libgit2/core/useragent.c @@ -1,14 +1,15 @@ #include "clar_libgit2.h" -#include "settings.h" + +extern char *git_http__user_agent; void test_core_useragent__get(void) { const char *custom_name = "super duper git"; git_str buf = GIT_STR_INIT; - cl_assert_equal_p(NULL, git_libgit2__user_agent()); + cl_assert_equal_p(NULL, git_http__user_agent); cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); - cl_assert_equal_s(custom_name, git_libgit2__user_agent()); + cl_assert_equal_s(custom_name, git_http__user_agent); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); cl_assert_equal_s(custom_name, buf.ptr); diff --git a/tests/libgit2/stream/registration.c b/tests/libgit2/stream/registration.c index 4d9171204..c92d21e1d 100644 --- a/tests/libgit2/stream/registration.c +++ b/tests/libgit2/stream/registration.c @@ -12,22 +12,8 @@ void test_stream_registration__cleanup(void) cl_git_pass(git_stream_register(GIT_STREAM_TLS | GIT_STREAM_STANDARD, NULL)); } -static int test_stream_init(git_stream **out, const char *host, const char *port) +static int test_stream_init(git_stream **out) { - GIT_UNUSED(host); - GIT_UNUSED(port); - - ctor_called = 1; - *out = &test_stream; - - 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; @@ -41,18 +27,17 @@ void test_stream_registration__insecure(void) registration.version = 1; registration.init = test_stream_init; - registration.wrap = test_stream_wrap; ctor_called = 0; cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, ®istration)); - cl_git_pass(git_stream_socket_new(&stream, "localhost", "80")); + cl_git_pass(git_stream_socket_new(&stream)); cl_assert_equal_i(1, ctor_called); cl_assert_equal_p(&test_stream, stream); ctor_called = 0; stream = NULL; cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, NULL)); - cl_git_pass(git_stream_socket_new(&stream, "localhost", "80")); + cl_git_pass(git_stream_socket_new(&stream)); cl_assert_equal_i(0, ctor_called); cl_assert(&test_stream != stream); @@ -68,18 +53,17 @@ void test_stream_registration__tls(void) registration.version = 1; registration.init = test_stream_init; - registration.wrap = test_stream_wrap; ctor_called = 0; cl_git_pass(git_stream_register(GIT_STREAM_TLS, ®istration)); - cl_git_pass(git_stream_tls_new(&stream, "localhost", "443")); + cl_git_pass(git_stream_tls_new(&stream)); cl_assert_equal_i(1, ctor_called); cl_assert_equal_p(&test_stream, stream); ctor_called = 0; stream = NULL; cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL)); - error = git_stream_tls_new(&stream, "localhost", "443"); + error = git_stream_tls_new(&stream); /* We don't have TLS support enabled, or we're on Windows * with WinHTTP, which is not actually TLS stream support. @@ -103,17 +87,16 @@ void test_stream_registration__both(void) registration.version = 1; registration.init = test_stream_init; - registration.wrap = test_stream_wrap; cl_git_pass(git_stream_register(GIT_STREAM_STANDARD | GIT_STREAM_TLS, ®istration)); ctor_called = 0; - cl_git_pass(git_stream_tls_new(&stream, "localhost", "443")); + cl_git_pass(git_stream_tls_new(&stream)); cl_assert_equal_i(1, ctor_called); cl_assert_equal_p(&test_stream, stream); ctor_called = 0; - cl_git_pass(git_stream_socket_new(&stream, "localhost", "80")); + cl_git_pass(git_stream_socket_new(&stream)); cl_assert_equal_i(1, ctor_called); cl_assert_equal_p(&test_stream, stream); }