Blame src/stransport_stream.c

Packit Service 20376f
/*
Packit Service 20376f
 * Copyright (C) the libgit2 contributors. All rights reserved.
Packit Service 20376f
 *
Packit Service 20376f
 * This file is part of libgit2, distributed under the GNU GPL v2 with
Packit Service 20376f
 * a Linking Exception. For full terms see the included COPYING file.
Packit Service 20376f
 */
Packit Service 20376f
Packit Service 20376f
#ifdef GIT_SECURE_TRANSPORT
Packit Service 20376f
Packit Service 20376f
#include <CoreFoundation/CoreFoundation.h>
Packit Service 20376f
#include <Security/SecureTransport.h>
Packit Service 20376f
#include <Security/SecCertificate.h>
Packit Service 20376f
Packit Service 20376f
#include "git2/transport.h"
Packit Service 20376f
Packit Service 20376f
#include "socket_stream.h"
Packit Service 20376f
#include "curl_stream.h"
Packit Service 20376f
Packit Service 20376f
static int stransport_error(OSStatus ret)
Packit Service 20376f
{
Packit Service 20376f
	CFStringRef message;
Packit Service 20376f
Packit Service 20376f
	if (ret == noErr || ret == errSSLClosedGraceful) {
Packit Service 20376f
		giterr_clear();
Packit Service 20376f
		return 0;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
#if !TARGET_OS_IPHONE
Packit Service 20376f
	message = SecCopyErrorMessageString(ret, NULL);
Packit Service 20376f
	GITERR_CHECK_ALLOC(message);
Packit Service 20376f
Packit Service 20376f
	giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
Packit Service 20376f
	CFRelease(message);
Packit Service 20376f
#else
Packit Service 20376f
    giterr_set(GITERR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
Packit Service 20376f
    GIT_UNUSED(message);
Packit Service 20376f
#endif
Packit Service 20376f
Packit Service 20376f
	return -1;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
typedef struct {
Packit Service 20376f
	git_stream parent;
Packit Service 20376f
	git_stream *io;
Packit Service 20376f
	SSLContextRef ctx;
Packit Service 20376f
	CFDataRef der_data;
Packit Service 20376f
	git_cert_x509 cert_info;
Packit Service 20376f
} stransport_stream;
Packit Service 20376f
Packit Service 20376f
static int stransport_connect(git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
	int error;
Packit Service 20376f
	SecTrustRef trust = NULL;
Packit Service 20376f
	SecTrustResultType sec_res;
Packit Service 20376f
	OSStatus ret;
Packit Service 20376f
Packit Service 20376f
	if ((error = git_stream_connect(st->io)) < 0)
Packit Service 20376f
		return error;
Packit Service 20376f
Packit Service 20376f
	ret = SSLHandshake(st->ctx);
Packit Service 20376f
	if (ret != errSSLServerAuthCompleted) {
Packit Service 20376f
		giterr_set(GITERR_SSL, "unexpected return value from ssl handshake %d", ret);
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
Packit Service 20376f
		goto on_error;
Packit Service 20376f
Packit Service 20376f
	if (!trust)
Packit Service 20376f
		return GIT_ECERTIFICATE;
Packit Service 20376f
Packit Service 20376f
	if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr)
Packit Service 20376f
		goto on_error;
Packit Service 20376f
Packit Service 20376f
	CFRelease(trust);
Packit Service 20376f
Packit Service 20376f
	if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) {
Packit Service 20376f
		giterr_set(GITERR_SSL, "internal security trust error");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure ||
Packit Service 20376f
	    sec_res == kSecTrustResultFatalTrustFailure) {
Packit Service 20376f
		giterr_set(GITERR_SSL, "untrusted connection error");
Packit Service 20376f
		return GIT_ECERTIFICATE;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
Packit Service 20376f
on_error:
Packit Service 20376f
	if (trust)
Packit Service 20376f
		CFRelease(trust);
Packit Service 20376f
Packit Service 20376f
	return stransport_error(ret);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int stransport_certificate(git_cert **out, git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
	SecTrustRef trust = NULL;
Packit Service 20376f
	SecCertificateRef sec_cert;
Packit Service 20376f
	OSStatus ret;
Packit Service 20376f
Packit Service 20376f
	if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
Packit Service 20376f
		return stransport_error(ret);
Packit Service 20376f
Packit Service 20376f
	sec_cert = SecTrustGetCertificateAtIndex(trust, 0);
Packit Service 20376f
	st->der_data = SecCertificateCopyData(sec_cert);
Packit Service 20376f
	CFRelease(trust);
Packit Service 20376f
Packit Service 20376f
	if (st->der_data == NULL) {
Packit Service 20376f
		giterr_set(GITERR_SSL, "retrieved invalid certificate data");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	st->cert_info.parent.cert_type = GIT_CERT_X509;
Packit Service 20376f
	st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data);
Packit Service 20376f
	st->cert_info.len = CFDataGetLength(st->der_data);
Packit Service 20376f
Packit Service 20376f
	*out = (git_cert *)&st->cert_info;
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int stransport_set_proxy(
Packit Service 20376f
	git_stream *stream,
Packit Service 20376f
	const git_proxy_options *proxy_opts)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	return git_stream_set_proxy(st->io, proxy_opts);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
/*
Packit Service 20376f
 * Contrary to typical network IO callbacks, Secure Transport write callback is
Packit Service 20376f
 * expected to write *all* passed data, not just as much as it can, and any
Packit Service 20376f
 * other case would be considered a failure.
Packit Service 20376f
 *
Packit Service 20376f
 * This behavior is actually not specified in the Apple documentation, but is
Packit Service 20376f
 * required for things to work correctly (and incidentally, that's also how
Packit Service 20376f
 * Apple implements it in its projects at opensource.apple.com).
Packit Service 20376f
 *
Packit Service 20376f
 * Libgit2 streams happen to already have this very behavior so this is just
Packit Service 20376f
 * passthrough.
Packit Service 20376f
 */
Packit Service 20376f
static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len)
Packit Service 20376f
{
Packit Service 20376f
	git_stream *io = (git_stream *) conn;
Packit Service 20376f
Packit Service 20376f
	if (git_stream_write(io, data, *len, 0) < 0) {
Packit Service 20376f
		return -36; /* "ioErr" from MacErrors.h which is not available on iOS */
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return noErr;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
	size_t data_len, processed;
Packit Service 20376f
	OSStatus ret;
Packit Service 20376f
Packit Service 20376f
	GIT_UNUSED(flags);
Packit Service 20376f
Packit Service 20376f
	data_len = len;
Packit Service 20376f
	if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr)
Packit Service 20376f
		return stransport_error(ret);
Packit Service 20376f
Packit Service 20376f
	return processed;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
/*
Packit Service 20376f
 * Contrary to typical network IO callbacks, Secure Transport read callback is
Packit Service 20376f
 * expected to read *exactly* the requested number of bytes, not just as much
Packit Service 20376f
 * as it can, and any other case would be considered a failure.
Packit Service 20376f
 *
Packit Service 20376f
 * This behavior is actually not specified in the Apple documentation, but is
Packit Service 20376f
 * required for things to work correctly (and incidentally, that's also how
Packit Service 20376f
 * Apple implements it in its projects at opensource.apple.com).
Packit Service 20376f
 */
Packit Service 20376f
static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len)
Packit Service 20376f
{
Packit Service 20376f
	git_stream *io = (git_stream *) conn;
Packit Service 20376f
	OSStatus error = noErr;
Packit Service 20376f
	size_t off = 0;
Packit Service 20376f
	ssize_t ret;
Packit Service 20376f
Packit Service 20376f
	do {
Packit Service 20376f
		ret = git_stream_read(io, data + off, *len - off);
Packit Service 20376f
		if (ret < 0) {
Packit Service 20376f
			error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */
Packit Service 20376f
			break;
Packit Service 20376f
		}
Packit Service 20376f
		if (ret == 0) {
Packit Service 20376f
			error = errSSLClosedGraceful;
Packit Service 20376f
			break;
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		off += ret;
Packit Service 20376f
	} while (off < *len);
Packit Service 20376f
Packit Service 20376f
	*len = off;
Packit Service 20376f
	return error;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static ssize_t stransport_read(git_stream *stream, void *data, size_t len)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
	size_t processed;
Packit Service 20376f
	OSStatus ret;
Packit Service 20376f
Packit Service 20376f
	if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr)
Packit Service 20376f
		return stransport_error(ret);
Packit Service 20376f
Packit Service 20376f
	return processed;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int stransport_close(git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
	OSStatus ret;
Packit Service 20376f
Packit Service 20376f
	ret = SSLClose(st->ctx);
Packit Service 20376f
	if (ret != noErr && ret != errSSLClosedGraceful)
Packit Service 20376f
		return stransport_error(ret);
Packit Service 20376f
Packit Service 20376f
	return git_stream_close(st->io);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static void stransport_free(git_stream *stream)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st = (stransport_stream *) stream;
Packit Service 20376f
Packit Service 20376f
	git_stream_free(st->io);
Packit Service 20376f
	CFRelease(st->ctx);
Packit Service 20376f
	if (st->der_data)
Packit Service 20376f
		CFRelease(st->der_data);
Packit Service 20376f
	git__free(st);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
Packit Service 20376f
{
Packit Service 20376f
	stransport_stream *st;
Packit Service 20376f
	int error;
Packit Service 20376f
	OSStatus ret;
Packit Service 20376f
Packit Service 20376f
	assert(out && host);
Packit Service 20376f
Packit Service 20376f
	st = git__calloc(1, sizeof(stransport_stream));
Packit Service 20376f
	GITERR_CHECK_ALLOC(st);
Packit Service 20376f
Packit Service 20376f
#ifdef GIT_CURL
Packit Service 20376f
	error = git_curl_stream_new(&st->io, host, port);
Packit Service 20376f
#else
Packit Service 20376f
	error = git_socket_stream_new(&st->io, host, port);
Packit Service 20376f
#endif
Packit Service 20376f
Packit Service 20376f
	if (error < 0){
Packit Service 20376f
		git__free(st);
Packit Service 20376f
		return error;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
Packit Service 20376f
	if (!st->ctx) {
Packit Service 20376f
		giterr_set(GITERR_NET, "failed to create SSL context");
Packit Service 20376f
		git__free(st);
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr ||
Packit Service 20376f
	    (ret = SSLSetConnection(st->ctx, st->io)) != noErr ||
Packit Service 20376f
	    (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr ||
Packit Service 20376f
	    (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr ||
Packit Service 20376f
	    (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr ||
Packit Service 20376f
	    (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) {
Packit Service 20376f
		CFRelease(st->ctx);
Packit Service 20376f
		git__free(st);
Packit Service 20376f
		return stransport_error(ret);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	st->parent.version = GIT_STREAM_VERSION;
Packit Service 20376f
	st->parent.encrypted = 1;
Packit Service 20376f
	st->parent.proxy_support = git_stream_supports_proxy(st->io);
Packit Service 20376f
	st->parent.connect = stransport_connect;
Packit Service 20376f
	st->parent.certificate = stransport_certificate;
Packit Service 20376f
	st->parent.set_proxy = stransport_set_proxy;
Packit Service 20376f
	st->parent.read = stransport_read;
Packit Service 20376f
	st->parent.write = stransport_write;
Packit Service 20376f
	st->parent.close = stransport_close;
Packit Service 20376f
	st->parent.free = stransport_free;
Packit Service 20376f
Packit Service 20376f
	*out = (git_stream *) st;
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
#endif