summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2014-11-17 17:02:56 +0100
committerCarlos Martín Nieto <cmn@dwim.me>2014-11-17 20:19:49 +0100
commit7b85608728b38aafd66931ffdcff4e8979dfe3ec (patch)
tree1612c2118142adcbef2e71f27c1da18c4fd68271
parent92e76f40e6239474e320feb42b1dbb9a1da9346e (diff)
downloadlibgit2-cmn/io-stream-backends.tar.gz
Tentative support for GnuTLScmn/io-stream-backends
Add GnuTLS support for the hashing functions as well as start work on a GnuTLS IO stream.
-rw-r--r--CMakeLists.txt10
-rw-r--r--src/global.c14
-rw-r--r--src/gnutls_stream.c165
-rw-r--r--src/hash.h2
-rw-r--r--src/hash/hash_gnutls.h58
5 files changed, 248 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 078bba636..aaa635165 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,6 +155,7 @@ IF (WIN32 AND WINHTTP AND NOT MINGW)
FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
ELSE ()
IF (NOT AMIGA)
+ FIND_PACKAGE(GnuTLS)
FIND_PACKAGE(OpenSSL)
ENDIF ()
@@ -174,6 +175,9 @@ ENDIF()
IF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin")
ADD_DEFINITIONS(-DWIN32_SHA1)
FILE(GLOB SRC_SHA1 src/hash/hash_win32.c)
+ELSEIF (GNUTLS_FOUND AND NOT SHA1_TYPE STREQUAL "bultin")
+ ADD_DEFINITIONS(-DGNUTLS_SHA1)
+ SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} gnutls")
ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin")
ADD_DEFINITIONS(-DOPENSSL_SHA1)
IF (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
@@ -370,7 +374,11 @@ IF (SECURITY_FOUND)
INCLUDE_DIRECTORIES(${SECURITY_INCLUDE_DIR})
ENDIF ()
-IF (OPENSSL_FOUND)
+IF (GNUTLS_FOUND)
+ ADD_DEFINITIONS(-DGIT_GNUTLS)
+ INCLUDE_DIRECTORIES(${GNUTLS_INCLUDE_DIR})
+ SET(SSL_LIBRARIES ${GNUTLS_LIBRARIES})
+ELSEIF(OPENSSL_FOUND)
ADD_DEFINITIONS(-DGIT_SSL)
INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR})
SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES})
diff --git a/src/global.c b/src/global.c
index 006202a2c..9e35ef39c 100644
--- a/src/global.c
+++ b/src/global.c
@@ -25,6 +25,10 @@ static git_mutex *openssl_locks;
# endif
#endif
+#ifdef GIT_GNUTLS
+# include <gnutls/gnutls.h>
+#endif
+
static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
static git_atomic git__n_shutdown_callbacks;
static git_atomic git__n_inits;
@@ -276,6 +280,11 @@ static void init_once(void)
/* OpenSSL needs to be initialized from the main thread */
init_ssl();
+#ifdef GIT_GNUTLS
+ gnutls_global_init();
+ git__on_shutdown(gnutls_global_deinit);
+#endif
+
GIT_MEMORY_BARRIER;
}
@@ -336,6 +345,11 @@ int git_libgit2_init(void)
ssl_inited = 1;
}
+#ifdef GIT_GNUTLS
+ gnutls_global_init();
+ git__on_shutdown(gnutls_global_deinit);
+#endif
+
git_atomic_inc(&git__n_inits);
return 0;
}
diff --git a/src/gnutls_stream.c b/src/gnutls_stream.c
new file mode 100644
index 000000000..37fc1000d
--- /dev/null
+++ b/src/gnutls_stream.c
@@ -0,0 +1,165 @@
+/*
+ * 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_GNUTLS
+
+#include <gnutls/gnutls.h>
+
+#include "stream.h"
+#include "socket_stream.h"
+#include "git2/transport.h"
+
+void set_gnutls_error(int error)
+{
+ giterr_set(GITERR_SSL, "gnutls: %s", gnutls_strerror(error));
+}
+
+typedef struct {
+ git_stream parent;
+ git_socket_stream *socket;
+ gnutls_session_t session;
+ git_cert_x509 cert_info;
+} gnutls_stream;
+
+static int verify_server_cert(gnutls_session_t session, const char *host)
+{
+ int error;
+ unsigned int status = 0;
+
+ if ((error = gnutls_certificate_verify_peers3(session, host, &status)) < 0) {
+ set_gnutls_error(error);
+ return -1;
+ }
+
+ if (!status)
+ return 0;
+ else
+ return GIT_ECERTIFICATE;
+}
+
+static ssize_t gnutls_stream_write(git_stream *stream, void *data, size_t len, int flags)
+{
+ ssize_t ret;
+ size_t off;
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ GIT_UNUSED(flags);
+
+ while (off < len) {
+ ret = gnutls_record_send(st->session, data + off, len - off);
+ if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN)
+ continue; /* try again with the same params */
+
+ if (ret < 0) {
+ set_gnutls_error(ret);
+ return -1;
+ }
+
+ off += ret;
+ }
+
+ return ret;
+}
+
+static ssize_t gnutls_stream_read(git_stream *stream, void *data, size_t len)
+{
+ ssize_t ret;
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ do {
+ ret = gnutls_record_recv(st->session, data, len);
+ } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
+
+ if (ret < 0) {
+ set_gnutls_error(ret);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int gnutls_connect(git_stream *stream)
+{
+ int error;
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ if ((error = git_stream_connect((git_stream *)st->socket)) < 0)
+ return error;
+
+ /* ideally we'd have functions that talk to the socket, but for now */
+ gnutls_transport_set_int(st->session, st->socket->s);
+
+ do {
+ error = gnutls_handshake(st->session);
+ } while (!gnutls_error_is_fatal(error));
+
+ if (error < 0) {
+ set_gnutls_error(error);
+ return -1;
+ }
+
+ return verify_server_cert(st->session, st->socket->host);
+}
+
+static int gnutls_stream_close(git_stream *stream)
+{
+ gnutls_stream *st = (gnutls_stream *) stream;
+ int error;
+
+ do {
+ error = gnutls_bye(st->session, GNUTLS_SHUT_RDWR);
+ } while (error == GNUTLS_E_INTERRUPTED || error == GNUTLS_E_AGAIN);
+
+ return git_stream_close((git_stream *)st->socket);
+}
+
+static void gnutls_stream_free(git_stream *stream)
+{
+ gnutls_stream *st = (gnutls_stream *) stream;
+
+ gnutls_deinit(st->session);
+ git__free(st);
+}
+
+int git_gnutls_stream_new(git_stream **out, const char *host, const char *port)
+{
+ gnutls_stream *st;
+ int error;
+
+ st = git__calloc(1, sizeof(gnutls_stream));
+ GITERR_CHECK_ALLOC(st);
+
+ if (git_socket_stream_new((git_stream **) &st->socket, host, port))
+ return -1;
+
+ if ((error = gnutls_init(&st->session, GNUTLS_CLIENT)) < 0) {
+ git_stream_free((git_stream *) st->socket);
+ git__free(st);
+ return -1;
+ }
+
+ st->parent.encrypted = 1;
+ st->parent.connect = gnutls_connect;
+ st->parent.certificate = gnutls_certificate;
+ st->parent.read = gnutls_stream_read;
+ st->parent.write = gnutls_stream_write;
+ st->parent.close = gnutls_stream_close;
+ st->parent.free = gnutls_stream_free;
+
+ *out = (git_stream *) st;
+ return 0;
+}
+
+#else
+
+int git_gnutls_stream_new(git_stream **out, const char *host, const char *port)
+{
+ giterr_set(GITERR_SSL, "GnuTLS is not supported in this version");
+ return -1;
+}
+
+#endif
diff --git a/src/hash.h b/src/hash.h
index 0bc02a8a9..c373633ce 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -18,6 +18,8 @@ void git_hash_ctx_cleanup(git_hash_ctx *ctx);
#if defined(GIT_COMMON_CRYPTO)
# include "hash/hash_common_crypto.h"
+#elif defined(GNUTLS_SHA1)
+# include "hash/hash_gnutls.h"
#elif defined(OPENSSL_SHA1)
# include "hash/hash_openssl.h"
#elif defined(WIN32_SHA1)
diff --git a/src/hash/hash_gnutls.h b/src/hash/hash_gnutls.h
new file mode 100644
index 000000000..f4a78a8f3
--- /dev/null
+++ b/src/hash/hash_gnutls.h
@@ -0,0 +1,58 @@
+/*
+ * 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_hash_gnutls_h__
+#define INCLUDE_hash_gnutls_h__
+
+#include "hash.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+struct git_hash_ctx {
+ gnutls_hash_hd_t c;
+};
+
+#define git_hash_global_init() 0
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx) git_hash_cleanup(ctx)
+
+GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx)
+{
+ int error;
+ assert(ctx);
+ if ((error = gnutls_hash_init(&ctx->c, GNUTLS_MAC_SHA1)) < 0) {
+ giterr_set(GITERR_SSL, "gnutls: %s", gnutls_strerror(error));
+ return -1;
+ }
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ int error;
+ assert(ctx);
+ if ((error = gnutls_hash(ctx->c, data, len)) < 0) {
+ giterr_set(GITERR_SSL, "gnutls: %s", gnutls_strerror(error));
+ return -1;
+ }
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx);
+ gnutls_hash_output(ctx->c, out);
+ return 0;
+}
+
+GIT_INLINE(void) git_hash_cleanup(git_hash_ctx *ctx)
+{
+ gnutls_hash_deinit(ctx->c, NULL);
+}
+
+#endif