From 6bb54cbff3636e42ae2523afeee08079e5bd1d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 2 Nov 2014 13:23:32 +0100 Subject: [PATCH] Add a SecureTransport TLS channel As an alternative to OpenSSL when we're on OS X. This one can actually take advantage of stacking the streams. --- CMakeLists.txt | 30 +++- cmake/Modules/FindCoreFoundation.cmake | 9 + cmake/Modules/FindSecurity.cmake | 9 + src/settings.c | 2 +- src/stransport_stream.c | 219 +++++++++++++++++++++++++ src/stransport_stream.h | 14 ++ src/transport.c | 2 +- src/transports/http.c | 2 +- tests/core/features.c | 2 +- 9 files changed, 283 insertions(+), 6 deletions(-) create mode 100644 cmake/Modules/FindCoreFoundation.cmake create mode 100644 cmake/Modules/FindSecurity.cmake create mode 100644 src/stransport_stream.c create mode 100644 src/stransport_stream.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c3ba504c..2c1430356 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,6 @@ OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) OPTION( ANDROID "Build for android NDK" OFF ) -OPTION( USE_OPENSSL "Link with and use openssl library" ON ) OPTION( USE_ICONV "Link with and use iconv library" OFF ) OPTION( USE_SSH "Link with libssh to enable SSH support" ON ) OPTION( USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF ) @@ -44,6 +43,8 @@ OPTION( VALGRIND "Configure build for valgrind" OFF ) IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") SET( USE_ICONV ON ) + FIND_PACKAGE(Security) + FIND_PACKAGE(CoreFoundation REQUIRED) ENDIF() IF(MSVC) @@ -68,6 +69,7 @@ IF(MSVC) ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE) ENDIF() + IF(WIN32) # By default, libgit2 is built with WinHTTP. To use the built-in # HTTP transport, invoke CMake with the "-DWINHTTP=OFF" argument. @@ -79,6 +81,10 @@ IF(MSVC) OPTION(MSVC_CRTDBG "Enable CRTDBG memory leak reporting" OFF) ENDIF() +IF (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + OPTION( USE_OPENSSL "Link with and use openssl library" ON ) +ENDIF() + # This variable will contain the libraries we need to put into # libgit2.pc's Requires.private. That is, what we're linking to or # what someone who's statically linking us needs to link to. @@ -148,6 +154,15 @@ STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "$ # Find required dependencies INCLUDE_DIRECTORIES(src include) +IF (SECURITY_FOUND) + MESSAGE("-- Found Security ${SECURITY_DIRS}") +ENDIF() + +IF (COREFOUNDATION_FOUND) + MESSAGE("-- Found CoreFoundation ${COREFOUNDATION_DIRS}") +ENDIF() + + IF (WIN32 AND EMBED_SSH_PATH) FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") INCLUDE_DIRECTORIES("${EMBED_SSH_PATH}/include") @@ -415,12 +430,19 @@ ELSE() # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE ENDIF() +IF (SECURITY_FOUND) + ADD_DEFINITIONS(-DGIT_SECURE_TRANSPORT) + INCLUDE_DIRECTORIES(${SECURITY_INCLUDE_DIR}) +ENDIF () + IF (OPENSSL_FOUND) ADD_DEFINITIONS(-DGIT_SSL) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) ENDIF() + + IF (THREADSAFE) IF (NOT WIN32) FIND_PACKAGE(Threads REQUIRED) @@ -459,6 +481,8 @@ ENDIF() # Compile and link libgit2 ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1} ${WIN_RC}) +TARGET_LINK_LIBRARIES(git2 ${SECURITY_DIRS}) +TARGET_LINK_LIBRARIES(git2 ${COREFOUNDATION_DIRS}) TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES}) TARGET_LINK_LIBRARIES(git2 ${GSSAPI_LIBRARIES}) @@ -527,6 +551,8 @@ IF (BUILD_CLAR) ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1}) + TARGET_LINK_LIBRARIES(libgit2_clar ${COREFOUNDATION_DIRS}) + TARGET_LINK_LIBRARIES(libgit2_clar ${SECURITY_DIRS}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${GSSAPI_LIBRARIES}) @@ -540,7 +566,7 @@ IF (BUILD_CLAR) ENDIF () ENABLE_TESTING() - IF (WINHTTP OR OPENSSL_FOUND) + IF (WINHTTP OR OPENSSL_FOUND OR SECURITY_FOUND) ADD_TEST(libgit2_clar libgit2_clar -ionline) ELSE () ADD_TEST(libgit2_clar libgit2_clar -v) diff --git a/cmake/Modules/FindCoreFoundation.cmake b/cmake/Modules/FindCoreFoundation.cmake new file mode 100644 index 000000000..ebd619a53 --- /dev/null +++ b/cmake/Modules/FindCoreFoundation.cmake @@ -0,0 +1,9 @@ +IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_DIRS) + SET(COREFOUNDATION_FOUND TRUE) +ELSE () + FIND_PATH(COREFOUNDATION_INCLUDE_DIR NAMES CoreFoundation.h) + FIND_LIBRARY(COREFOUNDATION_DIRS NAMES CoreFoundation) + IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_DIRS) + SET(COREFOUNDATION_FOUND TRUE) + ENDIF () +ENDIF () diff --git a/cmake/Modules/FindSecurity.cmake b/cmake/Modules/FindSecurity.cmake new file mode 100644 index 000000000..0decdde92 --- /dev/null +++ b/cmake/Modules/FindSecurity.cmake @@ -0,0 +1,9 @@ +IF (SECURITY_INCLUDE_DIR AND SECURITY_DIRS) + SET(SECURITY_FOUND TRUE) +ELSE () + FIND_PATH(SECURITY_INCLUDE_DIR NAMES Security/Security.h) + FIND_LIBRARY(SECURITY_DIRS NAMES Security) + IF (SECURITY_INCLUDE_DIR AND SECURITY_DIRS) + SET(SECURITY_FOUND TRUE) + ENDIF () +ENDIF () diff --git a/src/settings.c b/src/settings.c index 971b50935..77a5f2ed1 100644 --- a/src/settings.c +++ b/src/settings.c @@ -28,7 +28,7 @@ int git_libgit2_features() #ifdef GIT_THREADS | GIT_FEATURE_THREADS #endif -#if defined(GIT_SSL) || defined(GIT_WINHTTP) +#if defined(GIT_SSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) | GIT_FEATURE_HTTPS #endif #if defined(GIT_SSH) diff --git a/src/stransport_stream.c b/src/stransport_stream.c new file mode 100644 index 000000000..09cc7cb00 --- /dev/null +++ b/src/stransport_stream.c @@ -0,0 +1,219 @@ +/* + * 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. + */ + +#ifdef GIT_SECURE_TRANSPORT + +#include +#include +#include + +#include "git2/transport.h" + +#include "socket_stream.h" + +int stransport_error(OSStatus ret) +{ + switch (ret) { + case noErr: + giterr_clear(); + return 0; + case errSSLXCertChainInvalid: + case errSSLBadCert: + return GIT_ECERTIFICATE; + default: + giterr_set(GITERR_NET, "SecureTransport error %d", ret); + return -1; + } +} + +typedef struct { + git_stream parent; + git_stream *io; + SSLContextRef ctx; + CFDataRef der_data; + git_cert_x509 cert_info; +} stransport_stream; + +int stransport_connect(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + int error; + OSStatus ret; + + if ((error = git_stream_connect(st->io)) < 0) + return error; + + if ((ret = SSLHandshake(st->ctx)) != noErr) + return stransport_error(ret); + + return 0; +} + +int stransport_certificate(git_cert **out, git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + SecTrustRef trust = NULL; + SecCertificateRef sec_cert; + SecTrustResultType sec_res; + OSStatus ret; + + if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) + return stransport_error(ret); + + if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr) + return stransport_error(ret); + + sec_cert = SecTrustGetCertificateAtIndex(trust, 0); + st->der_data = SecCertificateCopyData(sec_cert); + CFRelease(trust); + + if (st->der_data == NULL) { + giterr_set(GITERR_SSL, "retrieved invalid certificate data"); + return -1; + } + + st->cert_info.cert_type = GIT_CERT_X509; + st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data); + st->cert_info.len = CFDataGetLength(st->der_data); + + *out = (git_cert *)&st->cert_info; + return 0; +} + +static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) +{ + git_stream *io = (git_stream *) conn; + ssize_t ret; + + ret = git_stream_write(io, data, *len, 0); + if (ret < 0) { + *len = 0; + return -1; + } + + *len = ret; + + return noErr; +} + +ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags) +{ + stransport_stream *st = (stransport_stream *) stream; + size_t data_len, processed; + OSStatus ret; + + GIT_UNUSED(flags); + + data_len = len; + if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) + return stransport_error(ret); + + return processed; +} + +static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) +{ + git_stream *io = (git_stream *) conn; + ssize_t ret; + size_t left, requested; + + requested = left = *len; + do { + ret = git_stream_read(io, data + (requested - left), left); + if (ret < 0) { + *len = 0; + return -1; + } + + left -= ret; + } while (left); + + *len = requested; + + if (ret == 0) + return errSSLClosedGraceful; + + return noErr; +} + +ssize_t stransport_read(git_stream *stream, void *data, size_t len) +{ + stransport_stream *st = (stransport_stream *) stream; + size_t processed; + OSStatus ret; + + if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) + return stransport_error(ret); + + return processed; +} + +int stransport_close(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + OSStatus ret; + + if ((ret = SSLClose(st->ctx)) != noErr) + return stransport_error(ret); + + return git_stream_close(st->io); +} + +void stransport_free(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + + 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) +{ + stransport_stream *st; + int error; + OSStatus ret; + + assert(out && host); + + st = git__calloc(1, sizeof(stransport_stream)); + GITERR_CHECK_ALLOC(st); + + if ((error = git_socket_stream_new(&st->io, host, port)) < 0){ + git__free(st); + return error; + } + + st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); + if (!st->ctx) { + giterr_set(GITERR_NET, "failed to create SSL context"); + return -1; + } + + if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || + (ret = SSLSetConnection(st->ctx, st->io)) != noErr || + (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) { + git_stream_free((git_stream *)st); + return stransport_error(ret); + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.connect = stransport_connect; + st->parent.certificate = stransport_certificate; + st->parent.read = stransport_read; + st->parent.write = stransport_write; + st->parent.close = stransport_close; + st->parent.free = stransport_free; + + *out = (git_stream *) st; + return 0; +} + +#endif diff --git a/src/stransport_stream.h b/src/stransport_stream.h new file mode 100644 index 000000000..714f90273 --- /dev/null +++ b/src/stransport_stream.h @@ -0,0 +1,14 @@ +/* + * 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_stransport_stream_h__ +#define INCLUDE_stransport_stream_h__ + +#include "git2/sys/stream.h" + +extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port); + +#endif diff --git a/src/transport.c b/src/transport.c index fc9c692b8..6266fdd34 100644 --- a/src/transport.c +++ b/src/transport.c @@ -29,7 +29,7 @@ static transport_definition local_transport_definition = { "file://", git_transp static transport_definition transports[] = { { "git://", git_transport_smart, &git_subtransport_definition }, { "http://", git_transport_smart, &http_subtransport_definition }, -#if defined(GIT_SSL) || defined(GIT_WINHTTP) +#if defined(GIT_SSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) { "https://", git_transport_smart, &http_subtransport_definition }, #endif { "file://", git_transport_local, NULL }, diff --git a/src/transports/http.c b/src/transports/http.c index 6b100df7a..264c9c512 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -557,7 +557,7 @@ static int http_connect(http_subtransport *t) error = git_stream_connect(t->io); -#ifdef GIT_SSL +#if defined(GIT_SSL) || defined(GIT_SECURE_TRANSPORT) if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL && git_stream_is_encrypted(t->io)) { git_cert *cert; diff --git a/tests/core/features.c b/tests/core/features.c index 3ce02f4d6..e6d0e0f51 100644 --- a/tests/core/features.c +++ b/tests/core/features.c @@ -17,7 +17,7 @@ void test_core_features__0(void) cl_assert((caps & GIT_FEATURE_THREADS) == 0); #endif -#if defined(GIT_SSL) || defined(GIT_WINHTTP) +#if defined(GIT_SSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) cl_assert((caps & GIT_FEATURE_HTTPS) != 0); #else cl_assert((caps & GIT_FEATURE_HTTPS) == 0);