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() diff --git a/include/git2/transport.h b/include/git2/transport.h index 99fd09a1b..2eeebd565 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -108,6 +108,13 @@ typedef enum { * it will ask via this credential type. */ GIT_CREDTYPE_USERNAME = (1u << 5), + + /** + * Credentials read from memory. + * + * Only available for libssh2+OpenSSL for now. + */ + GIT_CREDTYPE_SSH_MEMORY = (1u << 6), } git_credtype_t; /* The base structure for all credential types */ @@ -290,6 +297,23 @@ GIT_EXTERN(int) git_cred_default_new(git_cred **out); */ GIT_EXTERN(int) git_cred_username_new(git_cred **cred, const char *username); +/** + * 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); + /** * Signature of a function which acquires a credential object. * diff --git a/src/transports/cred.c b/src/transports/cred.c index 8163d3115..006cd2c52 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,7 @@ const char *git_cred__username(git_cred *cred) return c->username; } case GIT_CREDTYPE_SSH_KEY: + case GIT_CREDTYPE_SSH_MEMORY: { git_cred_ssh_key *c = (git_cred_ssh_key *) cred; return c->username; @@ -174,6 +183,45 @@ 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); +} + +int git_cred_ssh_key_memory_new( + git_cred **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ +#ifdef GIT_SSH_MEMORY_CREDENTIALS + return git_cred_ssh_key_type_new( + cred, + username, + publickey, + 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, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credtype_t credtype) { git_cred_ssh_key *c; @@ -182,7 +230,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..58f1aeb64 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -370,6 +370,25 @@ 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; + + assert(c->username); + assert(c->privatekey); + + rc = libssh2_userauth_publickey_frommemory( + session, + c->username, + strlen(c->username), + c->publickey, + c->publickey ? strlen(c->publickey) : 0, + c->privatekey, + strlen(c->privatekey), + c->passphrase); + break; + } +#endif default: rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; } @@ -740,6 +759,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; } 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),