From 7a8b85032f4390b6b14e55e0074d864fe742ca3b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 17 Mar 2015 09:19:15 -0700 Subject: [PATCH 1/6] Add support to read ssh keys from memory. --- include/git2/transport.h | 28 +++++++++++++++++++++++ src/transports/cred.c | 48 +++++++++++++++++++++++++++++++++++++++- src/transports/ssh.c | 19 ++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 99fd09a1b..57293d92f 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -108,6 +108,15 @@ typedef enum { * it will ask via this credential type. */ GIT_CREDTYPE_USERNAME = (1u << 5), + +#ifdef GIT_SSH_MEMORY_CREDENTIALS + /** + * Credentials read from memory. + * + * Only available for libssh2+OpenSSL for now. + */ + GIT_CREDTYPE_SSH_MEMORY = (1u << 6), +#endif } git_credtype_t; /* The base structure for all credential types */ @@ -290,6 +299,25 @@ GIT_EXTERN(int) git_cred_default_new(git_cred **out); */ GIT_EXTERN(int) git_cred_username_new(git_cred **cred, const char *username); +#ifdef GIT_SSH_MEMORY_CREDENTIALS +/** + * Create a new ssh key credential object reading the keys from memory. + * + * @param out The newly created credential object. + * @param username username to use to authenticate. + * @param publickey The public key of the credential. + * @param privatekey The private key of the credential. + * @param passphrase The passphrase of the credential. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_cred_ssh_key_memory_new( + git_cred **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); +#endif + /** * Signature of a function which acquires a credential object. * diff --git a/src/transports/cred.c b/src/transports/cred.c index 8163d3115..68007fb95 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -9,6 +9,14 @@ #include "smart.h" #include "git2/cred_helpers.h" +static int git_cred_ssh_key_type_new( + git_cred **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credtype_t credtype); + int git_cred_has_username(git_cred *cred) { if (cred->credtype == GIT_CREDTYPE_DEFAULT) @@ -31,6 +39,9 @@ const char *git_cred__username(git_cred *cred) return c->username; } case GIT_CREDTYPE_SSH_KEY: +#ifdef GIT_SSH_MEMORY_CREDENTIALS + case GIT_CREDTYPE_SSH_MEMORY: +#endif { git_cred_ssh_key *c = (git_cred_ssh_key *) cred; return c->username; @@ -174,6 +185,41 @@ int git_cred_ssh_key_new( const char *publickey, const char *privatekey, const char *passphrase) +{ + return git_cred_ssh_key_type_new( + cred, + username, + publickey, + privatekey, + passphrase, + GIT_CREDTYPE_SSH_KEY); +} + +#ifdef GIT_SSH_MEMORY_CREDENTIALS +int git_cred_ssh_key_memory_new( + git_cred **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_cred_ssh_key_type_new( + cred, + username, + publickey, + privatekey, + passphrase, + GIT_CREDTYPE_SSH_MEMORY); +} +#endif + +static int git_cred_ssh_key_type_new( + git_cred **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credtype_t credtype) { git_cred_ssh_key *c; @@ -182,7 +228,7 @@ int git_cred_ssh_key_new( c = git__calloc(1, sizeof(git_cred_ssh_key)); GITERR_CHECK_ALLOC(c); - c->parent.credtype = GIT_CREDTYPE_SSH_KEY; + c->parent.credtype = credtype; c->parent.free = ssh_key_free; c->username = git__strdup(username); diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 55f715b1d..0a7422791 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -370,6 +370,22 @@ static int _git_ssh_authenticate_session( session, c->username, c->prompt_callback); break; } +#ifdef GIT_SSH_MEMORY_CREDENTIALS + case GIT_CREDTYPE_SSH_MEMORY: { + git_cred_ssh_key *c = (git_cred_ssh_key *)cred; + + rc = libssh2_userauth_publickey_frommemory( + session, + c->username, + strlen(c->username), + c->publickey, + strlen(c->publickey), + c->privatekey, + strlen(c->privatekey), + c->passphrase); + break; + } +#endif default: rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } @@ -740,6 +756,9 @@ static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *use if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { *out |= GIT_CREDTYPE_SSH_KEY; *out |= GIT_CREDTYPE_SSH_CUSTOM; +#ifdef GIT_SSH_MEMORY_CREDENTIALS + *out |= GIT_CREDTYPE_SSH_MEMORY; +#endif ptr += strlen(SSH_AUTH_PUBLICKEY); continue; } From 08e6b875c11e71b27154d4fb0d36d56ac40aba33 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 19 Mar 2015 14:57:15 -0700 Subject: [PATCH 2/6] Return an error when ssh memory credentials are not supported. To not modify the external api. --- include/git2/transport.h | 2 -- src/transports/cred.c | 8 ++++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 57293d92f..4008615ab 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -299,7 +299,6 @@ GIT_EXTERN(int) git_cred_default_new(git_cred **out); */ GIT_EXTERN(int) git_cred_username_new(git_cred **cred, const char *username); -#ifdef GIT_SSH_MEMORY_CREDENTIALS /** * Create a new ssh key credential object reading the keys from memory. * @@ -316,7 +315,6 @@ GIT_EXTERN(int) git_cred_ssh_key_memory_new( const char *publickey, const char *privatekey, const char *passphrase); -#endif /** * Signature of a function which acquires a credential object. diff --git a/src/transports/cred.c b/src/transports/cred.c index 68007fb95..c1f305d77 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -195,7 +195,6 @@ int git_cred_ssh_key_new( GIT_CREDTYPE_SSH_KEY); } -#ifdef GIT_SSH_MEMORY_CREDENTIALS int git_cred_ssh_key_memory_new( git_cred **cred, const char *username, @@ -203,6 +202,7 @@ int git_cred_ssh_key_memory_new( const char *privatekey, const char *passphrase) { +#ifdef GIT_SSH_MEMORY_CREDENTIALS return git_cred_ssh_key_type_new( cred, username, @@ -210,8 +210,12 @@ int git_cred_ssh_key_memory_new( privatekey, passphrase, GIT_CREDTYPE_SSH_MEMORY); -} +#else + giterr_set(GITERR_INVALID, + "This version of libgit2 was not built with ssh memory credentials."); + return -1; #endif +} static int git_cred_ssh_key_type_new( git_cred **cred, From 1679ec1204b53580a7b5cd6165865854c912dc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sun, 24 May 2015 18:27:15 +0200 Subject: [PATCH 3/6] cmake: Add CMake check for libssh2 memory credential passing support --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 181d30127..c3c7a1543 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,6 +270,11 @@ IF (LIBSSH2_FOUND) LINK_DIRECTORIES(${LIBSSH2_LIBRARY_DIRS}) SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} libssh2") SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES}) + + CHECK_LIBRARY_EXISTS(${LIBSSH2_LIBRARIES} libssh2_userauth_publickey_frommemory "" HAVE_LIBSSH2_MEMORY_CREDENTIALS) + IF (HAVE_LIBSSH2_MEMORY_CREDENTIALS) + ADD_DEFINITIONS(-DGIT_SSH_MEMORY_CREDENTIALS) + ENDIF() ELSE() MESSAGE(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") ENDIF() From f7142b5e4a9ce3f389f7851aac2fc98ce7bef764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sun, 24 May 2015 18:38:47 +0200 Subject: [PATCH 4/6] cred: Declare GIT_CREDTYPE_SSH_MEMORY unconditionally Declare GIT_CREDTYPE_SSH_MEMORY to have consistent API independently of whether libgit2 was built with or without in-memory key passing support. Or rather, to have it at all since build-time definitions are not stored in headers. --- include/git2/transport.h | 2 -- src/transports/cred.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 4008615ab..2eeebd565 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -109,14 +109,12 @@ typedef enum { */ GIT_CREDTYPE_USERNAME = (1u << 5), -#ifdef GIT_SSH_MEMORY_CREDENTIALS /** * Credentials read from memory. * * Only available for libssh2+OpenSSL for now. */ GIT_CREDTYPE_SSH_MEMORY = (1u << 6), -#endif } git_credtype_t; /* The base structure for all credential types */ diff --git a/src/transports/cred.c b/src/transports/cred.c index c1f305d77..006cd2c52 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -39,9 +39,7 @@ const char *git_cred__username(git_cred *cred) return c->username; } case GIT_CREDTYPE_SSH_KEY: -#ifdef GIT_SSH_MEMORY_CREDENTIALS case GIT_CREDTYPE_SSH_MEMORY: -#endif { git_cred_ssh_key *c = (git_cred_ssh_key *) cred; return c->username; From 2629fc874da258d76a63a99566a5ba457c9d5d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sun, 24 May 2015 22:33:55 +0200 Subject: [PATCH 5/6] cred: Check for null values when getting key from memory The public key field is optional and as such can take NULL. Account for that and do not call strlen() on NULL values. Also assert() for non-NULL values of username & private key. --- src/transports/ssh.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 0a7422791..58f1aeb64 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -374,12 +374,15 @@ static int _git_ssh_authenticate_session( case GIT_CREDTYPE_SSH_MEMORY: { git_cred_ssh_key *c = (git_cred_ssh_key *)cred; + assert(c->username); + assert(c->privatekey); + rc = libssh2_userauth_publickey_frommemory( session, c->username, strlen(c->username), c->publickey, - strlen(c->publickey), + c->publickey ? strlen(c->publickey) : 0, c->privatekey, strlen(c->privatekey), c->passphrase); From 8085adf82cbb57487d3ac9acdf0b5bcba92a9a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 27 May 2015 18:44:40 +0200 Subject: [PATCH 6/6] test: Add a test for in-memory SSH private key cred_cb --- tests/online/clone.c | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/online/clone.c b/tests/online/clone.c index 1930a8ba3..fa0dbd69c 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -501,6 +501,74 @@ void test_online_clone__ssh_cert(void) cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, "ssh://localhost/foo", "./foo", &g_options)); } +static char *read_key_file(const char *path) +{ + FILE *f; + char *buf; + long key_length; + + if (!path || !*path) + return NULL; + + cl_assert((f = fopen(path, "r")) != NULL); + cl_assert(fseek(f, 0, SEEK_END) != -1); + cl_assert((key_length = ftell(f)) != -1); + cl_assert(fseek(f, 0, SEEK_SET) != -1); + cl_assert((buf = malloc(key_length)) != NULL); + cl_assert(fread(buf, key_length, 1, f) == 1); + fclose(f); + + return buf; +} + +static int ssh_memory_cred_cb(git_cred **cred, const char *url, const char *user_from_url, + unsigned int allowed_types, void *payload) +{ + const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); + const char *pubkey_path = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); + const char *privkey_path = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + const char *passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); + + GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); + + if (allowed_types & GIT_CREDTYPE_USERNAME) + return git_cred_username_new(cred, remote_user); + + if (allowed_types & GIT_CREDTYPE_SSH_KEY) + { + char *pubkey = read_key_file(pubkey_path); + char *privkey = read_key_file(privkey_path); + + int ret = git_cred_ssh_key_memory_new(cred, remote_user, pubkey, privkey, passphrase); + + if (privkey) + free(privkey); + if (pubkey) + free(pubkey); + return ret; + } + + giterr_set(GITERR_NET, "unexpected cred type"); + return -1; +} + +void test_online_clone__ssh_memory_auth(void) +{ + const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); + const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); + const char *privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + +#ifndef GIT_SSH_MEMORY_CREDENTIALS + clar__skip(); +#endif + if (!remote_url || !remote_user || !privkey || strncmp(remote_url, "ssh://", 5) != 0) + clar__skip(); + + g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb; + + cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options)); +} + 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),