diff --git a/src/main/java/de/cronn/proxy/ssh/SshConfiguration.java b/src/main/java/de/cronn/proxy/ssh/SshConfiguration.java index 7c53df1..e7f6b08 100644 --- a/src/main/java/de/cronn/proxy/ssh/SshConfiguration.java +++ b/src/main/java/de/cronn/proxy/ssh/SshConfiguration.java @@ -96,13 +96,24 @@ public SshProxyConfig getProxyConfiguration(String host) { } void addIdentity(String host) throws JSchException { + String identityFile = getIdentityFileForHost(host); + log.debug("using SSH key file {}", identityFile); + jsch.addIdentity(identityFile); + } + + void addIdentity(String host, String passphrase) throws JSchException { + String identityFile = getIdentityFileForHost(host); + log.debug("using SSH key file {}", identityFile); + jsch.addIdentity(identityFile, passphrase); + } + + private String getIdentityFileForHost(String host) { Config hostConfig = getHostConfig(host); String identityFile = hostConfig.getValue(SSH_CONFIG_KEY_IDENTITY_FILE); if (identityFile == null) { identityFile = getDefaultSshKeyPath().toString(); } - log.debug("using SSH key file {}", identityFile); - jsch.addIdentity(identityFile); + return identityFile; } public String getHostUser(String host) { diff --git a/src/main/java/de/cronn/proxy/ssh/SshProxy.java b/src/main/java/de/cronn/proxy/ssh/SshProxy.java index 7213707..f95344a 100644 --- a/src/main/java/de/cronn/proxy/ssh/SshProxy.java +++ b/src/main/java/de/cronn/proxy/ssh/SshProxy.java @@ -30,11 +30,22 @@ public class SshProxy implements Closeable { private final SshConfiguration sshConfiguration; private int timeoutMillis; + private final String passphrase; + public SshProxy() { - this(DEFAULT_TIMEOUT_MILLIS); + this(DEFAULT_TIMEOUT_MILLIS, null); + } + + public SshProxy(String passphrase) { + this(DEFAULT_TIMEOUT_MILLIS, passphrase); } public SshProxy(int timeoutMillis) { + this(timeoutMillis, null); + } + + public SshProxy(int timeoutMillis, String passphrase) { + this.passphrase = passphrase; try { sshConfiguration = SshConfiguration.getConfiguration(); } catch (Exception e) { @@ -56,7 +67,11 @@ public int connect(String sshTunnelHost, String host, int port, int localPort) { log.debug("tunneling to {}:{} via {}", host, port, sshTunnelHost); try { - sshConfiguration.addIdentity(sshTunnelHost); + if (passphrase != null && !passphrase.isEmpty()) { + sshConfiguration.addIdentity(sshTunnelHost, passphrase); + } else { + sshConfiguration.addIdentity(sshTunnelHost); + } SshProxyConfig proxyConfig = sshConfiguration.getProxyConfiguration(sshTunnelHost); if (proxyConfig == null) { diff --git a/src/test/java/de/cronn/proxy/ssh/SshProxyTest.java b/src/test/java/de/cronn/proxy/ssh/SshProxyTest.java index fa0a5f9..86a5b08 100644 --- a/src/test/java/de/cronn/proxy/ssh/SshProxyTest.java +++ b/src/test/java/de/cronn/proxy/ssh/SshProxyTest.java @@ -12,6 +12,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.Arrays; @@ -104,6 +105,52 @@ public void testSingleHop() throws Exception { } } + @Test(timeout = TEST_TIMEOUT_MILLIS) + public void testSingleHopWithPassphraseAuthentication() throws Exception { + setupNewSshKeys("id_rsa_passphrase", "id_rsa_passphrase.pub"); + SshServer sshServer = setUpSshServer(); + int sshServerPort = sshServer.getPort(); + + String hostConfigName = "localhost-" + sshServerPort; + appendToSshFile(CONFIG_FILENAME, "Host " + hostConfigName + "\n\tHostName localhost\n\tPort " + sshServerPort + "\n\n"); + + String testPassword = "password"; + + try (DummyServerSocketThread dummyServerSocketThread = new DummyServerSocketThread(TRANSFER_CHARSET, TEST_TEXT); + SshProxy sshProxy = new SshProxy(testPassword)) { + int port = sshProxy.connect(hostConfigName, "localhost", dummyServerSocketThread.getPort()); + + final String receivedText; + try (Socket s = new Socket(SshProxy.LOCALHOST, port); + InputStream is = s.getInputStream()) { + log.info("connected to port: {}", port); + receivedText = readLine(is); + } + assertEquals(TEST_TEXT, receivedText); + } finally { + tryStop(sshServer); + } + } + + @Test(timeout = TEST_TIMEOUT_MILLIS) + public void testSingleHopWithPassphraseAuthentication_IncorrectPassphrase() throws Exception { + setupNewSshKeys("id_rsa_passphrase", "id_rsa_passphrase.pub"); + SshServer sshServer = setUpSshServer(); + int sshServerPort = sshServer.getPort(); + + String hostConfigName = "localhost-" + sshServerPort; + appendToSshFile(CONFIG_FILENAME, "Host " + hostConfigName + "\n\tHostName localhost\n\tPort " + sshServerPort + "\n\n"); + + String testPassword = "incorrectPassword"; + + assertThrows(de.cronn.proxy.ssh.SshProxyRuntimeException.class, () -> { + try (DummyServerSocketThread dummyServerSocketThread = new DummyServerSocketThread(TRANSFER_CHARSET, TEST_TEXT); + SshProxy sshProxy = new SshProxy(testPassword)) { + sshProxy.connect(hostConfigName, "localhost", dummyServerSocketThread.getPort()); + } + }); + } + @Test(timeout = TEST_TIMEOUT_MILLIS) public void testSingleHop_EcDsaServer() throws Exception { SshServer sshServer = setUpSshServer(KeyUtils.EC_ALGORITHM); @@ -308,4 +355,11 @@ private void appendToSshFile(String filename, String text) throws IOException { Files.write(config, text.getBytes(CONFIG_CHARSET), StandardOpenOption.APPEND, StandardOpenOption.CREATE); } + private void setupNewSshKeys(String privateKeyFile, String publicKeyFile) throws IOException { + Path dotSsh = Paths.get(System.getProperty("user.home"), ".ssh"); + Path TEST_RESOURCES = Paths.get("src", "test", "resources"); + Files.copy(TEST_RESOURCES.resolve(privateKeyFile), dotSsh.resolve("id_rsa"), StandardCopyOption.REPLACE_EXISTING); + Files.copy(TEST_RESOURCES.resolve(publicKeyFile), dotSsh.resolve("id_rsa.pub"), StandardCopyOption.REPLACE_EXISTING); + } + } diff --git a/src/test/resources/id_rsa_passphrase b/src/test/resources/id_rsa_passphrase new file mode 100644 index 0000000..ac0f549 --- /dev/null +++ b/src/test/resources/id_rsa_passphrase @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,9601C994D8345E31A8A6A0771EFB4490 + +AnWhrDCv8j+dTw2jQW2BT/OKPRD6rNLLYhNzgMJGePftat3XqAx0NgDuVtQcJ4E0 +e3ochpF3Pw6O7lWjZ3n06x/3ibE+L3XQJhz+tx4dTYDmB0IXC/oKpW6xVAlM1Yt3 ++o9WZtpERh1+w4vWquDvumRmyjgDemuodg26FsxD/3zlxMQinBto6xivbrY+vRae +lbzZKtsbjeWWyJLsIbzLNiPtTJuaF1JzWyszFyXdhfQMx4G6pLjlZpoqFUsfVqq0 +iDV0R+CnwDqdN9EpjNDR8rNj7ErBTI+CCTH/9ymMaVtMDP9PJGYWdcx+lMomBVjf +HdBam9RB78yFzaUlUjjxz3d3WUhBk/LH+JlIMarzC9yfnvanxG3PgBeK4wld0Oh/ +FUOcIMffkEdipyH9KrfaUsj2xrtFp++F0F52D5tynG0i94NagAueFOQ2HF6kg3Xu +ueBXdqdHXPqGrl2pl01YwlsoM1QLSUlL+H1W/mWCMooxweOaxNbehtxU98pwcLnD +LCK33SImIY6FzL8Hst8qQWzBwL3RE2TA8McHaK5J5NzGRCVAeoF00yvy2A+Yu3FV +lf+R7bRsnkmBHwXPWkwUw9axpdb8ssRnFdJ0u19vtbxMziB/j0hM5k56WEOHnV/O +THxW457u8oiUWaXz4tUrFFE8BmmKptBUdlqe8xSBphwXIYA3GDSPFelbLuCDvDfl +lc+b9lGckjVkNTv+/PPQP6C/umF/vpH00Tbxd4U899l8k13cTw9tknYhHgpqEskx +MyjqD6K2lB9RiQbbVjhnebXXkbmiXR1izgXSibgfYEFu31BkLeyZhemR718NJ9NN +fZJwUeIsCNA4yyl4CKw+ZpDSLcVIEgcuVVFw2B6gpYDjU/yZokGDGj2YVfMOAXjV +RbevJpUQN9wU0Wnzsz05r4SRQ+vVkJYB0r3KqLqRr6Q/xWf1BQGAr5IZy+6xgWJl +vSoDSaLmHkUskajLVRJH39MNSEComRgwir+z48bHLrVBx6WhgUtQxNGLQ8jBcrer +Hd7ALtZT1nbLZOPjlGeYsiLPGKEm26+sYdm0mUwycNb2qq7MfmtqiernIiqz8EDo +aafjLsq3V95ABaE0T6wttq/xQBGYt1OLI5hcjP3NZgmEZLBI/GCrozTA1LtH0PUp +das9Ajg5LLpmMhNQqkepeJuoUiDITXyzPF/zucmksEUWUz6hyJyjf/NlSpwbtR7x +CsLwyi9VjO88PGawHjYgOSEWGNfGDXFOvzzaLw5aV49j5cUxmNCFl1s1Vs57b70o +KwKvFuGXr/gv0Br1CnDNjVuPfVyOALc6tjJMPqcbF5FiCR9qtMZH5+IL6Q4gzg/8 +06UB4U3urQo2gWBxF7YTEanZyYPzIcAPitV2tinR2JpRVHRonhDMzK4UqqNUB6R7 +epJRG+8dMBKhtNajigl01YFrU59+Izyfcme+1Rkbo6ragng4lXmFJZKPUEJs105A +AGlL5Hgl0VwtCIi4/HXFAsDSYc/st5SfH4AWFDAR3tyYWXYGOau7oP8+CNRJ9MAL +0o03coxyqCE3+IwHK833rX9qngFrrT/PyD2S8Ho0ZPUhSr0Tw9fhwyJ5FRPJ0t/4 +-----END RSA PRIVATE KEY----- diff --git a/src/test/resources/id_rsa_passphrase.pub b/src/test/resources/id_rsa_passphrase.pub new file mode 100644 index 0000000..df7270b --- /dev/null +++ b/src/test/resources/id_rsa_passphrase.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCp8oAUBvW5VMHeM9F3B5xrHDOreTr2LSKBW5f178Lvt/OWcCWD01teq6L5EuND36inJIDHMOQh8+CErSi1eNkjq0zvE4cWzssRU3zUy5qnDgsEuO7Mb8l0xrew18B0rsa0gFNw2BnlQTT8CqnT+LGJiDB0U0jNdAL3f97fVQ7VOx9vdmKMz/J0PCVl6zAwnFNbmgevLRmVo6aPTnu6+uDUS2gfS/p+BRZl/2TKhbQLVZz4H/BYl6/nEflYYe9cdrnZ263/mAJ3CQplbnOGuCv/jfWJrYnO/b7yLTBwvMVUsAJM94IDKzDSnon2nn7qZhPCznKdU+CHVuJWohhhj51H krzys@krzys-21ah0082pb