diff options
-rw-r--r-- | include/git2/errors.h | 1 | ||||
-rw-r--r-- | include/git2/remote.h | 8 | ||||
-rw-r--r-- | include/git2/sys/transport.h | 1 | ||||
-rw-r--r-- | include/git2/transport.h | 37 | ||||
-rw-r--r-- | include/git2/types.h | 12 | ||||
-rw-r--r-- | src/netops.c | 7 | ||||
-rw-r--r-- | src/remote.c | 3 | ||||
-rw-r--r-- | src/transports/http.c | 28 | ||||
-rw-r--r-- | src/transports/smart.c | 2 | ||||
-rw-r--r-- | src/transports/smart.h | 1 | ||||
-rw-r--r-- | src/transports/ssh.c | 34 | ||||
-rw-r--r-- | tests/online/push_util.h | 2 |
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; |