summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/errors.h1
-rw-r--r--include/git2/remote.h8
-rw-r--r--include/git2/sys/transport.h1
-rw-r--r--include/git2/transport.h37
-rw-r--r--include/git2/types.h12
-rw-r--r--src/netops.c7
-rw-r--r--src/remote.c3
-rw-r--r--src/transports/http.c28
-rw-r--r--src/transports/smart.c2
-rw-r--r--src/transports/smart.h1
-rw-r--r--src/transports/ssh.c34
-rw-r--r--tests/online/push_util.h2
12 files changed, 128 insertions, 8 deletions
diff --git a/include/git2/errors.h b/include/git2/errors.h
index b91560631..2ba9924f5 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -42,6 +42,7 @@ typedef enum {
GIT_ELOCKED = -14, /**< Lock file prevented operation */
GIT_EMODIFIED = -15, /**< Reference value does not match expected */
GIT_EAUTH = -16, /**< Authentication error */
+ GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */
GIT_PASSTHROUGH = -30, /**< Internal only */
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
diff --git a/include/git2/remote.h b/include/git2/remote.h
index c0717fa31..723147590 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -459,6 +459,14 @@ struct git_remote_callbacks {
git_cred_acquire_cb credentials;
/**
+ * If cert verification fails, this will be called to let the
+ * user make the final decision of whether to allow the
+ * connection to proceed. Returns 1 to allow the connection, 0
+ * to disallow it or a negative value to indicate an error.
+ */
+ git_transport_certificate_check_cb certificate_check;
+
+ /**
* During the download of new data, this will be regularly
* called with the current count of progress done by the
* indexer.
diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h
index 62ac455d3..44d41c14d 100644
--- a/include/git2/sys/transport.h
+++ b/include/git2/sys/transport.h
@@ -37,6 +37,7 @@ struct git_transport {
git_transport *transport,
git_transport_message_cb progress_cb,
git_transport_message_cb error_cb,
+ git_transport_certificate_check_cb certificate_check_cb,
void *payload);
/* Connect the transport to the remote repository, using the given
diff --git a/include/git2/transport.h b/include/git2/transport.h
index 7090698ac..cd4429fee 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -20,6 +20,43 @@
*/
GIT_BEGIN_DECL
+/**
+ * Type of host certificate structure that is passed to the check callback
+ */
+typedef enum git_cert_t {
+ /**
+ * The `data` argument to the callback will be a pointer to
+ * OpenSSL's `X509` structure.
+ */
+ GIT_CERT_X509_OPENSSL,
+ GIT_CERT_X509_WINHTTP,
+ /**
+ * The `data` argument to the callback will be a pointer to a
+ * `git_cert_hostkey` structure.
+ */
+ GIT_CERT_HOSTKEY_LIBSSH2,
+} git_cert_t;
+
+/**
+ * Hostkey information taken from libssh2
+ */
+typedef struct {
+ /**
+ * A hostkey type from libssh2, either
+ * `LIBSSH2_HOSTKEY_HASH_MD5` or `LIBSSH2_HOSTKEY_HASH_SHA1`
+ */
+ int type;
+ /**
+ * Hostkey hash. If the type is MD5, only the first 16 bytes
+ * will be set.
+ */
+ unsigned char hash[20];
+} git_cert_hostkey;
+
+/*
+ *** Begin interface for credentials acquisition ***
+ */
+
/** Authentication type requested */
typedef enum {
/* git_cred_userpass_plaintext */
diff --git a/include/git2/types.h b/include/git2/types.h
index 7ed1bcd4c..0009a8aa5 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -253,6 +253,18 @@ typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void
*/
typedef int (*git_transport_message_cb)(const char *str, int len, void *payload);
+
+typedef enum git_cert_t git_cert_t;
+
+/**
+ * Callback for the user's custom certificate checks.
+ *
+ * @param type The type of certificate or host info, SSH or X.509
+ * @param data The data for the certificate or host info
+ * @param payload Payload provided by the caller
+ */
+typedef int (*git_transport_certificate_check_cb)(git_cert_t type, void *data, void *payload);
+
/**
* Opaque structure representing a submodule.
*/
diff --git a/src/netops.c b/src/netops.c
index fceb4fb74..67d49a529 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -384,7 +384,7 @@ on_error:
cert_fail_name:
OPENSSL_free(peer_cn);
giterr_set(GITERR_SSL, "hostname does not match certificate");
- return -1;
+ return GIT_ECERTIFICATE;
}
static int ssl_setup(gitno_socket *socket, const char *host, int flags)
@@ -494,8 +494,9 @@ int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int f
p_freeaddrinfo(info);
#ifdef GIT_SSL
- if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
- return -1;
+ if ((flags & GITNO_CONNECT_SSL) &&
+ (ret = ssl_setup(s_out, host, flags)) < 0)
+ return ret;
#else
/* SSL is not supported */
if (flags & GITNO_CONNECT_SSL) {
diff --git a/src/remote.c b/src/remote.c
index 433015f60..9c93f67a1 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -673,7 +673,7 @@ int git_remote_connect(git_remote *remote, git_direction direction)
return error;
if (t->set_callbacks &&
- (error = t->set_callbacks(t, remote->callbacks.sideband_progress, NULL, remote->callbacks.payload)) < 0)
+ (error = t->set_callbacks(t, remote->callbacks.sideband_progress, NULL, remote->callbacks.certificate_check, remote->callbacks.payload)) < 0)
goto on_error;
if (!remote->check_cert)
@@ -1263,6 +1263,7 @@ int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *cal
return remote->transport->set_callbacks(remote->transport,
remote->callbacks.sideband_progress,
NULL,
+ remote->callbacks.certificate_check,
remote->callbacks.payload);
return 0;
diff --git a/src/transports/http.c b/src/transports/http.c
index f9df53b71..d37059bae 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -19,6 +19,10 @@ git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
};
+#ifdef GIT_SSL
+# include <openssl/x509v3.h>
+#endif
+
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/git-upload-pack";
@@ -524,7 +528,7 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
static int http_connect(http_subtransport *t)
{
- int flags = 0;
+ int flags = 0, error;
if (t->connected &&
http_should_keep_alive(&t->parser) &&
@@ -546,8 +550,26 @@ static int http_connect(http_subtransport *t)
flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
}
- if (gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags) < 0)
- return -1;
+ error = gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags);
+
+#ifdef GIT_SSL
+ if (error == GIT_ECERTIFICATE && t->owner->certificate_check_cb != NULL) {
+ X509 *cert = SSL_get_peer_certificate(t->socket.ssl.ssl);
+ int allow;
+
+ allow = t->owner->certificate_check_cb(GIT_CERT_X509_OPENSSL, cert, t->owner->message_cb_payload);
+ if (allow < 0) {
+ error = allow;
+ } else if (!allow) {
+ error = GIT_ECERTIFICATE;
+ } else {
+ error = 0;
+ }
+ }
+#else
+ if (error < 0)
+ return error;
+#endif
t->connected = 1;
return 0;
diff --git a/src/transports/smart.c b/src/transports/smart.c
index a5c3e82dc..d0f9c90e8 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -53,12 +53,14 @@ static int git_smart__set_callbacks(
git_transport *transport,
git_transport_message_cb progress_cb,
git_transport_message_cb error_cb,
+ git_transport_certificate_check_cb certificate_check_cb,
void *message_cb_payload)
{
transport_smart *t = (transport_smart *)transport;
t->progress_cb = progress_cb;
t->error_cb = error_cb;
+ t->certificate_check_cb = certificate_check_cb;
t->message_cb_payload = message_cb_payload;
return 0;
diff --git a/src/transports/smart.h b/src/transports/smart.h
index b29faae44..44e241adc 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -137,6 +137,7 @@ typedef struct {
int flags;
git_transport_message_cb progress_cb;
git_transport_message_cb error_cb;
+ git_transport_certificate_check_cb certificate_check_cb;
void *message_cb_payload;
git_smart_subtransport *wrapped;
git_smart_subtransport_stream *current_stream;
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index fff81661a..fa7dca7c3 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -517,10 +517,44 @@ static int _git_ssh_setup_conn(
if (error < 0)
goto on_error;
+ if (t->owner->certificate_check_cb != NULL) {
+ git_cert_hostkey cert;
+ const char *key;
+ int allow;
+
+ cert.type = LIBSSH2_HOSTKEY_HASH_SHA1;
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (key != NULL) {
+ memcpy(&cert.hash, key, 20);
+ } else {
+ cert.type = LIBSSH2_HOSTKEY_HASH_MD5;
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+ if (key != NULL)
+ memcpy(&cert.hash, key, 16);
+ }
+
+ if (key == NULL) {
+ giterr_set(GITERR_SSH, "unable to get the host key");
+ return -1;
+ }
+
+ allow = t->owner->certificate_check_cb(GIT_CERT_HOSTKEY_LIBSSH2, &cert, t->owner->message_cb_payload);
+ if (allow < 0) {
+ error = allow;
+ goto on_error;
+ }
+
+ if (!allow) {
+ error = GIT_ECERTIFICATE;
+ goto on_error;
+ }
+ }
+
channel = libssh2_channel_open_session(session);
if (!channel) {
error = -1;
ssh_error(session, "Failed to open SSH channel");
+ error = -1;
goto on_error;
}
diff --git a/tests/online/push_util.h b/tests/online/push_util.h
index a7207c49e..7736912d6 100644
--- a/tests/online/push_util.h
+++ b/tests/online/push_util.h
@@ -12,7 +12,7 @@ extern const git_oid OID_ZERO;
* @param data pointer to a record_callbacks_data instance
*/
#define RECORD_CALLBACKS_INIT(data) \
- { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, record_update_tips_cb, data }
+ { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, data }
typedef struct {
char *name;