Commit dbb45950 by Edward Thomson

Merge pull request #2997 from libgit2/cmn/secure-transport

Use SecureTransport on OS X
parents 69c333f9 44b769e4
......@@ -31,6 +31,9 @@ v0.22 + 1
* `git_rebase_commit` now returns `GIT_EUNMERGED` when you attempt to
commit with unstaged changes.
* On Mac OS X, we now use SecureTransport to provide the cryptographic
support for HTTPS connections insead of OpenSSL.
### API additions
* The `git_merge_options` gained a `file_flags` member.
......
......@@ -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)
ADD_DEFINITIONS(-DGIT_OPENSSL)
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)
......
......@@ -41,12 +41,22 @@ both of which are thread-safe. You do not need to do anything special.
When using libssh2 which itself uses WinCNG, there are no special
steps necessary. If you are using a MinGW or similar environment where
libssh2 uses OpenSSL or libgcrypt, then the non-Windows case affects
libssh2 uses OpenSSL or libgcrypt, then the general case affects
you.
Non-Windows
On Mac OS X
-----------
On OS X, the library makes use of CommonCrypto and SecureTransport for
cryptographic support. These are thread-safe and you do not need to do
anything special.
Note that libssh2 may still use OpenSSL itself. In that case, the
general case still affects you if you use ssh.
General Case
------------
On the rest of the platforms, libgit2 uses OpenSSL to be able to use
HTTPS as a transport. This library is made to be thread-implementation
agnostic, and the users of the library must set which locking function
......@@ -71,8 +81,8 @@ See the
[OpenSSL documentation](https://www.openssl.org/docs/crypto/threads.html)
on threading for more details.
Be also aware that libgit2 may not always link against OpenSSL in the
future if there are alternatives provided by the system.
Be also aware that libgit2 does not always link against OpenSSL
if there are alternatives provided by the system.
libssh2 may be linked against OpenSSL or libgcrypt. If it uses
OpenSSL, you only need to set up threading for OpenSSL once and the
......
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 ()
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 ()
......@@ -17,7 +17,7 @@ git_mutex git__mwindow_mutex;
#define MAX_SHUTDOWN_CB 8
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
# include <openssl/ssl.h>
SSL_CTX *git__ssl_ctx;
# ifdef GIT_THREADS
......@@ -57,7 +57,7 @@ static void git__shutdown(void)
}
}
#if defined(GIT_THREADS) && defined(GIT_SSL)
#if defined(GIT_THREADS) && defined(GIT_OPENSSL)
void openssl_locking_function(int mode, int n, const char *file, int line)
{
int lock;
......@@ -89,7 +89,7 @@ static void shutdown_ssl_locking(void)
static void init_ssl(void)
{
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
/* Older OpenSSL and MacOS OpenSSL doesn't have this */
......@@ -118,7 +118,7 @@ static void init_ssl(void)
int git_openssl_set_locking(void)
{
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
# ifdef GIT_THREADS
int num_locks, i;
......
......@@ -17,7 +17,7 @@ typedef struct {
char oid_fmt[GIT_OID_HEXSZ+1];
} git_global_st;
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
# include <openssl/ssl.h>
extern SSL_CTX *git__ssl_ctx;
#endif
......
......@@ -11,12 +11,12 @@
#include "common.h"
#include "stream.h"
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
# include <openssl/ssl.h>
#endif
typedef struct gitno_ssl {
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
SSL *ssl;
#else
size_t dummy;
......
......@@ -5,7 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
#include <ctype.h>
......@@ -374,6 +374,10 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(out);
GIT_UNUSED(host);
GIT_UNUSED(port);
giterr_set(GITERR_SSL, "openssl is not supported in this version");
return -1;
}
......
......@@ -5,7 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
# include <openssl/err.h>
#endif
......@@ -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_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT)
| GIT_FEATURE_HTTPS
#endif
#if defined(GIT_SSH)
......@@ -138,7 +138,7 @@ int git_libgit2_opts(int key, ...)
break;
case GIT_OPT_SET_SSL_CERT_LOCATIONS:
#ifdef GIT_SSL
#ifdef GIT_OPENSSL
{
const char *file = va_arg(ap, const char *);
const char *path = va_arg(ap, const char *);
......
/*
* 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 <CoreFoundation/CoreFoundation.h>
#include <Security/SecureTransport.h>
#include <Security/SecCertificate.h>
#include "git2/transport.h"
#include "socket_stream.h"
int stransport_error(OSStatus ret)
{
CFStringRef message;
if (ret == noErr || ret == errSSLClosedGraceful) {
giterr_clear();
return 0;
}
message = SecCopyErrorMessageString(ret, NULL);
GITERR_CHECK_ALLOC(message);
giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
CFRelease(message);
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;
SecTrustRef trust = NULL;
SecTrustResultType sec_res;
OSStatus ret;
if ((error = git_stream_connect(st->io)) < 0)
return error;
ret = SSLHandshake(st->ctx);
if (ret != errSSLServerAuthCompleted) {
giterr_set(GITERR_SSL, "unexpected return value from ssl handshake %d", ret);
return -1;
}
if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
goto on_error;
if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr)
goto on_error;
CFRelease(trust);
if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) {
giterr_set(GITERR_SSL, "internal security trust error");
return -1;
}
if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure ||
sec_res == kSecTrustResultFatalTrustFailure)
return GIT_ECERTIFICATE;
return 0;
on_error:
if (trust)
CFRelease(trust);
return stransport_error(ret);
}
int stransport_certificate(git_cert **out, git_stream *stream)
{
stransport_stream *st = (stransport_stream *) stream;
SecTrustRef trust = NULL;
SecCertificateRef sec_cert;
OSStatus ret;
if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
return stransport_error(ret);
sec_cert = SecTrustGetCertificateAtIndex(trust, 0);
st->der_data = SecCertificateCopyData(sec_cert);
CFRelease(trust);
if (st->der_data == NULL) {
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;
ret = SSLClose(st->ctx);
if (ret != noErr && ret != errSSLClosedGraceful)
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 = 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) {
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
/*
* 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
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2/errors.h"
#include "common.h"
#include "openssl_stream.h"
#include "stransport_stream.h"
int git_tls_stream_new(git_stream **out, const char *host, const char *port)
{
#ifdef GIT_SECURE_TRANSPORT
return git_stransport_stream_new(out, host, port);
#elif defined(GIT_OPENSSL)
return git_openssl_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;
#endif
}
/*
* 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_tls_stream_h__
#define INCLUDE_tls_stream_h__
#include "git2/sys/stream.h"
/**
* 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.
*/
extern int git_tls_stream_new(git_stream **out, const char *host, const char *port);
#endif
......@@ -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_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT)
{ "https://", git_transport_smart, &http_subtransport_definition },
#endif
{ "file://", git_transport_local, NULL },
......
......@@ -13,7 +13,7 @@
#include "smart.h"
#include "auth.h"
#include "auth_negotiate.h"
#include "openssl_stream.h"
#include "tls_stream.h"
#include "socket_stream.h"
git_http_auth_scheme auth_schemes[] = {
......@@ -545,7 +545,7 @@ static int http_connect(http_subtransport *t)
}
if (t->connection_data.use_ssl) {
error = git_openssl_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
} else {
error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
}
......@@ -557,7 +557,7 @@ static int http_connect(http_subtransport *t)
error = git_stream_connect(t->io);
#ifdef GIT_SSL
#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT)
if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
git_stream_is_encrypted(t->io)) {
git_cert *cert;
......
......@@ -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_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT)
cl_assert((caps & GIT_FEATURE_HTTPS) != 0);
#else
cl_assert((caps & GIT_FEATURE_HTTPS) == 0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment