From 66024c7cbcbae3a75d0b0426993d8ee5fa5f9dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 1 May 2012 00:05:25 +0200 Subject: [PATCH 1/9] http: add https support when GnuTLS is available If it's not available, an error saying so will be returned when trying to use a https:// URL. This also unifies a lot of the network code to use git_transport in many places instead of an socket descriptor. --- CMakeLists.txt | 11 +++- src/common.h | 10 ++++ src/fetch.c | 4 +- src/fetch.h | 2 +- src/netops.c | 129 +++++++++++++++++++++++++++++++++++++++--- src/netops.h | 14 ++++- src/pkt.c | 6 -- src/transport.c | 2 +- src/transport.h | 10 +++- src/transports/git.c | 51 ++++++++--------- src/transports/http.c | 80 ++++++++++++++++---------- 11 files changed, 239 insertions(+), 80 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bfbabc0a5..34cc64753 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ INCLUDE_DIRECTORIES(src include deps/http-parser) FILE(GLOB SRC_HTTP deps/http-parser/*.c) +FIND_PACKAGE(GnuTLS) IF (NOT WIN32) FIND_PACKAGE(ZLIB) ELSE() @@ -86,6 +87,12 @@ IF (NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) ENDIF () + +IF (GNUTLS_FOUND) + INCLUDE_DIRECTORIES(GNUTLS_INCLUDE_DIR) + ADD_DEFINITIONS(-DGIT_GNUTLS) +ENDIF() + IF (THREADSAFE) IF (NOT WIN32) find_package(Threads REQUIRED) @@ -118,7 +125,7 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") TARGET_LINK_LIBRARIES(git2 socket nsl) ENDIF () -TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT}) +TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES}) SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR}) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY) @@ -154,7 +161,7 @@ IF (BUILD_CLAR) WORKING_DIRECTORY ${CLAR_PATH} ) ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX}) - TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT}) + TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES}) IF (WIN32) TARGET_LINK_LIBRARIES(libgit2_clar ws2_32) ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") diff --git a/src/common.h b/src/common.h index 30757de70..75e6e5867 100644 --- a/src/common.h +++ b/src/common.h @@ -20,6 +20,10 @@ #include #include +#ifdef GIT_GNUTLS +# include +#endif + #ifdef GIT_WIN32 # include @@ -65,6 +69,12 @@ void giterr_clear(void); void giterr_set_str(int error_class, const char *string); void giterr_set_regex(const regex_t *regex, int error_code); +#ifdef GIT_GNUTLS +typedef struct gitno_ssl { + gnutls_session_t session; + gnutls_certificate_credentials_t cred; +} gitno_ssl; +#endif #include "util.h" diff --git a/src/fetch.c b/src/fetch.c index c92cf4ef5..96b263faa 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -110,7 +110,7 @@ int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_st int git_fetch__download_pack( const char *buffered, size_t buffered_size, - GIT_SOCKET fd, + git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) @@ -120,7 +120,7 @@ int git_fetch__download_pack( gitno_buffer buf; git_indexer_stream *idx; - gitno_buffer_setup(&buf, buff, sizeof(buff), fd); + gitno_buffer_setup(t, &buf, buff, sizeof(buff)); if (memcmp(buffered, "PACK", strlen("PACK"))) { giterr_set(GITERR_NET, "The pack doesn't start with the signature"); diff --git a/src/fetch.h b/src/fetch.h index b3192a563..a7f126520 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -12,7 +12,7 @@ int git_fetch_negotiate(git_remote *remote); int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); -int git_fetch__download_pack(const char *buffered, size_t buffered_size, GIT_SOCKET fd, +int git_fetch__download_pack(const char *buffered, size_t buffered_size, git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); diff --git a/src/netops.c b/src/netops.c index 4d461a049..f2b504a00 100644 --- a/src/netops.c +++ b/src/netops.c @@ -18,6 +18,11 @@ # endif #endif +#ifdef GIT_GNUTLS +# include +# include +# include +#endif #include "git2/errors.h" @@ -25,6 +30,7 @@ #include "netops.h" #include "posix.h" #include "buffer.h" +#include "transport.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -45,25 +51,66 @@ static void net_set_error(const char *str) } #endif -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd) +#ifdef GIT_GNUTLS +static int ssl_set_error(int error) +{ + giterr_set(GITERR_NET, "SSL error: (%s) %s", gnutls_strerror_name(error), gnutls_strerror(error)); + return -1; +} +#endif + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) { memset(buf, 0x0, sizeof(gitno_buffer)); memset(data, 0x0, len); buf->data = data; buf->len = len; buf->offset = 0; - buf->fd = fd; + buf->fd = t->socket; +#ifdef GIT__GNUTLS + if (t->encrypt) + buf->ssl = t->ssl; +#endif +} + +static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +{ + int ret; + + do { + ret = gnutls_record_recv(ssl->session, data, len); + } while(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); + + if (ret < 0) { + ssl_set_error(ret); + return -1; + } + + return ret; } int gitno_recv(gitno_buffer *buf) { int ret; +#ifdef GIT_GNUTLS + if (buf->ssl != NULL) { + if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) + return -1; + } else { + ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } + } +#else ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); if (ret < 0) { net_set_error("Error receiving socket data"); return -1; } +#endif buf->offset += ret; return ret; @@ -92,7 +139,44 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port) +#ifdef GIT_GNUTLS +static int ssl_setup(git_transport *t) +{ + int ret; + + if ((ret = gnutls_global_init()) < 0) + return ssl_set_error(ret); + + if ((ret = gnutls_certificate_allocate_credentials(&t->ssl.cred)) < 0) + return ssl_set_error(ret); + + gnutls_init(&t->ssl.session, GNUTLS_CLIENT); + //gnutls_certificate_set_verify_function(ssl->cred, SSL_VERIFY_NONE); + gnutls_credentials_set(t->ssl.session, GNUTLS_CRD_CERTIFICATE, t->ssl.cred); + + if ((ret = gnutls_priority_set_direct (t->ssl.session, "NORMAL", NULL)) < 0) + return ssl_set_error(ret); + + gnutls_transport_set_ptr(t->ssl.session, (gnutls_transport_ptr_t) t->socket); + + do { + ret = gnutls_handshake(t->ssl.session); + } while (ret < 0 && !gnutls_error_is_fatal(ret)); + + if (ret < 0) { + ssl_set_error(ret); + goto on_error; + } + + return 0; + +on_error: + gnutls_deinit(t->ssl.session); + return -1; +} +#endif + +int gitno_connect(git_transport *t, const char *host, const char *port) { struct addrinfo *info = NULL, *p; struct addrinfo hints; @@ -129,20 +213,51 @@ int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port) return -1; } + t->socket = s; freeaddrinfo(info); - *sock = s; + +#ifdef GIT_GNUTLS + if (t->encrypt && ssl_setup(t) < 0) + return -1; +#endif + return 0; } -int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags) +#ifdef GIT_GNUTLS +static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) { int ret; size_t off = 0; while (off < len) { - errno = 0; + ret = gnutls_record_send(ssl->session, msg + off, len - off); + if (ret < 0) { + if (gnutls_error_is_fatal(ret)) + return ssl_set_error(ret); - ret = p_send(s, msg + off, len - off, flags); + ret = 0; + } + off += ret; + } + + return off; +} +#endif + +int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +{ + int ret; + size_t off = 0; + +#ifdef GIT_GNUTLS + if (t->encrypt) + return send_ssl(&t->ssl, msg, len); +#endif + + while (off < len) { + errno = 0; + ret = p_send(t->socket, msg + off, len - off, flags); if (ret < 0) { net_set_error("Error sending data"); return -1; diff --git a/src/netops.h b/src/netops.h index 9d13f3891..9401ac2a9 100644 --- a/src/netops.h +++ b/src/netops.h @@ -8,21 +8,29 @@ #define INCLUDE_netops_h__ #include "posix.h" +#include "transport.h" +#ifdef GIT_GNUTLS +# include +#endif typedef struct gitno_buffer { char *data; size_t len; size_t offset; GIT_SOCKET fd; +#ifdef GIT_GNUTLS + struct gitno_ssl *ssl; +#endif } gitno_buffer; -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd); +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len); int gitno_recv(gitno_buffer *buf); + void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(GIT_SOCKET *s, const char *host, const char *port); -int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags); +GIT_SOCKET gitno_connect(git_transport *t, const char *host, const char *port); +int gitno_send(git_transport *t, const char *msg, size_t len, int flags); int gitno_close(GIT_SOCKET s); int gitno_send_chunk_size(int s, size_t len); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); diff --git a/src/pkt.c b/src/pkt.c index 95430ddfc..88510f4b1 100644 --- a/src/pkt.c +++ b/src/pkt.c @@ -281,12 +281,6 @@ int git_pkt_buffer_flush(git_buf *buf) return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); } -int git_pkt_send_flush(GIT_SOCKET s) -{ - - return gitno_send(s, pkt_flush_str, strlen(pkt_flush_str), 0); -} - static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) { char capstr[20]; diff --git a/src/transport.c b/src/transport.c index 5b2cd7ea4..fb2b94946 100644 --- a/src/transport.c +++ b/src/transport.c @@ -17,7 +17,7 @@ static struct { } transports[] = { {"git://", git_transport_git}, {"http://", git_transport_http}, - {"https://", git_transport_dummy}, + {"https://", git_transport_https}, {"file://", git_transport_local}, {"git+ssh://", git_transport_dummy}, {"ssh+git://", git_transport_dummy}, diff --git a/src/transport.h b/src/transport.h index 125df2745..0c348cc2d 100644 --- a/src/transport.h +++ b/src/transport.h @@ -10,6 +10,8 @@ #include "git2/net.h" #include "git2/indexer.h" #include "vector.h" +#include "posix.h" +#include "common.h" #define GIT_CAP_OFS_DELTA "ofs-delta" @@ -53,7 +55,12 @@ struct git_transport { * Whether we want to push or fetch */ int direction : 1, /* 0 fetch, 1 push */ - connected : 1; + connected : 1, + encrypt : 1; +#ifdef GIT_GNUTLS + struct gitno_ssl ssl; +#endif + GIT_SOCKET socket; /** * Connect and store the remote heads */ @@ -94,6 +101,7 @@ int git_transport_new(struct git_transport **transport, const char *url); int git_transport_local(struct git_transport **transport); int git_transport_git(struct git_transport **transport); int git_transport_http(struct git_transport **transport); +int git_transport_https(struct git_transport **transport); int git_transport_dummy(struct git_transport **transport); /** diff --git a/src/transports/git.c b/src/transports/git.c index 5baa810f0..2e7995549 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -25,7 +25,6 @@ typedef struct { git_transport parent; git_protocol proto; - GIT_SOCKET socket; git_vector refs; git_remote_head **heads; git_transport_caps caps; @@ -77,7 +76,7 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) return 0; } -static int send_request(GIT_SOCKET s, const char *cmd, const char *url) +static int send_request(git_transport *t, const char *cmd, const char *url) { int error; git_buf request = GIT_BUF_INIT; @@ -86,7 +85,7 @@ static int send_request(GIT_SOCKET s, const char *cmd, const char *url) if (error < 0) goto cleanup; - error = gitno_send(s, request.ptr, request.size, 0); + error = gitno_send(t, request.ptr, request.size, 0); cleanup: git_buf_free(&request); @@ -102,9 +101,6 @@ static int do_connect(transport_git *t, const char *url) { char *host, *port; const char prefix[] = "git://"; - int error; - - t->socket = INVALID_SOCKET; if (!git__prefixcmp(url, prefix)) url += strlen(prefix); @@ -112,24 +108,22 @@ static int do_connect(transport_git *t, const char *url) if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) return -1; - if ((error = gitno_connect(&t->socket, host, port)) == 0) { - error = send_request(t->socket, NULL, url); - } + if (gitno_connect((git_transport *)t, host, port) < 0) + goto on_error; + + if (send_request((git_transport *)t, NULL, url) < 0) + goto on_error; git__free(host); git__free(port); - if (error < 0 && t->socket != INVALID_SOCKET) { - gitno_close(t->socket); - t->socket = INVALID_SOCKET; - } - - if (t->socket == INVALID_SOCKET) { - giterr_set(GITERR_NET, "Failed to connect to the host"); - return -1; - } - return 0; + +on_error: + git__free(host); + git__free(port); + gitno_close(t->parent.socket); + return -1; } /* @@ -215,7 +209,7 @@ static int git_connect(git_transport *transport, int direction) if (do_connect(t, transport->url) < 0) goto cleanup; - gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket); + gitno_buffer_setup(transport, &t->buf, t->buff, sizeof(t->buff)); t->parent.connected = 1; if (store_refs(t) < 0) @@ -308,7 +302,7 @@ static int git_negotiate_fetch(git_transport *transport, git_repository *repo, c if (git_fetch_setup_walk(&walk, repo) < 0) goto on_error; - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + if (gitno_send(transport, data.ptr, data.size, 0) < 0) goto on_error; git_buf_clear(&data); @@ -328,7 +322,7 @@ static int git_negotiate_fetch(git_transport *transport, git_repository *repo, c if (git_buf_oom(&data)) goto on_error; - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + if (gitno_send(transport, data.ptr, data.size, 0) < 0) goto on_error; pkt_type = recv_pkt(buf); @@ -351,7 +345,7 @@ static int git_negotiate_fetch(git_transport *transport, git_repository *repo, c git_buf_clear(&data); git_pkt_buffer_flush(&data); git_pkt_buffer_done(&data); - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + if (gitno_send(transport, data.ptr, data.size, 0) < 0) goto on_error; git_buf_free(&data); @@ -392,7 +386,7 @@ static int git_download_pack(git_transport *transport, git_repository *repo, git if (pkt->type == GIT_PKT_PACK) { git__free(pkt); - return git_fetch__download_pack(buf->data, buf->offset, t->socket, repo, bytes, stats); + return git_fetch__download_pack(buf->data, buf->offset, transport, repo, bytes, stats); } /* For now we don't care about anything */ @@ -406,12 +400,15 @@ static int git_download_pack(git_transport *transport, git_repository *repo, git return read_bytes; } -static int git_close(git_transport *transport) +static int git_close(git_transport *t) { - transport_git *t = (transport_git*) transport; + git_buf buf = GIT_BUF_INIT; + if (git_pkt_buffer_flush(&buf) < 0) + return -1; /* Can't do anything if there's an error, so don't bother checking */ - git_pkt_send_flush(t->socket); + gitno_send(t, buf.ptr, buf.size, 0); + if (gitno_close(t->socket) < 0) { giterr_set(GITERR_NET, "Failed to close socket"); return -1; diff --git a/src/transports/http.c b/src/transports/http.c index 2a8ebbb09..4f8e03163 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -32,7 +32,6 @@ typedef struct { git_protocol proto; git_vector refs; git_vector common; - GIT_SOCKET socket; git_buf buf; git_remote_head **heads; int error; @@ -43,6 +42,7 @@ typedef struct { enum last_cb last_cb; http_parser parser; char *content_type; + char *path; char *host; char *port; char *service; @@ -52,12 +52,9 @@ typedef struct { #endif } transport_http; -static int gen_request(git_buf *buf, const char *url, const char *host, const char *op, +static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, const char *service, ssize_t content_length, int ls) { - const char *path = url; - - path = strchr(path, '/'); if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ path = "/"; @@ -85,15 +82,12 @@ static int gen_request(git_buf *buf, const char *url, const char *host, const ch static int do_connect(transport_http *t, const char *host, const char *port) { - GIT_SOCKET s; - if (t->parent.connected && http_should_keep_alive(&t->parser)) return 0; - if (gitno_connect(&s, host, port) < 0) + if (gitno_connect((git_transport *) t, host, port) < 0) return -1; - t->socket = s; t->parent.connected = 1; return 0; @@ -231,7 +225,7 @@ static int store_refs(transport_http *t) settings.on_body = on_body_store_refs; settings.on_message_complete = on_message_complete; - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); while(1) { size_t parsed; @@ -267,7 +261,8 @@ static int http_connect(git_transport *transport, int direction) int ret; git_buf request = GIT_BUF_INIT; const char *service = "upload-pack"; - const char *url = t->parent.url, *prefix = "http://"; + const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; + const char *default_port; if (direction == GIT_DIR_PUSH) { giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); @@ -278,10 +273,19 @@ static int http_connect(git_transport *transport, int direction) if (git_vector_init(&t->refs, 16, NULL) < 0) return -1; - if (!git__prefixcmp(url, prefix)) - url += strlen(prefix); + if (!git__prefixcmp(url, prefix_http)) { + url = t->parent.url + strlen(prefix_http); + default_port = "80"; + } - if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, "80")) < 0) + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + } + + t->path = strchr(url, '/'); + + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) goto cleanup; t->service = git__strdup(service); @@ -291,12 +295,13 @@ static int http_connect(git_transport *transport, int direction) goto cleanup; /* Generate and send the HTTP request */ - if ((ret = gen_request(&request, url, t->host, "GET", service, 0, 1)) < 0) { + if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { giterr_set(GITERR_NET, "Failed to generate request"); goto cleanup; } - if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0) + + if (gitno_send(transport, request.ptr, request.size, 0) < 0) goto cleanup; ret = store_refs(t); @@ -403,7 +408,7 @@ static int parse_response(transport_http *t) settings.on_body = on_body_parse_response; settings.on_message_complete = on_message_complete; - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); while(1) { size_t parsed; @@ -437,13 +442,9 @@ static int http_negotiate_fetch(git_transport *transport, git_repository *repo, git_oid oid; git_pkt_ack *pkt; git_vector *common = &t->common; - const char *prefix = "http://", *url = t->parent.url; git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT; - gitno_buffer_setup(&buf, buff, sizeof(buff), t->socket); - /* TODO: Store url in the transport */ - if (!git__prefixcmp(url, prefix)) - url += strlen(prefix); + gitno_buffer_setup(transport, &buf, buff, sizeof(buff)); if (git_vector_init(common, 16, NULL) < 0) return -1; @@ -474,13 +475,13 @@ static int http_negotiate_fetch(git_transport *transport, git_repository *repo, git_pkt_buffer_done(&data); - if ((ret = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0)) < 0) + if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", data.size, 0)) < 0) goto cleanup; - if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0) + if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) goto cleanup; - if ((ret = gitno_send(t->socket, data.ptr, data.size, 0)) < 0) + if ((ret = gitno_send(transport, data.ptr, data.size, 0)) < 0) goto cleanup; git_buf_clear(&request); @@ -547,7 +548,7 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi git_indexer_stream *idx = NULL; download_pack_cbdata data; - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) { giterr_set(GITERR_NET, "The pack doesn't start with a pack signature"); @@ -557,7 +558,6 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) return -1; - /* * This is part of the previous response, so we don't want to * re-init the parser, just set these two callbacks. @@ -576,6 +576,9 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0) goto on_error; + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); + + do { size_t parsed; @@ -603,9 +606,7 @@ on_error: static int http_close(git_transport *transport) { - transport_http *t = (transport_http *) transport; - - if (gitno_close(t->socket) < 0) { + if (gitno_close(transport->socket) < 0) { giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); return -1; } @@ -680,3 +681,22 @@ int git_transport_http(git_transport **out) *out = (git_transport *) t; return 0; } + +int git_transport_https(git_transport **out) +{ +#ifdef GIT_GNUTLS + transport_http *t; + if (git_transport_http((git_transport **)&t) < 0) + return -1; + + t->parent.encrypt = 1; + *out = (git_transport *) t; + + return 0; +#else + GIT_UNUSED(out); + + giterr_set(GITERR_NET, "HTTPS support not available"); + return -1; +#endif +} From a6f24a5b3a8dcb9ab7f84679d658e66f374b88d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 1 May 2012 01:50:26 +0200 Subject: [PATCH 2/9] https: make it work with OpenSSL as well Add specific functions that use OpenSSL instead of GnuTLS --- CMakeLists.txt | 23 +++++++++---- src/common.h | 8 +++++ src/netops.c | 78 ++++++++++++++++++++++++++++++++++++++++--- src/netops.h | 8 ++--- src/transport.h | 2 +- src/transports/http.c | 3 +- 6 files changed, 102 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34cc64753..b92585976 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,6 @@ INCLUDE_DIRECTORIES(src include deps/http-parser) FILE(GLOB SRC_HTTP deps/http-parser/*.c) -FIND_PACKAGE(GnuTLS) IF (NOT WIN32) FIND_PACKAGE(ZLIB) ELSE() @@ -87,10 +86,20 @@ IF (NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) ENDIF () - -IF (GNUTLS_FOUND) - INCLUDE_DIRECTORIES(GNUTLS_INCLUDE_DIR) - ADD_DEFINITIONS(-DGIT_GNUTLS) +FIND_PACKAGE(OpenSSL) +IF (OPENSSL_FOUND) + ADD_DEFINITIONS(-DGIT_OPENSSL) + ADD_DEFINITIONS(-DGIT_SSL) + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) +ELSE() + FIND_PACKAGE(GnuTLS) + IF (GNUTLS_FOUND) + INCLUDE_DIRECTORIES(GNUTLS_INCLUDE_DIR) + ADD_DEFINITIONS(-DGIT_GNUTLS) + ADD_DEFINITIONS(-DGIT_SSL) + SET(SSL_LIBRARIES ${GNUTLS_LIBRARIES}) + ENDIF() ENDIF() IF (THREADSAFE) @@ -125,7 +134,7 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") TARGET_LINK_LIBRARIES(git2 socket nsl) ENDIF () -TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES}) +TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR}) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY) @@ -161,7 +170,7 @@ IF (BUILD_CLAR) WORKING_DIRECTORY ${CLAR_PATH} ) ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX}) - TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES}) + TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) IF (WIN32) TARGET_LINK_LIBRARIES(libgit2_clar ws2_32) ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") diff --git a/src/common.h b/src/common.h index 75e6e5867..30865659c 100644 --- a/src/common.h +++ b/src/common.h @@ -22,6 +22,9 @@ #ifdef GIT_GNUTLS # include +#elif defined(GIT_OPENSSL) +# include +# include #endif #ifdef GIT_WIN32 @@ -74,6 +77,11 @@ typedef struct gitno_ssl { gnutls_session_t session; gnutls_certificate_credentials_t cred; } gitno_ssl; +#elif defined(GIT_OPENSSL) +typedef struct gitno_ssl { + SSL_CTX *ctx; + SSL *ssl; +} gitno_ssl; #endif #include "util.h" diff --git a/src/netops.c b/src/netops.c index f2b504a00..67a361ea9 100644 --- a/src/netops.c +++ b/src/netops.c @@ -22,8 +22,11 @@ # include # include # include +#elif defined(GIT_OPENSSL) +# include #endif + #include "git2/errors.h" #include "common.h" @@ -57,6 +60,14 @@ static int ssl_set_error(int error) giterr_set(GITERR_NET, "SSL error: (%s) %s", gnutls_strerror_name(error), gnutls_strerror(error)); return -1; } +#elif GIT_OPENSSL +static int ssl_set_error(gitno_ssl *ssl, int error) +{ + int err; + err = SSL_get_error(ssl->ssl, error); + giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + return -1; +} #endif void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) @@ -67,12 +78,13 @@ void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigne buf->len = len; buf->offset = 0; buf->fd = t->socket; -#ifdef GIT__GNUTLS +#ifdef GIT_SSL if (t->encrypt) - buf->ssl = t->ssl; + buf->ssl = &t->ssl; #endif } +#ifdef GIT_GNUTLS static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) { int ret; @@ -88,12 +100,27 @@ static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) return ret; } +#elif defined(GIT_OPENSSL) +static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +{ + int ret; + + do { + ret = SSL_read(ssl->ssl, data, len); + } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); + + if (ret < 0) + return ssl_set_error(ssl, ret); + + return ret; +} +#endif int gitno_recv(gitno_buffer *buf) { int ret; -#ifdef GIT_GNUTLS +#ifdef GIT_SSL if (buf->ssl != NULL) { if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) return -1; @@ -174,6 +201,31 @@ on_error: gnutls_deinit(t->ssl.session); return -1; } +#elif defined(GIT_OPENSSL) +static int ssl_setup(git_transport *t) +{ + int ret; + + SSL_library_init(); + SSL_load_error_strings(); + t->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (t->ssl.ctx == NULL) + return ssl_set_error(&t->ssl, 0); + + SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); + + t->ssl.ssl = SSL_new(t->ssl.ctx); + if (t->ssl.ssl == NULL) + return ssl_set_error(&t->ssl, 0); + + if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) + return ssl_set_error(&t->ssl, ret); + + if ((ret = SSL_connect(t->ssl.ssl)) <= 0) + return ssl_set_error(&t->ssl, ret); + + return 0; +} #endif int gitno_connect(git_transport *t, const char *host, const char *port) @@ -216,7 +268,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port) t->socket = s; freeaddrinfo(info); -#ifdef GIT_GNUTLS +#ifdef GIT_SSL if (t->encrypt && ssl_setup(t) < 0) return -1; #endif @@ -243,6 +295,22 @@ static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) return off; } +#elif defined(GIT_OPENSSL) +static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) +{ + int ret; + size_t off = 0; + + while (off < len) { + ret = SSL_write(ssl->ssl, msg + off, len - off); + if (ret <= 0) + return ssl_set_error(ssl, ret); + + off += ret; + } + + return off; +} #endif int gitno_send(git_transport *t, const char *msg, size_t len, int flags) @@ -250,7 +318,7 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags) int ret; size_t off = 0; -#ifdef GIT_GNUTLS +#ifdef GIT_SSL if (t->encrypt) return send_ssl(&t->ssl, msg, len); #endif diff --git a/src/netops.h b/src/netops.h index 9401ac2a9..8591a8e94 100644 --- a/src/netops.h +++ b/src/netops.h @@ -9,16 +9,14 @@ #include "posix.h" #include "transport.h" -#ifdef GIT_GNUTLS -# include -#endif +#include "common.h" typedef struct gitno_buffer { char *data; size_t len; size_t offset; GIT_SOCKET fd; -#ifdef GIT_GNUTLS +#ifdef GIT_SSL struct gitno_ssl *ssl; #endif } gitno_buffer; @@ -29,7 +27,7 @@ int gitno_recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -GIT_SOCKET gitno_connect(git_transport *t, const char *host, const char *port); +int gitno_connect(git_transport *t, const char *host, const char *port); int gitno_send(git_transport *t, const char *msg, size_t len, int flags); int gitno_close(GIT_SOCKET s); int gitno_send_chunk_size(int s, size_t len); diff --git a/src/transport.h b/src/transport.h index 0c348cc2d..0257ccea5 100644 --- a/src/transport.h +++ b/src/transport.h @@ -57,7 +57,7 @@ struct git_transport { int direction : 1, /* 0 fetch, 1 push */ connected : 1, encrypt : 1; -#ifdef GIT_GNUTLS +#ifdef GIT_SSL struct gitno_ssl ssl; #endif GIT_SOCKET socket; diff --git a/src/transports/http.c b/src/transports/http.c index 4f8e03163..6746f68b4 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -578,7 +578,6 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); - do { size_t parsed; @@ -684,7 +683,7 @@ int git_transport_http(git_transport **out) int git_transport_https(git_transport **out) { -#ifdef GIT_GNUTLS +#ifdef GIT_SSL transport_http *t; if (git_transport_http((git_transport **)&t) < 0) return -1; From 89460f3f57b6efa906263a19b982f8a7859b15c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 3 May 2012 14:07:55 +0200 Subject: [PATCH 3/9] ssl: teardown the connection on close This should help us free some resources, though the libraries do keep some buffers allocated regardless. --- src/netops.c | 38 +++++++++++++++++++++++++++++++------- src/netops.h | 1 + src/transports/http.c | 3 +++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/netops.c b/src/netops.c index 67a361ea9..7ee720d67 100644 --- a/src/netops.c +++ b/src/netops.c @@ -166,9 +166,35 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } +int gitno_ssl_teardown(git_transport *t) +{ + int ret = ret; + + if (!t->encrypt) + return 0; + #ifdef GIT_GNUTLS + gnutls_deinit(t->ssl.session); + gnutls_certificate_free_credentials(t->ssl.cred); + gnutls_global_deinit(); +#elif defined(GIT_OPENSSL) + + do { + ret = SSL_shutdown(t->ssl.ssl); + } while (ret == 0); + if (ret < 0) + return ssl_set_error(&t->ssl, ret); + + SSL_free(t->ssl.ssl); + SSL_CTX_free(t->ssl.ctx); +#endif + return 0; +} + + static int ssl_setup(git_transport *t) { +#ifdef GIT_GNUTLS int ret; if ((ret = gnutls_global_init()) < 0) @@ -199,11 +225,9 @@ static int ssl_setup(git_transport *t) on_error: gnutls_deinit(t->ssl.session); + gnutls_global_deinit(); return -1; -} #elif defined(GIT_OPENSSL) -static int ssl_setup(git_transport *t) -{ int ret; SSL_library_init(); @@ -225,9 +249,11 @@ static int ssl_setup(git_transport *t) return ssl_set_error(&t->ssl, ret); return 0; -} +#else + GIT_UNUSED(t); + return 0; #endif - +} int gitno_connect(git_transport *t, const char *host, const char *port) { struct addrinfo *info = NULL, *p; @@ -268,10 +294,8 @@ int gitno_connect(git_transport *t, const char *host, const char *port) t->socket = s; freeaddrinfo(info); -#ifdef GIT_SSL if (t->encrypt && ssl_setup(t) < 0) return -1; -#endif return 0; } diff --git a/src/netops.h b/src/netops.h index 8591a8e94..4976f87f8 100644 --- a/src/netops.h +++ b/src/netops.h @@ -30,6 +30,7 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons); int gitno_connect(git_transport *t, const char *host, const char *port); int gitno_send(git_transport *t, const char *msg, size_t len, int flags); int gitno_close(GIT_SOCKET s); +int gitno_ssl_teardown(git_transport *t); int gitno_send_chunk_size(int s, size_t len); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); diff --git a/src/transports/http.c b/src/transports/http.c index 6746f68b4..36cc66c69 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -605,6 +605,9 @@ on_error: static int http_close(git_transport *transport) { + if (gitno_ssl_teardown(transport) < 0) + return -1; + if (gitno_close(transport->socket) < 0) { giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); return -1; From dbb36e1b42de2b65b3ea98501dc6aae754acd744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 17 May 2012 17:56:49 +0200 Subject: [PATCH 4/9] ssl: check certificates against the system's trusted CAs --- include/git2/errors.h | 1 + src/netops.c | 108 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/include/git2/errors.h b/include/git2/errors.h index fb6670004..ccbc9fcf4 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -88,6 +88,7 @@ typedef enum { GITERR_TAG, GITERR_TREE, GITERR_INDEXER, + GITERR_SSL, } git_error_t; /** diff --git a/src/netops.c b/src/netops.c index 7ee720d67..ff0d6d735 100644 --- a/src/netops.c +++ b/src/netops.c @@ -24,9 +24,10 @@ # include #elif defined(GIT_OPENSSL) # include +# include #endif - +#include #include "git2/errors.h" #include "common.h" @@ -192,7 +193,102 @@ int gitno_ssl_teardown(git_transport *t) } -static int ssl_setup(git_transport *t) +#ifdef GIT_OPENSSL +/* + * This function is based on the one from the cURL project + */ +static int match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = *pattern++; + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + while (*host) { + if (match_host(pattern, host++) == 0) + return 0; + } + break; + } + + if (tolower(c) != tolower(*host++)) + return -1; + } + + return -1; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(git_transport *t, const char *host) +{ + X509 *cert; + X509_NAME *peer_name; + char buf[1024]; + int matched = -1; + GENERAL_NAMES *alts; + + cert = SSL_get_peer_certificate(t->ssl.ssl); + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num, i; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* If it contains embedded NULs, don't even try */ + if (namelen != strnlen(name, namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) { + giterr_set(GITERR_SSL, "Certificate host name check failed"); + return -1; + } + if (matched == 1) + return 0; + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + X509_NAME_get_text_by_NID(peer_name, NID_commonName, buf, sizeof(buf)); + if (strcasecmp(host, buf)) { + giterr_set(GITERR_NET, "CN %s doesn't match host %s\n", buf, host); + return -1; + } + + return 0; +} +#endif + +static int ssl_setup(git_transport *t, const char *host) { #ifdef GIT_GNUTLS int ret; @@ -237,6 +333,9 @@ on_error: return ssl_set_error(&t->ssl, 0); SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_PEER, NULL); + if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) + return ssl_set_error(&t->ssl, 0); t->ssl.ssl = SSL_new(t->ssl.ctx); if (t->ssl.ssl == NULL) @@ -248,6 +347,9 @@ on_error: if ((ret = SSL_connect(t->ssl.ssl)) <= 0) return ssl_set_error(&t->ssl, ret); + if (verify_server_cert(t, host) < 0) + return -1; + return 0; #else GIT_UNUSED(t); @@ -294,7 +396,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port) t->socket = s; freeaddrinfo(info); - if (t->encrypt && ssl_setup(t) < 0) + if (t->encrypt && ssl_setup(t, host) < 0) return -1; return 0; From 16768191c739e6478db95b80a51753dfd0662302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 17 May 2012 21:16:59 +0200 Subject: [PATCH 5/9] ssl: match host names according to RFC 2818 (HTTP over TLS) --- src/netops.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/netops.c b/src/netops.c index ff0d6d735..2f127102c 100644 --- a/src/netops.c +++ b/src/netops.c @@ -194,13 +194,11 @@ int gitno_ssl_teardown(git_transport *t) #ifdef GIT_OPENSSL -/* - * This function is based on the one from the cURL project - */ +/* Match host names according to RFC 2818 rules */ static int match_host(const char *pattern, const char *host) { for (;;) { - char c = *pattern++; + char c = tolower(*pattern++); if (c == '\0') return *host ? -1 : 0; @@ -211,14 +209,24 @@ static int match_host(const char *pattern, const char *host) if (c == '\0') return 0; - while (*host) { - if (match_host(pattern, host++) == 0) - return 0; + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = tolower(*host); + if (c == h) + return match_host(pattern, host++); + if (h == '.') + return match_host(pattern, host); + host++; } - break; + return -1; } - if (tolower(c) != tolower(*host++)) + if (c != tolower(*host++)) return -1; } From d3e1367f61030f78692fb9f02e82cd49b1f8e949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 17 May 2012 21:40:20 +0200 Subject: [PATCH 6/9] ssl: remove GnuTLS support It's too much work for now to redo everything. Move the ssl context struct to transport.h --- CMakeLists.txt | 9 ---- src/common.h | 19 --------- src/netops.c | 109 +++++++----------------------------------------- src/transport.h | 13 ++++++ 4 files changed, 28 insertions(+), 122 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b92585976..59cf77e6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,18 +88,9 @@ ENDIF () FIND_PACKAGE(OpenSSL) IF (OPENSSL_FOUND) - ADD_DEFINITIONS(-DGIT_OPENSSL) ADD_DEFINITIONS(-DGIT_SSL) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) -ELSE() - FIND_PACKAGE(GnuTLS) - IF (GNUTLS_FOUND) - INCLUDE_DIRECTORIES(GNUTLS_INCLUDE_DIR) - ADD_DEFINITIONS(-DGIT_GNUTLS) - ADD_DEFINITIONS(-DGIT_SSL) - SET(SSL_LIBRARIES ${GNUTLS_LIBRARIES}) - ENDIF() ENDIF() IF (THREADSAFE) diff --git a/src/common.h b/src/common.h index 30865659c..e2a300291 100644 --- a/src/common.h +++ b/src/common.h @@ -20,13 +20,6 @@ #include #include -#ifdef GIT_GNUTLS -# include -#elif defined(GIT_OPENSSL) -# include -# include -#endif - #ifdef GIT_WIN32 # include @@ -72,18 +65,6 @@ void giterr_clear(void); void giterr_set_str(int error_class, const char *string); void giterr_set_regex(const regex_t *regex, int error_code); -#ifdef GIT_GNUTLS -typedef struct gitno_ssl { - gnutls_session_t session; - gnutls_certificate_credentials_t cred; -} gitno_ssl; -#elif defined(GIT_OPENSSL) -typedef struct gitno_ssl { - SSL_CTX *ctx; - SSL *ssl; -} gitno_ssl; -#endif - #include "util.h" diff --git a/src/netops.c b/src/netops.c index 2f127102c..6967ebb5b 100644 --- a/src/netops.c +++ b/src/netops.c @@ -18,11 +18,7 @@ # endif #endif -#ifdef GIT_GNUTLS -# include -# include -# include -#elif defined(GIT_OPENSSL) +#ifdef GIT_SSL # include # include #endif @@ -55,13 +51,7 @@ static void net_set_error(const char *str) } #endif -#ifdef GIT_GNUTLS -static int ssl_set_error(int error) -{ - giterr_set(GITERR_NET, "SSL error: (%s) %s", gnutls_strerror_name(error), gnutls_strerror(error)); - return -1; -} -#elif GIT_OPENSSL +#ifdef GIT_SSL static int ssl_set_error(gitno_ssl *ssl, int error) { int err; @@ -85,23 +75,7 @@ void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigne #endif } -#ifdef GIT_GNUTLS -static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) -{ - int ret; - - do { - ret = gnutls_record_recv(ssl->session, data, len); - } while(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); - - if (ret < 0) { - ssl_set_error(ret); - return -1; - } - - return ret; -} -#elif defined(GIT_OPENSSL) +#ifdef GIT_SSL static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) { int ret; @@ -174,11 +148,7 @@ int gitno_ssl_teardown(git_transport *t) if (!t->encrypt) return 0; -#ifdef GIT_GNUTLS - gnutls_deinit(t->ssl.session); - gnutls_certificate_free_credentials(t->ssl.cred); - gnutls_global_deinit(); -#elif defined(GIT_OPENSSL) +#ifdef GIT_SSL do { ret = SSL_shutdown(t->ssl.ssl); @@ -193,7 +163,7 @@ int gitno_ssl_teardown(git_transport *t) } -#ifdef GIT_OPENSSL +#ifdef GIT_SSL /* Match host names according to RFC 2818 rules */ static int match_host(const char *pattern, const char *host) { @@ -294,44 +264,9 @@ static int verify_server_cert(git_transport *t, const char *host) return 0; } -#endif static int ssl_setup(git_transport *t, const char *host) { -#ifdef GIT_GNUTLS - int ret; - - if ((ret = gnutls_global_init()) < 0) - return ssl_set_error(ret); - - if ((ret = gnutls_certificate_allocate_credentials(&t->ssl.cred)) < 0) - return ssl_set_error(ret); - - gnutls_init(&t->ssl.session, GNUTLS_CLIENT); - //gnutls_certificate_set_verify_function(ssl->cred, SSL_VERIFY_NONE); - gnutls_credentials_set(t->ssl.session, GNUTLS_CRD_CERTIFICATE, t->ssl.cred); - - if ((ret = gnutls_priority_set_direct (t->ssl.session, "NORMAL", NULL)) < 0) - return ssl_set_error(ret); - - gnutls_transport_set_ptr(t->ssl.session, (gnutls_transport_ptr_t) t->socket); - - do { - ret = gnutls_handshake(t->ssl.session); - } while (ret < 0 && !gnutls_error_is_fatal(ret)); - - if (ret < 0) { - ssl_set_error(ret); - goto on_error; - } - - return 0; - -on_error: - gnutls_deinit(t->ssl.session); - gnutls_global_deinit(); - return -1; -#elif defined(GIT_OPENSSL) int ret; SSL_library_init(); @@ -359,11 +294,16 @@ on_error: return -1; return 0; -#else - GIT_UNUSED(t); - return 0; -#endif } +#else +static int ssl_setup(git_transport *t, const char *host) +{ + GIT_UNUSED(t); + GIT_UNUSED(host); + return 0; +} +#endif + int gitno_connect(git_transport *t, const char *host, const char *port) { struct addrinfo *info = NULL, *p; @@ -410,26 +350,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port) return 0; } -#ifdef GIT_GNUTLS -static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) -{ - int ret; - size_t off = 0; - - while (off < len) { - ret = gnutls_record_send(ssl->session, msg + off, len - off); - if (ret < 0) { - if (gnutls_error_is_fatal(ret)) - return ssl_set_error(ret); - - ret = 0; - } - off += ret; - } - - return off; -} -#elif defined(GIT_OPENSSL) +#ifdef GIT_SSL static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) { int ret; diff --git a/src/transport.h b/src/transport.h index 0257ccea5..00c140baf 100644 --- a/src/transport.h +++ b/src/transport.h @@ -12,6 +12,11 @@ #include "vector.h" #include "posix.h" #include "common.h" +#ifdef GIT_SSL +# include +# include +#endif + #define GIT_CAP_OFS_DELTA "ofs-delta" @@ -20,6 +25,14 @@ typedef struct git_transport_caps { ofs_delta:1; } git_transport_caps; +#ifdef GIT_SSL +typedef struct gitno_ssl { + SSL_CTX *ctx; + SSL *ssl; +} gitno_ssl; +#endif + + /* * A day in the life of a network operation * ======================================== From 3f9eb1e50201a3a99dad84ce22ad799fed15eaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 17 May 2012 22:22:05 +0200 Subject: [PATCH 7/9] ssl: add support for certificates issues to an IP address --- src/netops.c | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/netops.c b/src/netops.c index 6967ebb5b..6341fb887 100644 --- a/src/netops.c +++ b/src/netops.c @@ -24,6 +24,7 @@ #endif #include +#include #include "git2/errors.h" #include "common.h" @@ -219,8 +220,23 @@ static int verify_server_cert(git_transport *t, const char *host) X509 *cert; X509_NAME *peer_name; char buf[1024]; - int matched = -1; + int matched = -1, type = GEN_DNS; GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr; + + /* Try to parse the host as an IP address to see if it is */ + if (inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if(inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + cert = SSL_get_peer_certificate(t->ssl.ssl); @@ -235,14 +251,23 @@ static int verify_server_cert(git_transport *t, const char *host) const char *name = (char *) ASN1_STRING_data(gn->d.ia5); size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); - /* If it contains embedded NULs, don't even try */ - if (namelen != strnlen(name, namelen)) + /* Skip any names of a type we're not looking for */ + if (gn->type != type) continue; - if (check_host_name(name, host) < 0) - matched = 0; - else - matched = 1; + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (namelen != strnlen(name, namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = !!memcmp(name, addr, namelen); + } } } GENERAL_NAMES_free(alts); From 441df990b4e68459eb98c10445478d0fece30b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 17 May 2012 23:57:30 +0200 Subject: [PATCH 8/9] ssl: look up the last CN the alternative names don't match --- src/netops.c | 65 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/src/netops.c b/src/netops.c index 6341fb887..bd76d3022 100644 --- a/src/netops.c +++ b/src/netops.c @@ -219,12 +219,15 @@ static int verify_server_cert(git_transport *t, const char *host) { X509 *cert; X509_NAME *peer_name; - char buf[1024]; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; int matched = -1, type = GEN_DNS; GENERAL_NAMES *alts; struct in6_addr addr6; struct in_addr addr4; void *addr; + int i = -1,j; + /* Try to parse the host as an IP address to see if it is */ if (inet_pton(AF_INET, host, &addr4)) { @@ -243,7 +246,7 @@ static int verify_server_cert(git_transport *t, const char *host) /* Check the alternative names */ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (alts) { - int num, i; + int num; num = sk_GENERAL_NAME_num(alts); for (i = 0; i < num && matched != 1; i++) { @@ -257,7 +260,7 @@ static int verify_server_cert(git_transport *t, const char *host) if (type == GEN_DNS) { /* If it contains embedded NULs, don't even try */ - if (namelen != strnlen(name, namelen)) + if (memchr(name, '\0', namelen)) continue; if (check_host_name(name, host) < 0) @@ -272,22 +275,62 @@ static int verify_server_cert(git_transport *t, const char *host) } GENERAL_NAMES_free(alts); - if (matched == 0) { - giterr_set(GITERR_SSL, "Certificate host name check failed"); - return -1; - } + if (matched == 0) + goto on_error; + if (matched == 1) return 0; /* If no alternative names are available, check the common name */ peer_name = X509_get_subject_name(cert); - X509_NAME_get_text_by_NID(peer_name, NID_commonName, buf, sizeof(buf)); - if (strcasecmp(host, buf)) { - giterr_set(GITERR_NET, "CN %s doesn't match host %s\n", buf, host); - return -1; + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; } + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GITERR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_data(str), size); + peer_cn[size] = '\0'; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GITERR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail; + + OPENSSL_free(peer_cn); + return 0; + +on_error: + OPENSSL_free(peer_cn); + return ssl_set_error(&t->ssl, 0); + +cert_fail: + OPENSSL_free(peer_cn); + giterr_set(GITERR_SSL, "Certificate host name check failed"); + return -1; } static int ssl_setup(git_transport *t, const char *host) From 250b95b24b1a079be5825f862e42f4b99a4c3587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 26 May 2012 21:17:08 +0200 Subject: [PATCH 9/9] ssl: allow skipping the server certificate check Sometimes it's useful not to perform the check. Allow it to be configurable. --- include/git2/remote.h | 9 +++++++++ src/netops.c | 2 +- src/remote.c | 10 ++++++++++ src/remote.h | 3 ++- src/transport.h | 1 + src/transports/http.c | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 865dfef04..1f36f78e9 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -229,6 +229,15 @@ GIT_EXTERN(int) git_remote_list(git_strarray *remotes_list, git_repository *repo */ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url); +/** + * Choose whether to check the server's certificate (applies to HTTPS only) + * + * @param remote the remote to configure + * @param check whether to check the server's certificate (defaults to yes) + */ + +GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); + /** @} */ GIT_END_DECL #endif diff --git a/src/netops.c b/src/netops.c index bd76d3022..706e0924e 100644 --- a/src/netops.c +++ b/src/netops.c @@ -358,7 +358,7 @@ static int ssl_setup(git_transport *t, const char *host) if ((ret = SSL_connect(t->ssl.ssl)) <= 0) return ssl_set_error(&t->ssl, ret); - if (verify_server_cert(t, host) < 0) + if (t->check_cert && verify_server_cert(t, host) < 0) return -1; return 0; diff --git a/src/remote.c b/src/remote.c index 9740344f8..8e280c4da 100644 --- a/src/remote.c +++ b/src/remote.c @@ -66,6 +66,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con memset(remote, 0x0, sizeof(git_remote)); remote->repo = repo; + remote->check_cert = 1; if (git_vector_init(&remote->refs, 32, NULL) < 0) return -1; @@ -108,6 +109,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) GITERR_CHECK_ALLOC(remote); memset(remote, 0x0, sizeof(git_remote)); + remote->check_cert = 1; remote->name = git__strdup(name); GITERR_CHECK_ALLOC(remote->name); @@ -287,6 +289,7 @@ int git_remote_connect(git_remote *remote, int direction) if (git_transport_new(&t, remote->url) < 0) return -1; + t->check_cert = remote->check_cert; if (t->connect(t, direction) < 0) { goto on_error; } @@ -508,3 +511,10 @@ on_error: git_remote_free(*out); return -1; } + +void git_remote_check_cert(git_remote *remote, int check) +{ + assert(remote); + + remote->check_cert = check; +} diff --git a/src/remote.h b/src/remote.h index 5a1625d05..0949ad434 100644 --- a/src/remote.h +++ b/src/remote.h @@ -19,7 +19,8 @@ struct git_remote { struct git_refspec push; git_transport *transport; git_repository *repo; - unsigned int need_pack:1; + unsigned int need_pack:1, + check_cert; }; #endif diff --git a/src/transport.h b/src/transport.h index 00c140baf..68b92f7a6 100644 --- a/src/transport.h +++ b/src/transport.h @@ -69,6 +69,7 @@ struct git_transport { */ int direction : 1, /* 0 fetch, 1 push */ connected : 1, + check_cert: 1, encrypt : 1; #ifdef GIT_SSL struct gitno_ssl ssl; diff --git a/src/transports/http.c b/src/transports/http.c index 36cc66c69..afbf5ed19 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -692,6 +692,7 @@ int git_transport_https(git_transport **out) return -1; t->parent.encrypt = 1; + t->parent.check_cert = 1; *out = (git_transport *) t; return 0;