Commit 1312f87b by Vicent Marti

Merge pull request #2464 from libgit2/cmn/host-cert-info

Provide a callback for certificate validation
parents 25abbc27 52e09724
...@@ -42,6 +42,7 @@ typedef enum { ...@@ -42,6 +42,7 @@ typedef enum {
GIT_ELOCKED = -14, /**< Lock file prevented operation */ GIT_ELOCKED = -14, /**< Lock file prevented operation */
GIT_EMODIFIED = -15, /**< Reference value does not match expected */ GIT_EMODIFIED = -15, /**< Reference value does not match expected */
GIT_EAUTH = -16, /**< Authentication error */ GIT_EAUTH = -16, /**< Authentication error */
GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */
GIT_PASSTHROUGH = -30, /**< Internal only */ GIT_PASSTHROUGH = -30, /**< Internal only */
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
......
...@@ -408,14 +408,6 @@ GIT_EXTERN(int) git_remote_supported_url(const char* url); ...@@ -408,14 +408,6 @@ GIT_EXTERN(int) git_remote_supported_url(const char* url);
GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo); GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo);
/** /**
* 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);
/**
* Argument to the completion callback which tells it which operation * Argument to the completion callback which tells it which operation
* finished. * finished.
*/ */
...@@ -456,6 +448,14 @@ struct git_remote_callbacks { ...@@ -456,6 +448,14 @@ struct git_remote_callbacks {
git_cred_acquire_cb credentials; 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 * During the download of new data, this will be regularly
* called with the current count of progress done by the * called with the current count of progress done by the
* indexer. * indexer.
......
...@@ -23,9 +23,6 @@ GIT_BEGIN_DECL ...@@ -23,9 +23,6 @@ GIT_BEGIN_DECL
typedef enum { typedef enum {
GIT_TRANSPORTFLAGS_NONE = 0, GIT_TRANSPORTFLAGS_NONE = 0,
/* If the connection is secured with SSL/TLS, the authenticity
* of the server certificate should not be verified. */
GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1
} git_transport_flags_t; } git_transport_flags_t;
typedef struct git_transport git_transport; typedef struct git_transport git_transport;
...@@ -37,6 +34,7 @@ struct git_transport { ...@@ -37,6 +34,7 @@ struct git_transport {
git_transport *transport, git_transport *transport,
git_transport_message_cb progress_cb, git_transport_message_cb progress_cb,
git_transport_message_cb error_cb, git_transport_message_cb error_cb,
git_transport_certificate_check_cb certificate_check_cb,
void *payload); void *payload);
/* Connect the transport to the remote repository, using the given /* Connect the transport to the remote repository, using the given
......
...@@ -20,6 +20,67 @@ ...@@ -20,6 +20,67 @@
*/ */
GIT_BEGIN_DECL GIT_BEGIN_DECL
/**
* Type of SSH host fingerprint
*/
typedef enum {
/** MD5 is available */
GIT_CERT_SSH_MD5 = (1 << 0),
/** SHA-1 is available */
GIT_CERT_SSH_SHA1 = (1 << 1),
} git_cert_ssh_t;
/**
* Hostkey information taken from libssh2
*/
typedef struct {
/**
* Type of certificate. Here to share the header with
* `git_cert`.
*/
git_cert_t cert_type;
/**
* A hostkey type from libssh2, either
* `GIT_CERT_SSH_MD5` or `GIT_CERT_SSH_SHA1`
*/
git_cert_ssh_t type;
/**
* Hostkey hash. If type has `GIT_CERT_SSH_MD5` set, this will
* have the MD5 hash of the hostkey.
*/
unsigned char hash_md5[16];
/**
* Hostkey hash. If type has `GIT_CERT_SSH_SHA1` set, this will
* have the SHA-1 hash of the hostkey.
*/
unsigned char hash_sha1[20];
} git_cert_hostkey;
/**
* X.509 certificate information
*/
typedef struct {
/**
* Type of certificate. Here to share the header with
* `git_cert`.
*/
git_cert_t cert_type;
/**
* Pointer to the X.509 certificate data
*/
void *data;
/**
* Length of the memory block pointed to by `data`.
*/
size_t len;
} git_cert_x509;
/*
*** Begin interface for credentials acquisition ***
*/
/** Authentication type requested */ /** Authentication type requested */
typedef enum { typedef enum {
/* git_cred_userpass_plaintext */ /* git_cred_userpass_plaintext */
......
...@@ -254,6 +254,44 @@ typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void ...@@ -254,6 +254,44 @@ 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 int (*git_transport_message_cb)(const char *str, int len, void *payload);
/** /**
* 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
* the DER-encoded data.
*/
GIT_CERT_X509,
/**
* The `data` argument to the callback will be a pointer to a
* `git_cert_hostkey` structure.
*/
GIT_CERT_HOSTKEY_LIBSSH2,
} git_cert_t;
/**
* Parent type for `git_cert_hostkey` and `git_cert_x509`.
*/
typedef struct {
/**
* Type of certificate. A `GIT_CERT_` value.
*/
git_cert_t cert_type;
} git_cert;
/**
* 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 len The size of the certificate or host info
* @param valid Whether the libgit2 checks (OpenSSL or WinHTTP) think
* this certificate is valid
* @param payload Payload provided by the caller
*/
typedef int (*git_transport_certificate_check_cb)(git_cert *cert, int valid, void *payload);
/**
* Opaque structure representing a submodule. * Opaque structure representing a submodule.
*/ */
typedef struct git_submodule git_submodule; typedef struct git_submodule git_submodule;
......
...@@ -15,7 +15,7 @@ export GITTEST_REMOTE_URL="git://localhost/test.git" ...@@ -15,7 +15,7 @@ export GITTEST_REMOTE_URL="git://localhost/test.git"
mkdir _build mkdir _build
cd _build cd _build
cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $?
cmake --build . --target install || exit $? make -j2 install || exit $?
ctest -V . || exit $? ctest -V . || exit $?
# Now that we've tested the raw git protocol, let's set up ssh to we # Now that we've tested the raw git protocol, let's set up ssh to we
...@@ -33,6 +33,9 @@ ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q ...@@ -33,6 +33,9 @@ ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q
cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys
ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts
# Get the fingerprint for localhost and remove the colons so we can parse it as a hex number
export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -F localhost -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':')
export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git" export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git"
export GITTEST_REMOTE_USER=$USER export GITTEST_REMOTE_USER=$USER
export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa" export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa"
...@@ -40,6 +43,6 @@ export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" ...@@ -40,6 +43,6 @@ export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub"
export GITTEST_REMOTE_SSH_PASSPHRASE="" export GITTEST_REMOTE_SSH_PASSPHRASE=""
if [ -e ./libgit2_clar ]; then if [ -e ./libgit2_clar ]; then
./libgit2_clar -sonline::push -sonline::clone::cred_callback && ./libgit2_clar -sonline::push -sonline::clone::cred_callback -sonline::clone::ssh_cert &&
./libgit2_clar -sonline::clone::ssh_with_paths ./libgit2_clar -sonline::clone::ssh_with_paths
fi fi
...@@ -384,10 +384,10 @@ on_error: ...@@ -384,10 +384,10 @@ on_error:
cert_fail_name: cert_fail_name:
OPENSSL_free(peer_cn); OPENSSL_free(peer_cn);
giterr_set(GITERR_SSL, "hostname does not match certificate"); 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) static int ssl_setup(gitno_socket *socket, const char *host)
{ {
int ret; int ret;
...@@ -406,9 +406,6 @@ static int ssl_setup(gitno_socket *socket, const char *host, int flags) ...@@ -406,9 +406,6 @@ static int ssl_setup(gitno_socket *socket, const char *host, int flags)
if ((ret = SSL_connect(socket->ssl.ssl)) <= 0) if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
return ssl_set_error(&socket->ssl, ret); return ssl_set_error(&socket->ssl, ret);
if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
return 0;
return verify_server_cert(&socket->ssl, host); return verify_server_cert(&socket->ssl, host);
} }
#endif #endif
...@@ -494,8 +491,9 @@ int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int f ...@@ -494,8 +491,9 @@ int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int f
p_freeaddrinfo(info); p_freeaddrinfo(info);
#ifdef GIT_SSL #ifdef GIT_SSL
if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0) if ((flags & GITNO_CONNECT_SSL) &&
return -1; (ret = ssl_setup(s_out, host)) < 0)
return ret;
#else #else
/* SSL is not supported */ /* SSL is not supported */
if (flags & GITNO_CONNECT_SSL) { if (flags & GITNO_CONNECT_SSL) {
......
...@@ -47,10 +47,6 @@ typedef struct gitno_buffer gitno_buffer; ...@@ -47,10 +47,6 @@ typedef struct gitno_buffer gitno_buffer;
enum { enum {
/* Attempt to create an SSL connection. */ /* Attempt to create an SSL connection. */
GITNO_CONNECT_SSL = 1, GITNO_CONNECT_SSL = 1,
/* Valid only when GITNO_CONNECT_SSL is also specified.
* Indicates that the server certificate should not be validated. */
GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,
}; };
/** /**
......
...@@ -80,6 +80,8 @@ static int ensure_remote_name_is_valid(const char *name) ...@@ -80,6 +80,8 @@ static int ensure_remote_name_is_valid(const char *name)
return error; return error;
} }
#if 0
/* We could export this as a helper */
static int get_check_cert(int *out, git_repository *repo) static int get_check_cert(int *out, git_repository *repo)
{ {
git_config *cfg; git_config *cfg;
...@@ -105,6 +107,7 @@ static int get_check_cert(int *out, git_repository *repo) ...@@ -105,6 +107,7 @@ static int get_check_cert(int *out, git_repository *repo)
*out = git_config__get_bool_force(cfg, "http.sslverify", 1); *out = git_config__get_bool_force(cfg, "http.sslverify", 1);
return 0; return 0;
} }
#endif
static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{ {
...@@ -121,9 +124,6 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n ...@@ -121,9 +124,6 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
remote->repo = repo; remote->repo = repo;
remote->update_fetchhead = 1; remote->update_fetchhead = 1;
if (get_check_cert(&remote->check_cert, repo) < 0)
goto on_error;
if (git_vector_init(&remote->refs, 32, NULL) < 0) if (git_vector_init(&remote->refs, 32, NULL) < 0)
goto on_error; goto on_error;
...@@ -274,7 +274,6 @@ int git_remote_dup(git_remote **dest, git_remote *source) ...@@ -274,7 +274,6 @@ int git_remote_dup(git_remote **dest, git_remote *source)
remote->transport_cb_payload = source->transport_cb_payload; remote->transport_cb_payload = source->transport_cb_payload;
remote->repo = source->repo; remote->repo = source->repo;
remote->download_tags = source->download_tags; remote->download_tags = source->download_tags;
remote->check_cert = source->check_cert;
remote->update_fetchhead = source->update_fetchhead; remote->update_fetchhead = source->update_fetchhead;
if (git_vector_init(&remote->refs, 32, NULL) < 0 || if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
...@@ -369,9 +368,6 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) ...@@ -369,9 +368,6 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
remote->name = git__strdup(name); remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name); GITERR_CHECK_ALLOC(remote->name);
if ((error = get_check_cert(&remote->check_cert, repo)) < 0)
goto cleanup;
if (git_vector_init(&remote->refs, 32, NULL) < 0 || if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
git_vector_init(&remote->refspecs, 2, NULL) < 0 || git_vector_init(&remote->refspecs, 2, NULL) < 0 ||
git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { git_vector_init(&remote->active_refspecs, 2, NULL) < 0) {
...@@ -673,12 +669,9 @@ int git_remote_connect(git_remote *remote, git_direction direction) ...@@ -673,12 +669,9 @@ int git_remote_connect(git_remote *remote, git_direction direction)
return error; return error;
if (t->set_callbacks && 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; goto on_error;
if (!remote->check_cert)
flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
if ((error = t->connect(t, url, remote->callbacks.credentials, remote->callbacks.payload, direction, flags)) != 0) if ((error = t->connect(t, url, remote->callbacks.credentials, remote->callbacks.payload, direction, flags)) != 0)
goto on_error; goto on_error;
...@@ -1248,13 +1241,6 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) ...@@ -1248,13 +1241,6 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
return 0; return 0;
} }
void git_remote_check_cert(git_remote *remote, int check)
{
assert(remote);
remote->check_cert = check;
}
int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks) int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks)
{ {
assert(remote && callbacks); assert(remote && callbacks);
...@@ -1267,6 +1253,7 @@ int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *cal ...@@ -1267,6 +1253,7 @@ int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *cal
return remote->transport->set_callbacks(remote->transport, return remote->transport->set_callbacks(remote->transport,
remote->callbacks.sideband_progress, remote->callbacks.sideband_progress,
NULL, NULL,
remote->callbacks.certificate_check,
remote->callbacks.payload); remote->callbacks.payload);
return 0; return 0;
......
...@@ -31,7 +31,6 @@ struct git_remote { ...@@ -31,7 +31,6 @@ struct git_remote {
git_transfer_progress stats; git_transfer_progress stats;
unsigned int need_pack; unsigned int need_pack;
git_remote_autotag_option_t download_tags; git_remote_autotag_option_t download_tags;
int check_cert;
int update_fetchhead; int update_fetchhead;
}; };
......
...@@ -19,6 +19,10 @@ git_http_auth_scheme auth_schemes[] = { ...@@ -19,6 +19,10 @@ git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic }, { 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_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-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"; 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) ...@@ -524,7 +528,7 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
static int http_connect(http_subtransport *t) static int http_connect(http_subtransport *t)
{ {
int flags = 0; int flags = 0, error;
if (t->connected && if (t->connected &&
http_should_keep_alive(&t->parser) && http_should_keep_alive(&t->parser) &&
...@@ -541,13 +545,55 @@ static int http_connect(http_subtransport *t) ...@@ -541,13 +545,55 @@ static int http_connect(http_subtransport *t)
return -1; return -1;
flags |= GITNO_CONNECT_SSL; flags |= GITNO_CONNECT_SSL;
if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags)
flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
} }
if (gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags) < 0) error = gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags);
return -1;
#ifdef GIT_SSL
if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL) {
X509 *cert = SSL_get_peer_certificate(t->socket.ssl.ssl);
git_cert_x509 cert_info;
int len, is_valid;
unsigned char *guard, *encoded_cert;
/* Retrieve the length of the certificate first */
len = i2d_X509(cert, NULL);
if (len < 0) {
giterr_set(GITERR_NET, "failed to retrieve certificate information");
return -1;
}
encoded_cert = git__malloc(len);
GITERR_CHECK_ALLOC(encoded_cert);
/* i2d_X509 makes 'copy' point to just after the data */
guard = encoded_cert;
len = i2d_X509(cert, &guard);
if (len < 0) {
git__free(encoded_cert);
giterr_set(GITERR_NET, "failed to retrieve certificate information");
return -1;
}
giterr_clear();
is_valid = error != GIT_ECERTIFICATE;
cert_info.cert_type = GIT_CERT_X509;
cert_info.data = encoded_cert;
cert_info.len = len;
error = t->owner->certificate_check_cb((git_cert *) &cert_info, is_valid, t->owner->message_cb_payload);
git__free(encoded_cert);
if (error < 0) {
if (!giterr_last())
giterr_set(GITERR_NET, "user cancelled certificate check");
return error;
}
}
#endif
if (error < 0)
return error;
t->connected = 1; t->connected = 1;
return 0; return 0;
...@@ -608,6 +654,7 @@ replay: ...@@ -608,6 +654,7 @@ replay:
while (!*bytes_read && !t->parse_finished) { while (!*bytes_read && !t->parse_finished) {
size_t data_offset; size_t data_offset;
int error;
/* /*
* Make the parse_buffer think it's as full of data as * Make the parse_buffer think it's as full of data as
...@@ -654,8 +701,8 @@ replay: ...@@ -654,8 +701,8 @@ replay:
if (PARSE_ERROR_REPLAY == t->parse_error) { if (PARSE_ERROR_REPLAY == t->parse_error) {
s->sent_request = 0; s->sent_request = 0;
if (http_connect(t) < 0) if ((error = http_connect(t)) < 0)
return -1; return error;
goto replay; goto replay;
} }
...@@ -907,8 +954,8 @@ static int http_action( ...@@ -907,8 +954,8 @@ static int http_action(
(ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0) (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
return ret; return ret;
if (http_connect(t) < 0) if ((ret = http_connect(t)) < 0)
return -1; return ret;
switch (action) { switch (action) {
case GIT_SERVICE_UPLOADPACK_LS: case GIT_SERVICE_UPLOADPACK_LS:
......
...@@ -53,12 +53,14 @@ static int git_smart__set_callbacks( ...@@ -53,12 +53,14 @@ static int git_smart__set_callbacks(
git_transport *transport, git_transport *transport,
git_transport_message_cb progress_cb, git_transport_message_cb progress_cb,
git_transport_message_cb error_cb, git_transport_message_cb error_cb,
git_transport_certificate_check_cb certificate_check_cb,
void *message_cb_payload) void *message_cb_payload)
{ {
transport_smart *t = (transport_smart *)transport; transport_smart *t = (transport_smart *)transport;
t->progress_cb = progress_cb; t->progress_cb = progress_cb;
t->error_cb = error_cb; t->error_cb = error_cb;
t->certificate_check_cb = certificate_check_cb;
t->message_cb_payload = message_cb_payload; t->message_cb_payload = message_cb_payload;
return 0; return 0;
......
...@@ -137,6 +137,7 @@ typedef struct { ...@@ -137,6 +137,7 @@ typedef struct {
int flags; int flags;
git_transport_message_cb progress_cb; git_transport_message_cb progress_cb;
git_transport_message_cb error_cb; git_transport_message_cb error_cb;
git_transport_certificate_check_cb certificate_check_cb;
void *message_cb_payload; void *message_cb_payload;
git_smart_subtransport *wrapped; git_smart_subtransport *wrapped;
git_smart_subtransport_stream *current_stream; git_smart_subtransport_stream *current_stream;
......
...@@ -473,6 +473,46 @@ static int _git_ssh_setup_conn( ...@@ -473,6 +473,46 @@ static int _git_ssh_setup_conn(
GITERR_CHECK_ALLOC(port); GITERR_CHECK_ALLOC(port);
} }
if ((error = gitno_connect(&s->socket, host, port, 0)) < 0)
goto on_error;
if ((error = _git_ssh_session_create(&session, s->socket)) < 0)
goto on_error;
if (t->owner->certificate_check_cb != NULL) {
git_cert_hostkey cert = { 0 };
const char *key;
cert.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
if (key != NULL) {
cert.type |= GIT_CERT_SSH_SHA1;
memcpy(&cert.hash_sha1, key, 20);
}
key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
if (key != NULL) {
cert.type |= GIT_CERT_SSH_MD5;
memcpy(&cert.hash_md5, key, 16);
}
if (cert.type == 0) {
giterr_set(GITERR_SSH, "unable to get the host key");
return -1;
}
/* We don't currently trust any hostkeys */
giterr_clear();
error = t->owner->certificate_check_cb((git_cert *) &cert, 0, t->owner->message_cb_payload);
if (error < 0) {
if (!giterr_last())
giterr_set(GITERR_NET, "user cancelled hostkey check");
goto on_error;
}
}
/* we need the username to ask for auth methods */ /* we need the username to ask for auth methods */
if (!user) { if (!user) {
if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
...@@ -488,12 +528,6 @@ static int _git_ssh_setup_conn( ...@@ -488,12 +528,6 @@ static int _git_ssh_setup_conn(
goto on_error; goto on_error;
} }
if ((error = gitno_connect(&s->socket, host, port, 0)) < 0)
goto on_error;
if ((error = _git_ssh_session_create(&session, s->socket)) < 0)
goto on_error;
if ((error = list_auth_methods(&auth_methods, session, user)) < 0) if ((error = list_auth_methods(&auth_methods, session, user)) < 0)
goto on_error; goto on_error;
...@@ -602,10 +636,8 @@ static int ssh_receivepack_ls( ...@@ -602,10 +636,8 @@ static int ssh_receivepack_ls(
{ {
const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
if (_git_ssh_setup_conn(t, url, cmd, stream) < 0)
return -1;
return 0; return _git_ssh_setup_conn(t, url, cmd, stream);
} }
static int ssh_receivepack( static int ssh_receivepack(
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include "remote.h" #include "remote.h"
#include "repository.h" #include "repository.h"
#include <wincrypt.h>
#pragma comment(lib, "crypt32")
#include <winhttp.h> #include <winhttp.h>
#pragma comment(lib, "winhttp") #pragma comment(lib, "winhttp")
...@@ -203,6 +205,39 @@ static int fallback_cred_acquire_cb( ...@@ -203,6 +205,39 @@ static int fallback_cred_acquire_cb(
return error; return error;
} }
static int certificate_check(winhttp_stream *s, int valid)
{
int error;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
PCERT_CONTEXT cert_ctx;
DWORD cert_ctx_size = sizeof(cert_ctx);
git_cert_x509 cert;
/* If there is no override, we should fail if WinHTTP doesn't think it's fine */
if (t->owner->certificate_check_cb == NULL && !valid)
return GIT_ECERTIFICATE;
if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl)
return 0;
if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
giterr_set(GITERR_OS, "failed to get server certificate");
return -1;
}
giterr_clear();
cert.cert_type = GIT_CERT_X509;
cert.data = cert_ctx->pbCertEncoded;
cert.len = cert_ctx->cbCertEncoded;
error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->owner->cred_acquire_payload);
CertFreeCertificateContext(cert_ctx);
if (error < 0 && !giterr_last())
giterr_set(GITERR_NET, "user cancelled certificate check");
return error;
}
static int winhttp_stream_connect(winhttp_stream *s) static int winhttp_stream_connect(winhttp_stream *s)
{ {
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
...@@ -353,13 +388,6 @@ static int winhttp_stream_connect(winhttp_stream *s) ...@@ -353,13 +388,6 @@ static int winhttp_stream_connect(winhttp_stream *s)
if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
goto on_error; goto on_error;
if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) &&
!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
(LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
goto on_error;
}
} }
/* If we have a credential on the subtransport, apply it to the request */ /* If we have a credential on the subtransport, apply it to the request */
...@@ -527,6 +555,74 @@ on_error: ...@@ -527,6 +555,74 @@ on_error:
return error; return error;
} }
static int do_send_request(winhttp_stream *s, size_t len, int ignore_length)
{
int request_failed = 0, cert_valid = 1, error = 0;
if (ignore_length) {
if (!WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
return -1;
}
} else {
if (!WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
len, 0)) {
return -1;
}
}
return 0;
}
static int send_request(winhttp_stream *s, size_t len, int ignore_length)
{
int request_failed = 0, cert_valid = 1, error = 0;
DWORD ignore_flags;
if ((error = do_send_request(s, len, ignore_length)) < 0)
request_failed = 1;
if (request_failed) {
if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE) {
giterr_set(GITERR_OS, "failed to send request");
return -1;
} else {
cert_valid = 0;
}
}
giterr_clear();
if ((error = certificate_check(s, cert_valid)) < 0) {
if (!giterr_last())
giterr_set(GITERR_OS, "user cancelled certificate check");
return error;
}
/* if neither the request nor the certificate check returned errors, we're done */
if (!request_failed)
return 0;
ignore_flags =
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_UNKNOWN_CA;
if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) {
giterr_set(GITERR_OS, "failed to set security options");
return -1;
}
if ((error = do_send_request(s, len, ignore_length)) < 0)
giterr_set(GITERR_OS, "failed to send request");
return error;
}
static int winhttp_stream_read( static int winhttp_stream_read(
git_smart_subtransport_stream *stream, git_smart_subtransport_stream *stream,
char *buffer, char *buffer,
...@@ -537,6 +633,7 @@ static int winhttp_stream_read( ...@@ -537,6 +633,7 @@ static int winhttp_stream_read(
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD dw_bytes_read; DWORD dw_bytes_read;
char replay_count = 0; char replay_count = 0;
int error;
replay: replay:
/* Enforce a reasonable cap on the number of replays */ /* Enforce a reasonable cap on the number of replays */
...@@ -553,15 +650,12 @@ replay: ...@@ -553,15 +650,12 @@ replay:
DWORD status_code, status_code_length, content_type_length, bytes_written; DWORD status_code, status_code_length, content_type_length, bytes_written;
char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
int request_failed = 0, cert_valid = 1;
if (!s->sent_request) { if (!s->sent_request) {
if (!WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0, if ((error = send_request(s, s->post_body_len, 0)) < 0)
WINHTTP_NO_REQUEST_DATA, 0, return error;
s->post_body_len, 0)) {
giterr_set(GITERR_OS, "Failed to send request");
return -1;
}
s->sent_request = 1; s->sent_request = 1;
} }
...@@ -815,6 +909,7 @@ static int winhttp_stream_write_single( ...@@ -815,6 +909,7 @@ static int winhttp_stream_write_single(
winhttp_stream *s = (winhttp_stream *)stream; winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD bytes_written; DWORD bytes_written;
int error;
if (!s->request && winhttp_stream_connect(s) < 0) if (!s->request && winhttp_stream_connect(s) < 0)
return -1; return -1;
...@@ -825,13 +920,8 @@ static int winhttp_stream_write_single( ...@@ -825,13 +920,8 @@ static int winhttp_stream_write_single(
return -1; return -1;
} }
if (!WinHttpSendRequest(s->request, if ((error = send_request(s, len, 0)) < 0)
WINHTTP_NO_ADDITIONAL_HEADERS, 0, return error;
WINHTTP_NO_REQUEST_DATA, 0,
(DWORD)len, 0)) {
giterr_set(GITERR_OS, "Failed to send request");
return -1;
}
s->sent_request = 1; s->sent_request = 1;
...@@ -954,6 +1044,7 @@ static int winhttp_stream_write_chunked( ...@@ -954,6 +1044,7 @@ static int winhttp_stream_write_chunked(
{ {
winhttp_stream *s = (winhttp_stream *)stream; winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
int error;
if (!s->request && winhttp_stream_connect(s) < 0) if (!s->request && winhttp_stream_connect(s) < 0)
return -1; return -1;
...@@ -967,13 +1058,8 @@ static int winhttp_stream_write_chunked( ...@@ -967,13 +1058,8 @@ static int winhttp_stream_write_chunked(
return -1; return -1;
} }
if (!WinHttpSendRequest(s->request, if ((error = send_request(s, 0, 1)) < 0)
WINHTTP_NO_ADDITIONAL_HEADERS, 0, return error;
WINHTTP_NO_REQUEST_DATA, 0,
WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
giterr_set(GITERR_OS, "Failed to send request");
return -1;
}
s->sent_request = 1; s->sent_request = 1;
} }
......
...@@ -473,8 +473,90 @@ void test_online_clone__ssh_cannot_change_username(void) ...@@ -473,8 +473,90 @@ void test_online_clone__ssh_cannot_change_username(void)
cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
} }
int ssh_certificate_check(git_cert *cert, int valid, void *payload)
{
git_cert_hostkey *key;
git_oid expected = {{0}}, actual = {{0}};
const char *expected_str;
GIT_UNUSED(valid);
GIT_UNUSED(payload);
expected_str = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT");
cl_assert(expected_str);
cl_git_pass(git_oid_fromstrp(&expected, expected_str));
cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type);
key = (git_cert_hostkey *) cert;
/*
* We need to figure out how long our input was to check for
* the type. Here we abuse the fact that both hashes fit into
* our git_oid type.
*/
if (strlen(expected_str) == 32 && key->type & GIT_CERT_SSH_MD5) {
memcpy(&actual.id, key->hash_md5, 16);
} else if (strlen(expected_str) == 40 && key->type & GIT_CERT_SSH_SHA1) {
memcpy(&actual, key->hash_sha1, 20);
} else {
cl_fail("Cannot find a usable SSH hash");
}
cl_assert(!memcmp(&expected, &actual, 20));
return GIT_EUSER;
}
void test_online_clone__ssh_cert(void)
{
g_options.remote_callbacks.certificate_check = ssh_certificate_check;
if (!cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT"))
cl_skip();
cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, "ssh://localhost/foo", "./foo", &g_options));
}
void test_online_clone__url_with_no_path_returns_EINVALIDSPEC(void) void test_online_clone__url_with_no_path_returns_EINVALIDSPEC(void)
{ {
cl_git_fail_with(git_clone(&g_repo, "http://github.com", "./foo", &g_options), cl_git_fail_with(git_clone(&g_repo, "http://github.com", "./foo", &g_options),
GIT_EINVALIDSPEC); GIT_EINVALIDSPEC);
} }
static int fail_certificate_check(git_cert *cert, int valid, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
return GIT_ECERTIFICATE;
}
void test_online_clone__certificate_invalid(void)
{
g_options.remote_callbacks.certificate_check = fail_certificate_check;
cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options),
GIT_ECERTIFICATE);
#ifdef GIT_SSH
cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options),
GIT_ECERTIFICATE);
#endif
}
static int succeed_certificate_check(git_cert *cert, int valid, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
return 0;
}
void test_online_clone__certificate_valid(void)
{
g_options.remote_callbacks.certificate_check = succeed_certificate_check;
cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
...@@ -12,7 +12,7 @@ extern const git_oid OID_ZERO; ...@@ -12,7 +12,7 @@ extern const git_oid OID_ZERO;
* @param data pointer to a record_callbacks_data instance * @param data pointer to a record_callbacks_data instance
*/ */
#define RECORD_CALLBACKS_INIT(data) \ #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 { typedef struct {
char *name; char *name;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment