Commit 2c8550f040185fd416eb116c1a043caa8e623ed3

Carlos Martín Nieto 2015-05-29T19:38:11

Merge pull request #3157 from mgorny/ssh_memory_auth Support getting SSH keys from memory, pt. 2

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 181d301..c3c7a15 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 99fd09a..2eeebd5 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 */
@@ -291,6 +298,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.
  *
  * - cred: The newly created credential object.
diff --git a/src/transports/cred.c b/src/transports/cred.c
index 8163d31..006cd2c 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;
@@ -175,6 +184,45 @@ int git_cred_ssh_key_new(
 	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;
 
 	assert(username && cred && privatekey);
@@ -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 55f715b..58f1aeb 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 1930a8b..fa0dbd6 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),