{"thread":{"id":"18921","subject":"[JGIT PATCH] Correctly honor IdentityFile from ~/.ssh/config","startedAt":"2009-04-17T16:36:42Z","lastAt":"2009-04-17T16:36:42Z","messageCount":1,"participants":["Shawn O. Pearce"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"111505","messageId":"1239986202-28629-1-git-send-email-spearce@spearce.org","threadId":"18921","inReplyTo":null,"subject":"[JGIT PATCH] Correctly honor IdentityFile from ~/.ssh/config","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-04-17T16:36:42Z","receivedAt":"2009-04-17T16:36:42Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"JSch offers all identities available within the JSch object used to\ncreate a Session.  In order to offer only a specific IdentityFile\nto the remote server we must create a unique JSch with only that\none identity loaded.  However, we also want to cache the key once\nit has been decrypted, so that repeated uses of a single file from\nthe same JVM do not cause repeated passphrase prompts.\n\nThe SshConfigSessionFactory now loads only the default SSH keys\n(~/.ssh/identity for SSH1, ~/.ssh/{id_rsa,id_dsa} for SSH2) into\nthe default JSch.  If a Host block in ~/.ssh/config selects some\nother IdentityFile then we create a unique JSch instance for the\nkey, and copy over the same known hosts table.\n\nFix issue 35 (Correctly honor IdentityFile in ~/.ssh/config)\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../spearce/egit/ui/EclipseSshSessionFactory.java  |   36 +++++--\n .../jgit/transport/SshConfigSessionFactory.java    |  100 ++++++++++++--------\n 2 files changed, 89 insertions(+), 47 deletions(-)\n\ndiff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/EclipseSshSessionFactory.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/EclipseSshSessionFactory.java\nindex 2cafdfe..04600ea 100644\n--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/EclipseSshSessionFactory.java\n+++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/EclipseSshSessionFactory.java\n@@ -1,6 +1,7 @@\n /*******************************************************************************\n  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>\n+ * Copyright (C) 2009, Google, Inc.\n  *\n  * All rights reserved. This program and the accompanying materials\n  * are made available under the terms of the Eclipse Public License v1.0\n@@ -10,8 +11,8 @@\n \n import org.eclipse.jsch.core.IJSchService;\n import org.eclipse.jsch.ui.UserInfoPrompter;\n+import org.spearce.jgit.transport.OpenSshConfig;\n import org.spearce.jgit.transport.SshConfigSessionFactory;\n-import org.spearce.jgit.transport.OpenSshConfig.Host;\n \n import com.jcraft.jsch.JSch;\n import com.jcraft.jsch.JSchException;\n@@ -25,19 +26,36 @@ EclipseSshSessionFactory(final IJSchService p) {\n \t}\n \n \t@Override\n-\tprotected Session createSession(final String user, final String host,\n-\t\t\tfinal int port) throws JSchException {\n-\t\treturn provider.createSession(host, port, user);\n+\tprotected JSch createDefaultJSch() throws JSchException {\n+\t\t// Forcing a dummy session to be created will cause the known hosts\n+\t\t// and configured private keys to be initialized. This is needed by\n+\t\t// our parent class in case non-default JSch instances need to be made.\n+\t\t//\n+\t\tprovider.createSession(\"127.0.0.1\", 0, \"eclipse\");\n+\t\treturn provider.getJSch();\n \t}\n \n \t@Override\n-\tprotected void configure(final Host hc, final Session session) {\n-\t\tif (!hc.isBatchMode())\n-\t\t\tnew UserInfoPrompter(session);\n+\tprotected Session createSession(final OpenSshConfig.Host hc,\n+\t\t\tfinal String user, final String host, final int port)\n+\t\t\tthrows JSchException {\n+\t\tfinal JSch jsch = getJSch(hc);\n+\t\tif (jsch == provider.getJSch()) {\n+\t\t\t// If its the default JSch desired, let the provider\n+\t\t\t// manage the session creation for us.\n+\t\t\t//\n+\t\t\treturn provider.createSession(host, port, user);\n+\t\t} else {\n+\t\t\t// This host configuration is using a different IdentityFile,\n+\t\t\t// one that is not available through the default JSch.\n+\t\t\t//\n+\t\t\treturn jsch.getSession(user, host, port);\n+\t\t}\n \t}\n \n \t@Override\n-\tprotected JSch getUserJSch() throws JSchException {\n-\t\treturn provider.getJSch();\n+\tprotected void configure(final OpenSshConfig.Host hc, final Session session) {\n+\t\tif (!hc.isBatchMode())\n+\t\t\tnew UserInfoPrompter(session);\n \t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/SshConfigSessionFactory.java b/org.spearce.jgit/src/org/spearce/jgit/transport/SshConfigSessionFactory.java\nindex f929e6b..4d29829 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/SshConfigSessionFactory.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/SshConfigSessionFactory.java\n@@ -2,6 +2,7 @@\n  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>\n  * Copyright (C) 2009, JetBrains s.r.o.\n+ * Copyright (C) 2009, Google, Inc.\n  *\n  * All rights reserved.\n  *\n@@ -43,8 +44,8 @@\n import java.io.FileNotFoundException;\n import java.io.IOException;\n import java.io.OutputStream;\n-import java.util.HashSet;\n-import java.util.Set;\n+import java.util.HashMap;\n+import java.util.Map;\n \n import org.spearce.jgit.util.FS;\n \n@@ -66,9 +67,9 @@\n  * to supply appropriate {@link UserInfo} to the session.\n  */\n public abstract class SshConfigSessionFactory extends SshSessionFactory {\n-\tprivate final Set<String> loadedIdentities = new HashSet<String>();\n+\tprivate final Map<String, JSch> byIdentityFile = new HashMap<String, JSch>();\n \n-\tprivate JSch userJSch;\n+\tprivate JSch defaultJSch;\n \n \tprivate OpenSshConfig config;\n \n@@ -82,9 +83,7 @@ public synchronized Session getSession(String user, String pass,\n \t\tif (user == null)\n \t\t\tuser = hc.getUser();\n \n-\t\tfinal Session session = createSession(user, host, port);\n-\t\tif (hc.getIdentityFile() != null)\n-\t\t\taddIdentity(hc.getIdentityFile());\n+\t\tfinal Session session = createSession(hc, user, host, port);\n \t\tif (pass != null)\n \t\t\tsession.setPassword(pass);\n \t\tfinal String strictHostKeyCheckingPolicy = hc\n@@ -102,6 +101,8 @@ public synchronized Session getSession(String user, String pass,\n \t/**\n \t * Create a new JSch session for the requested address.\n \t *\n+\t * @param hc\n+\t *            host configuration\n \t * @param user\n \t *            login to authenticate as.\n \t * @param host\n@@ -112,9 +113,10 @@ public synchronized Session getSession(String user, String pass,\n \t * @throws JSchException\n \t *             the session could not be created.\n \t */\n-\tprotected Session createSession(String user, String host, int port)\n+\tprotected Session createSession(final OpenSshConfig.Host hc,\n+\t\t\tfinal String user, final String host, final int port)\n \t\t\tthrows JSchException {\n-\t\treturn getUserJSch().getSession(user, host, port);\n+\t\treturn getJSch(hc).getSession(user, host, port);\n \t}\n \n \t/**\n@@ -131,17 +133,50 @@ protected Session createSession(String user, String host, int port)\n \t/**\n \t * Obtain the JSch used to create new sessions.\n \t *\n+\t * @param hc\n+\t *            host configuration\n \t * @return the JSch instance to use.\n \t * @throws JSchException\n \t *             the user configuration could not be created.\n \t */\n-\tprotected JSch getUserJSch() throws JSchException {\n-\t\tif (userJSch == null) {\n-\t\t\tuserJSch = new JSch();\n-\t\t\tknownHosts(userJSch);\n-\t\t\tidentities();\n+\tprotected JSch getJSch(final OpenSshConfig.Host hc) throws JSchException {\n+\t\tfinal JSch def = getDefaultJSch();\n+\t\tfinal File identityFile = hc.getIdentityFile();\n+\t\tif (identityFile == null) {\n+\t\t\treturn def;\n+\t\t}\n+\n+\t\tfinal String identityKey = identityFile.getAbsolutePath();\n+\t\tJSch jsch = byIdentityFile.get(identityKey);\n+\t\tif (jsch == null) {\n+\t\t\tjsch = new JSch();\n+\t\t\tjsch.setHostKeyRepository(def.getHostKeyRepository());\n+\t\t\tjsch.addIdentity(identityKey);\n+\t\t\tbyIdentityFile.put(identityKey, jsch);\n \t\t}\n-\t\treturn userJSch;\n+\t\treturn jsch;\n+\t}\n+\n+\tprivate JSch getDefaultJSch() throws JSchException {\n+\t\tif (defaultJSch == null) {\n+\t\t\tdefaultJSch = createDefaultJSch();\n+\t\t\tfor (Object name : defaultJSch.getIdentityNames()) {\n+\t\t\t\tbyIdentityFile.put((String) name, defaultJSch);\n+\t\t\t}\n+\t\t}\n+\t\treturn defaultJSch;\n+\t}\n+\n+\t/**\n+\t * @return the new default JSch implementation.\n+\t * @throws JSchException\n+\t *             known host keys cannot be loaded.\n+\t */\n+\tprotected JSch createDefaultJSch() throws JSchException {\n+\t\tfinal JSch jsch = new JSch();\n+\t\tknownHosts(jsch);\n+\t\tidentities(jsch);\n+\t\treturn jsch;\n \t}\n \n \tprivate OpenSshConfig getConfig() {\n@@ -150,7 +185,7 @@ private OpenSshConfig getConfig() {\n \t\treturn config;\n \t}\n \n-\tprivate void knownHosts(final JSch sch) throws JSchException {\n+\tprivate static void knownHosts(final JSch sch) throws JSchException {\n \t\tfinal File home = FS.userHome();\n \t\tif (home == null)\n \t\t\treturn;\n@@ -169,39 +204,28 @@ private void knownHosts(final JSch sch) throws JSchException {\n \t\t}\n \t}\n \n-\tprivate void identities() {\n+\tprivate static void identities(final JSch sch) {\n \t\tfinal File home = FS.userHome();\n \t\tif (home == null)\n \t\t\treturn;\n \t\tfinal File sshdir = new File(home, \".ssh\");\n-\t\tfinal File[] keys = sshdir.listFiles();\n-\t\tif (keys == null)\n-\t\t\treturn;\n-\t\tfor (int i = 0; i < keys.length; i++) {\n-\t\t\tfinal File pk = keys[i];\n-\t\t\tfinal String n = pk.getName();\n-\t\t\tif (!n.endsWith(\".pub\"))\n-\t\t\t\tcontinue;\n-\t\t\tfinal File k = new File(sshdir, n.substring(0, n.length() - 4));\n-\t\t\tif (!k.isFile())\n-\t\t\t\tcontinue;\n+\t\tif (sshdir.isDirectory()) {\n+\t\t\tloadIdentity(sch, new File(sshdir, \"identity\"));\n+\t\t\tloadIdentity(sch, new File(sshdir, \"id_rsa\"));\n+\t\t\tloadIdentity(sch, new File(sshdir, \"id_dsa\"));\n+\t\t}\n+\t}\n \n+\tprivate static void loadIdentity(final JSch sch, final File priv) {\n+\t\tif (priv.isFile()) {\n \t\t\ttry {\n-\t\t\t\taddIdentity(k);\n+\t\t\t\tsch.addIdentity(priv.getAbsolutePath());\n \t\t\t} catch (JSchException e) {\n-\t\t\t\tcontinue;\n+\t\t\t\t// Instead, pretend the key doesn't exist.\n \t\t\t}\n \t\t}\n \t}\n \n-\tprivate void addIdentity(final File identityFile) throws JSchException {\n-\t\tfinal String path = identityFile.getAbsolutePath();\n-\t\tif (!loadedIdentities.contains(path)) {\n-\t\t\tgetUserJSch().addIdentity(path);\n-\t\t\tloadedIdentities.add(path);\n-\t\t}\n-\t}\n-\n \t@Override\n \tpublic OutputStream getErrorStream() {\n \t\treturn new OutputStream() {\n-- \n1.6.3.rc0.200.g4086\n"}]}