From c5792fe4a846fcd15fe2bae14d14f527f21ab0a0 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Wed, 25 Jul 2018 10:34:52 +0200 Subject: [PATCH 01/19] Added Kex integration test --- README.adoc | 3 +- .../docker-image/test-container/sshd_config | 2 +- .../sshj/transport/kex/KexSpec.groovy | 46 +++++++++++++++++++ .../sshj/transport/kex/Curve25519SHA256.java | 17 ++++++- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy diff --git a/README.adoc b/README.adoc index 3ecf1e2be..43cc6167b 100644 --- a/README.adoc +++ b/README.adoc @@ -73,6 +73,7 @@ key exchange:: `diffie-hellman-group14-sha256`, `diffie-hellman-group15-sha512`, `diffie-hellman-group16-sha512`, `diffie-hellman-group17-sha512`, `diffie-hellman-group18-sha512` `diffie-hellman-group-exchange-sha1`, `diffie-hellman-group-exchange-sha256`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `curve25519-sha256@libssh.org` + SSHJ also supports the following extended (non official) key exchange algoriths: `diffie-hellman-group14-sha256@ssh.com`, `diffie-hellman-group15-sha256`, `diffie-hellman-group15-sha256@ssh.com`, `diffie-hellman-group15-sha384@ssh.com`, `diffie-hellman-group16-sha256`, `diffie-hellman-group16-sha384@ssh.com`, `diffie-hellman-group16-sha512@ssh.com`, `diffie-hellman-group18-sha512@ssh.com` @@ -81,7 +82,7 @@ signatures:: `ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519` mac:: - `hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`, `hmac-ripemd160` + `hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`, `hmac-ripemd160`, `hmac-ripemd160@openssh.com` compression:: `zlib` and `zlib@openssh.com` (delayed zlib) diff --git a/src/itest/docker-image/test-container/sshd_config b/src/itest/docker-image/test-container/sshd_config index e42e39331..4f1931aa5 100644 --- a/src/itest/docker-image/test-container/sshd_config +++ b/src/itest/docker-image/test-container/sshd_config @@ -128,5 +128,5 @@ Subsystem sftp /usr/lib/ssh/sftp-server # PermitTTY no # ForceCommand cvs server - +KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1 macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com diff --git a/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy new file mode 100644 index 000000000..cd1b2d2c2 --- /dev/null +++ b/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy @@ -0,0 +1,46 @@ +package com.hierynomus.sshj.transport.kex + +import com.hierynomus.sshj.IntegrationBaseSpec +import com.hierynomus.sshj.transport.mac.Macs +import net.schmizz.sshj.DefaultConfig +import net.schmizz.sshj.transport.kex.Curve25519DH +import net.schmizz.sshj.transport.kex.Curve25519SHA256 +import net.schmizz.sshj.transport.kex.DH +import net.schmizz.sshj.transport.kex.DHGexSHA1 +import net.schmizz.sshj.transport.kex.DHGexSHA256 +import net.schmizz.sshj.transport.kex.ECDH +import net.schmizz.sshj.transport.kex.ECDHNistP +import spock.lang.Unroll + +class KexSpec extends IntegrationBaseSpec { + + @Unroll + def "should correctly connect with #kex Key Exchange"() { + given: + def cfg = new DefaultConfig() + cfg.setKeyExchangeFactories(kexFactory) + def client = getConnectedClient(cfg) + + when: + client.authPublickey(USERNAME, KEYFILE) + + then: + client.authenticated + + where: + kexFactory << [DHGroups.Group1SHA1(), + DHGroups.Group14SHA1(), + DHGroups.Group14SHA256(), + DHGroups.Group16SHA512(), + DHGroups.Group18SHA512(), + new DHGexSHA1.Factory(), + new DHGexSHA256.Factory(), + new Curve25519SHA256.Factory(), + new Curve25519SHA256.FactoryLibSsh(), + new ECDHNistP.Factory256(), + new ECDHNistP.Factory384(), + new ECDHNistP.Factory521()] + kex = kexFactory.name + } + +} diff --git a/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java b/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java index 61bb42f74..69fa4b24f 100644 --- a/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java +++ b/src/main/java/net/schmizz/sshj/transport/kex/Curve25519SHA256.java @@ -21,7 +21,7 @@ public class Curve25519SHA256 extends AbstractDHG { /** Named factory for Curve25519SHA256 key exchange */ - public static class Factory + public static class FactoryLibSsh implements net.schmizz.sshj.common.Factory.Named { @Override @@ -35,6 +35,21 @@ public String getName() { } } + /** Named factory for Curve25519SHA256 key exchange */ + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public KeyExchange create() { + return new Curve25519SHA256(); + } + + @Override + public String getName() { + return "curve25519-sha256"; + } + } + public Curve25519SHA256() { super(new Curve25519DH(), new SHA256()); } From 7556a7f6f60e67815fe9dfb86dab34d0ec75cc14 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Wed, 25 Jul 2018 12:59:25 +0200 Subject: [PATCH 02/19] Updated license header --- .../hierynomus/sshj/transport/kex/KexSpec.groovy | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy index cd1b2d2c2..c0bc72adb 100644 --- a/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy +++ b/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy @@ -1,3 +1,18 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.hierynomus.sshj.transport.kex import com.hierynomus.sshj.IntegrationBaseSpec From deff0971703a5f240ccfc93db94cf5d4b0ad3e17 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Thu, 2 Aug 2018 13:11:09 +0200 Subject: [PATCH 03/19] Fix SFTPClient.mkdirs to not inadvertently prefix with '/' (#415) --- .../net/schmizz/sshj/sftp/PathComponents.java | 13 +++++-- .../net/schmizz/sshj/sftp/PathHelper.java | 20 +++++++---- .../sshj/sftp/SFTPClientSpec.groovy | 35 +++++++++++++++++++ .../channel/direct/CommandTest.java | 7 +++- .../net/schmizz/sshj/sftp/PathHelperTest.java | 13 +++++-- .../net/schmizz/sshj/sftp/SFTPClientTest.java | 10 ++++-- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/sftp/PathComponents.java b/src/main/java/net/schmizz/sshj/sftp/PathComponents.java index 37ad0bedf..6774c602e 100644 --- a/src/main/java/net/schmizz/sshj/sftp/PathComponents.java +++ b/src/main/java/net/schmizz/sshj/sftp/PathComponents.java @@ -18,8 +18,14 @@ public class PathComponents { static String adjustForParent(String parent, String path, String pathSep) { - return (path.startsWith(pathSep)) ? path // Absolute path, nothing to adjust - : (parent + (parent.endsWith(pathSep) ? "" : pathSep) + path); // Relative path + if (path.startsWith(pathSep)) { + return path; // Absolute path, nothing to adjust + } else if (parent.endsWith(pathSep)) { + return parent + path; // Relative path, parent endsWith '/' + } else if (parent.isEmpty()) { + return path; + } + return parent + pathSep + path; // Relative path } static String trimTrailingSeparator(String somePath, String pathSep) { @@ -33,7 +39,8 @@ static String trimTrailingSeparator(String somePath, String pathSep) { public PathComponents(String parent, String name, String pathSep) { this.parent = parent; this.name = name; - this.path = trimTrailingSeparator(adjustForParent(parent, name, pathSep), pathSep); + String adjusted = adjustForParent(parent, name, pathSep); + this.path = !pathSep.equals(adjusted) ? trimTrailingSeparator(adjusted, pathSep) : adjusted; } public String getParent() { diff --git a/src/main/java/net/schmizz/sshj/sftp/PathHelper.java b/src/main/java/net/schmizz/sshj/sftp/PathHelper.java index b40fcd11b..e8a01fda1 100644 --- a/src/main/java/net/schmizz/sshj/sftp/PathHelper.java +++ b/src/main/java/net/schmizz/sshj/sftp/PathHelper.java @@ -70,16 +70,25 @@ public PathComponents getComponents(String parent, String name) { */ public PathComponents getComponents(final String path) throws IOException { - if (path.equals(pathSep)) - return getComponents("", ""); + if (path.equals(pathSep)) { + return getComponents("", "/"); + } - if (path.isEmpty() || ".".equals(path) || ("." + pathSep).equals(path)) + if (path.isEmpty() || ".".equals(path) || ("." + pathSep).equals(path)) { return getComponents(getDotDir()); + } final String withoutTrailSep = trimTrailingSeparator(path); final int lastSep = withoutTrailSep.lastIndexOf(pathSep); - final String parent = (lastSep == -1) ? "" : withoutTrailSep.substring(0, lastSep); - final String name = (lastSep == -1) ? withoutTrailSep : withoutTrailSep.substring(lastSep + pathSep.length()); + String parent; + String name; + if (lastSep == -1) { + parent = ""; + name = withoutTrailSep; + } else { + parent = lastSep == 0 ? "/" : withoutTrailSep.substring(0, lastSep); + name = withoutTrailSep.substring(lastSep + pathSep.length()); + } if (".".equals(name) || "..".equals(name)) { return getComponents(canonicalizer.canonicalize(path)); @@ -87,5 +96,4 @@ public PathComponents getComponents(final String path) return getComponents(parent, name); } } - } \ No newline at end of file diff --git a/src/test/groovy/com/hierynomus/sshj/sftp/SFTPClientSpec.groovy b/src/test/groovy/com/hierynomus/sshj/sftp/SFTPClientSpec.groovy index 09764c1b4..1fd48615b 100644 --- a/src/test/groovy/com/hierynomus/sshj/sftp/SFTPClientSpec.groovy +++ b/src/test/groovy/com/hierynomus/sshj/sftp/SFTPClientSpec.groovy @@ -18,6 +18,7 @@ package com.hierynomus.sshj.sftp import com.hierynomus.sshj.test.SshFixture import com.hierynomus.sshj.test.util.FileUtil import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.sftp.FileMode import net.schmizz.sshj.sftp.SFTPClient import org.junit.Rule import org.junit.rules.TemporaryFolder @@ -167,6 +168,40 @@ class SFTPClientSpec extends Specification { !new File(dest, "totototo.txt").exists() } + def "should mkdirs with existing parent path"() { + given: + SSHClient sshClient = fixture.setupConnectedDefaultClient() + sshClient.authPassword("test", "test") + SFTPClient ftp = sshClient.newSFTPClient() + ftp.mkdir("dir1") + + when: + ftp.mkdirs("dir1/dir2/dir3/dir4") + + then: + ftp.statExistence("dir1/dir2/dir3/dir4") != null + + cleanup: + ["dir1/dir2/dir3/dir4", "dir1/dir2/dir3", "dir1/dir2", "dir1"].each { + ftp.rmdir(it) + } + ftp.close() + sshClient.disconnect() + } + + def "should stat root"() { + given: + SSHClient sshClient = fixture.setupConnectedDefaultClient() + sshClient.authPassword("test", "test") + SFTPClient ftp = sshClient.newSFTPClient() + + when: + def attrs = ftp.statExistence("/") + + then: + attrs.type == FileMode.Type.DIRECTORY + } + private void doUpload(File src, File dest) throws IOException { SSHClient sshClient = fixture.setupConnectedDefaultClient() sshClient.authPassword("test", "test") diff --git a/src/test/java/com/hierynomus/sshj/connection/channel/direct/CommandTest.java b/src/test/java/com/hierynomus/sshj/connection/channel/direct/CommandTest.java index bdd119475..3c62c9a1a 100644 --- a/src/test/java/com/hierynomus/sshj/connection/channel/direct/CommandTest.java +++ b/src/test/java/com/hierynomus/sshj/connection/channel/direct/CommandTest.java @@ -18,6 +18,7 @@ import com.hierynomus.sshj.test.SshFixture; import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.connection.channel.direct.Session; +import net.schmizz.sshj.sftp.SFTPClient; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -46,6 +47,10 @@ public void shouldExecuteBackgroundCommand() throws IOException { exec.join(); assertThat("File should exist", file.exists()); assertThat("File should be directory", file.isDirectory()); - + SFTPClient sftpClient = sshClient.newSFTPClient(); + if (sftpClient.statExistence("&") != null) { + sftpClient.rmdir("&"); + // TODO fail here when this is fixed + } } } diff --git a/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java b/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java index 5c4f129fa..957ad099e 100644 --- a/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java +++ b/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java @@ -50,7 +50,7 @@ public void empty() public void root() throws IOException { final PathComponents comp = helper.getComponents("/"); - assertEquals("", comp.getName()); + assertEquals("/", comp.getName()); assertEquals("", comp.getParent()); } @@ -67,7 +67,7 @@ public void dotDot() throws IOException { final PathComponents comp = helper.getComponents(".."); assertEquals("home", comp.getName()); - assertEquals("", comp.getParent()); + assertEquals("/", comp.getParent()); } @Test @@ -75,9 +75,18 @@ public void fileInHomeDir() throws IOException { final PathComponents comp = helper.getComponents("somefile"); assertEquals("somefile", comp.getName()); + assertEquals("somefile", comp.getPath()); assertEquals("", comp.getParent()); } + @Test + public void pathInHomeDir() throws IOException { + final PathComponents comp = helper.getComponents("dir1/dir2"); + assertEquals("dir2", comp.getName()); + assertEquals("dir1/dir2", comp.getPath()); + assertEquals("dir1", comp.getParent()); + } + @Test public void fileSomeLevelsDeep() throws IOException { diff --git a/src/test/java/net/schmizz/sshj/sftp/SFTPClientTest.java b/src/test/java/net/schmizz/sshj/sftp/SFTPClientTest.java index bf2c25df4..61888ddf1 100644 --- a/src/test/java/net/schmizz/sshj/sftp/SFTPClientTest.java +++ b/src/test/java/net/schmizz/sshj/sftp/SFTPClientTest.java @@ -30,15 +30,21 @@ public class SFTPClientTest { @Before public void setPathHelper() throws Exception { PathHelper helper = new PathHelper(new PathHelper.Canonicalizer() { + /** + * Very basic, it does not try to canonicalize relative bits in the middle of a path. + */ @Override public String canonicalize(String path) throws IOException { - if (path.equals(".")) - return "/workingdirectory"; + if ("".equals(path) || ".".equals(path) || "./".equals(path)) + return "/home/me"; + if ("..".equals(path) || "../".equals(path)) + return "/home"; return path; } }, DEFAULT_PATH_SEPARATOR); when(sftpEngine.getPathHelper()).thenReturn(helper); + when(sftpEngine.stat("/")).thenReturn(new FileAttributes.Builder().withType(FileMode.Type.DIRECTORY).build()); when(sftpEngine.getLoggerFactory()).thenReturn(LoggerFactory.DEFAULT); } From 4de9f8ab9fd1e8c7a3c9b4fb0d5f82d58eb4c0a0 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Tue, 28 Aug 2018 13:22:31 +0200 Subject: [PATCH 04/19] Add support for Encrypt-then-MAC MAC Algorithms (#450) --- README.adoc | 1 + src/itest/docker-image/Dockerfile | 4 +- .../sshj/transport/mac/MacSpec.groovy | 25 ++++ .../hierynomus/sshj/transport/mac/Macs.java | 49 +++++-- .../java/net/schmizz/sshj/DefaultConfig.java | 23 ++- .../net/schmizz/sshj/transport/Converter.java | 2 + .../net/schmizz/sshj/transport/Decoder.java | 137 +++++++++++++----- .../net/schmizz/sshj/transport/Encoder.java | 44 ++++-- .../schmizz/sshj/transport/mac/BaseMAC.java | 10 ++ .../net/schmizz/sshj/transport/mac/MAC.java | 40 ++++- .../sshj/transport/mac/BaseMacTest.java | 7 +- 11 files changed, 269 insertions(+), 73 deletions(-) diff --git a/README.adoc b/README.adoc index 43cc6167b..9691e6446 100644 --- a/README.adoc +++ b/README.adoc @@ -83,6 +83,7 @@ signatures:: mac:: `hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`, `hmac-ripemd160`, `hmac-ripemd160@openssh.com` + `hmac-md5-etm@openssh.com`, `hmac-md5-96-etm@openssh.com`, `hmac-sha1-etm@openssh.com`, `hmac-sha1-96-etm@openssh.com`, `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-512-etm@openssh.com`, `hmac-ripemd160-etm@openssh.com` compression:: `zlib` and `zlib@openssh.com` (delayed zlib) diff --git a/src/itest/docker-image/Dockerfile b/src/itest/docker-image/Dockerfile index f16a5ccb2..67977b024 100644 --- a/src/itest/docker-image/Dockerfile +++ b/src/itest/docker-image/Dockerfile @@ -1,4 +1,4 @@ -FROM sickp/alpine-sshd:7.5 +FROM sickp/alpine-sshd:7.5-r2 ADD id_rsa.pub /home/sshj/.ssh/authorized_keys @@ -6,6 +6,7 @@ ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub ADD test-container/sshd_config /etc/ssh/sshd_config +RUN apk add --no-cache tini RUN \ echo "root:smile" | chpasswd && \ adduser -D -s /bin/ash sshj && \ @@ -15,3 +16,4 @@ RUN \ chmod 644 /etc/ssh/ssh_host_ecdsa_key.pub && \ chown -R sshj:sshj /home/sshj +ENTRYPOINT ["/sbin/tini", "/entrypoint.sh"] \ No newline at end of file diff --git a/src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy index af021e4b5..a5842a449 100644 --- a/src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy +++ b/src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy @@ -19,6 +19,7 @@ import com.hierynomus.sshj.IntegrationBaseSpec import net.schmizz.sshj.DefaultConfig import net.schmizz.sshj.transport.mac.HMACRIPEMD160 import net.schmizz.sshj.transport.mac.HMACSHA2256 +import spock.lang.AutoCleanup import spock.lang.Unroll class MacSpec extends IntegrationBaseSpec { @@ -36,8 +37,32 @@ class MacSpec extends IntegrationBaseSpec { then: client.authenticated + cleanup: + client.disconnect() + where: macFactory << [Macs.HMACRIPEMD160(), Macs.HMACRIPEMD160OpenSsh(), Macs.HMACSHA2256(), Macs.HMACSHA2512()] mac = macFactory.name } + + @Unroll + def "should correctly connect with Encrypt-Then-Mac #mac MAC"() { + given: + def cfg = new DefaultConfig() + cfg.setMACFactories(macFactory) + def client = getConnectedClient(cfg) + + when: + client.authPublickey(USERNAME, KEYFILE) + + then: + client.authenticated + + cleanup: + client.disconnect() + + where: + macFactory << [Macs.HMACRIPEMD160Etm(), Macs.HMACSHA2256Etm(), Macs.HMACSHA2512Etm()] + mac = macFactory.name + } } diff --git a/src/main/java/com/hierynomus/sshj/transport/mac/Macs.java b/src/main/java/com/hierynomus/sshj/transport/mac/Macs.java index e79634eee..5c40e52b4 100644 --- a/src/main/java/com/hierynomus/sshj/transport/mac/Macs.java +++ b/src/main/java/com/hierynomus/sshj/transport/mac/Macs.java @@ -16,48 +16,73 @@ package com.hierynomus.sshj.transport.mac; import net.schmizz.sshj.transport.mac.BaseMAC; +import net.schmizz.sshj.transport.mac.MAC; +@SuppressWarnings("PMD.MethodNamingConventions") public class Macs { public static Factory HMACMD5() { - return new Factory("hmac-md5", "HmacMD5", 16, 16); + return new Factory("hmac-md5", "HmacMD5", 16, 16, false); } public static Factory HMACMD596() { - return new Factory("hmac-md5-96", "HmacMD5", 12, 16); + return new Factory("hmac-md5-96", "HmacMD5", 12, 16, false); + } + public static Factory HMACMD5Etm() { + return new Factory("hmac-md5-etm@openssh.com", "HmacMD5", 16, 16, true); + } + public static Factory HMACMD596Etm() { + return new Factory("hmac-md5-96-etm@openssh.com", "HmacMD5", 12, 16, true); } public static Factory HMACRIPEMD160() { - return new Factory("hmac-ripemd160", "HMACRIPEMD160", 20, 20); + return new Factory("hmac-ripemd160", "HMACRIPEMD160", 20, 20, false); } public static Factory HMACRIPEMD16096() { - return new Factory("hmac-ripemd160-96", "HMACRIPEMD160", 12, 20); + return new Factory("hmac-ripemd160-96", "HMACRIPEMD160", 12, 20, false); + } + public static Factory HMACRIPEMD160Etm() { + return new Factory("hmac-ripemd160-etm@openssh.com", "HMACRIPEMD160", 20, 20, true); } public static Factory HMACRIPEMD160OpenSsh() { - return new Factory("hmac-ripemd160@openssh.com", "HMACRIPEMD160", 20, 20); + return new Factory("hmac-ripemd160@openssh.com", "HMACRIPEMD160", 20, 20, false); } public static Factory HMACSHA1() { - return new Factory("hmac-sha1", "HmacSHA1", 20, 20); + return new Factory("hmac-sha1", "HmacSHA1", 20, 20, false); } public static Factory HMACSHA196() { - return new Factory("hmac-sha1-96", "HmacSHA1", 12, 20); + return new Factory("hmac-sha1-96", "HmacSHA1", 12, 20, false); + } + public static Factory HMACSHA1Etm() { + return new Factory("hmac-sha1-etm@openssh.com", "HmacSHA1", 20, 20, true); + } + public static Factory HMACSHA196Etm() { + return new Factory("hmac-sha1-96@openssh.com", "HmacSHA1", 12, 20, true); } public static Factory HMACSHA2256() { - return new Factory("hmac-sha2-256", "HmacSHA256", 32, 32); + return new Factory("hmac-sha2-256", "HmacSHA256", 32, 32, false); + } + public static Factory HMACSHA2256Etm() { + return new Factory("hmac-sha2-256-etm@openssh.com", "HmacSHA256", 32, 32, true); } public static Factory HMACSHA2512() { - return new Factory("hmac-sha2-512", "HmacSHA512", 64, 64); + return new Factory("hmac-sha2-512", "HmacSHA512", 64, 64, false); + } + public static Factory HMACSHA2512Etm() { + return new Factory("hmac-sha2-512-etm@openssh.com", "HmacSHA512", 64, 64, true); } - private static class Factory implements net.schmizz.sshj.common.Factory.Named { + private static class Factory implements net.schmizz.sshj.common.Factory.Named { private String name; private String algorithm; private int bSize; private int defBSize; + private final boolean etm; - public Factory(String name, String algorithm, int bSize, int defBSize) { + public Factory(String name, String algorithm, int bSize, int defBSize, boolean etm) { this.name = name; this.algorithm = algorithm; this.bSize = bSize; this.defBSize = defBSize; + this.etm = etm; } @Override @@ -67,7 +92,7 @@ public String getName() { @Override public BaseMAC create() { - return new BaseMAC(algorithm, bSize, defBSize); + return new BaseMAC(algorithm, bSize, defBSize, etm); } } } diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index 91cfe2f62..5586f8f7f 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -20,6 +20,7 @@ import com.hierynomus.sshj.transport.cipher.StreamCiphers; import com.hierynomus.sshj.transport.kex.DHGroups; import com.hierynomus.sshj.transport.kex.ExtendedDHGroups; +import com.hierynomus.sshj.transport.mac.Macs; import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import net.schmizz.keepalive.KeepAliveProvider; import net.schmizz.sshj.common.Factory; @@ -219,12 +220,22 @@ protected void initSignatureFactories() { protected void initMACFactories() { setMACFactories( - new HMACSHA1.Factory(), - new HMACSHA196.Factory(), - new HMACMD5.Factory(), - new HMACMD596.Factory(), - new HMACSHA2256.Factory(), - new HMACSHA2512.Factory() + Macs.HMACSHA1(), + Macs.HMACSHA1Etm(), + Macs.HMACSHA196(), + Macs.HMACSHA196Etm(), + Macs.HMACMD5(), + Macs.HMACMD5Etm(), + Macs.HMACMD596(), + Macs.HMACMD596Etm(), + Macs.HMACSHA2256(), + Macs.HMACSHA2256Etm(), + Macs.HMACSHA2512(), + Macs.HMACSHA2512Etm(), + Macs.HMACRIPEMD160(), + Macs.HMACRIPEMD160Etm(), + Macs.HMACRIPEMD16096(), + Macs.HMACRIPEMD160OpenSsh() ); } diff --git a/src/main/java/net/schmizz/sshj/transport/Converter.java b/src/main/java/net/schmizz/sshj/transport/Converter.java index df1b2ad3b..0e817e2e5 100644 --- a/src/main/java/net/schmizz/sshj/transport/Converter.java +++ b/src/main/java/net/schmizz/sshj/transport/Converter.java @@ -44,6 +44,7 @@ abstract class Converter { protected int cipherSize = 8; protected long seq = -1; protected boolean authed; + protected boolean etm; long getSequenceNumber() { return seq; @@ -56,6 +57,7 @@ void setAlgorithms(Cipher cipher, MAC mac, Compression compression) { if (compression != null) compression.init(getCompressionType()); this.cipherSize = cipher.getIVSize(); + this.etm = mac.isEtm(); } void setAuthenticated() { diff --git a/src/main/java/net/schmizz/sshj/transport/Decoder.java b/src/main/java/net/schmizz/sshj/transport/Decoder.java index 98e015b8b..c3619cd5e 100644 --- a/src/main/java/net/schmizz/sshj/transport/Decoder.java +++ b/src/main/java/net/schmizz/sshj/transport/Decoder.java @@ -21,7 +21,9 @@ import net.schmizz.sshj.transport.mac.MAC; import org.slf4j.Logger; -/** Decodes packets from the SSH binary protocol per the current algorithms. */ +/** + * Decodes packets from the SSH binary protocol per the current algorithms. + */ final class Decoder extends Converter { @@ -29,16 +31,26 @@ final class Decoder private final Logger log; - /** What we pass decoded packets to */ + /** + * What we pass decoded packets to + */ private final SSHPacketHandler packetHandler; - /** Buffer where as-yet undecoded data lives */ + /** + * Buffer where as-yet undecoded data lives + */ private final SSHPacket inputBuffer = new SSHPacket(); - /** Used in case compression is active to store the uncompressed data */ + /** + * Used in case compression is active to store the uncompressed data + */ private final SSHPacket uncompressBuffer = new SSHPacket(); - /** MAC result is stored here */ + /** + * MAC result is stored here + */ private byte[] macResult; - /** -1 if packet length not yet been decoded, else the packet length */ + /** + * -1 if packet length not yet been decoded, else the packet length + */ private int packetLength = -1; /** @@ -60,53 +72,97 @@ final class Decoder */ private int decode() throws SSHException { + + if (etm) { + return decodeEtm(); + } else { + return decodeMte(); + } + } + + /** + * Decode an Encrypt-Then-Mac packet. + */ + private int decodeEtm() throws SSHException { + int bytesNeeded; + while (true) { + if (packetLength == -1) { + assert inputBuffer.rpos() == 0 : "buffer cleared"; + bytesNeeded = 4 - inputBuffer.available(); + if (bytesNeeded <= 0) { + // In Encrypt-Then-Mac, the packetlength is sent unencrypted. + packetLength = inputBuffer.readUInt32AsInt(); + checkPacketLength(packetLength); + } else { + // Needs more data + break; + } + } else { + assert inputBuffer.rpos() == 4 : "packet length read"; + bytesNeeded = packetLength + mac.getBlockSize() - inputBuffer.available(); + if (bytesNeeded <= 0) { + seq = seq + 1 & 0xffffffffL; + checkMAC(inputBuffer.array()); + decryptBuffer(4, packetLength); + inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte()); + final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer; + if (log.isTraceEnabled()) { + log.trace("Received packet #{}: {}", seq, plain.printHex()); + } + packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet + inputBuffer.clear(); + packetLength = -1; + } else { + // Needs more data + break; + } + } + } + return bytesNeeded; + } + + /** + * Decode a Mac-Then-Encrypt packet + * @return + * @throws SSHException + */ + private int decodeMte() throws SSHException { int need; /* Decoding loop */ for (; ; ) - if (packetLength == -1) // Waiting for beginning of packet - { - + if (packetLength == -1) { // Waiting for beginning of packet assert inputBuffer.rpos() == 0 : "buffer cleared"; - need = cipherSize - inputBuffer.available(); - if (need <= 0) + if (need <= 0) { packetLength = decryptLength(); - else + } else { // Need more data break; - + } } else { - assert inputBuffer.rpos() == 4 : "packet length read"; - need = packetLength + (mac != null ? mac.getBlockSize() : 0) - inputBuffer.available(); if (need <= 0) { - - decryptPayload(inputBuffer.array()); - + decryptBuffer(cipherSize, packetLength + 4 - cipherSize); // Decrypt the rest of the payload seq = seq + 1 & 0xffffffffL; - - if (mac != null) + if (mac != null) { checkMAC(inputBuffer.array()); - + } // Exclude the padding & MAC inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte()); - final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer; - - if (log.isTraceEnabled()) + if (log.isTraceEnabled()) { log.trace("Received packet #{}: {}", seq, plain.printHex()); - + } packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet - inputBuffer.clear(); packetLength = -1; - - } else + } else { // Need more data break; + } } return need; @@ -118,8 +174,9 @@ private void checkMAC(final byte[] data) mac.update(data, 0, packetLength + 4); // packetLength+4 = entire packet w/o mac mac.doFinal(macResult, 0); // compute // Check against the received MAC - if (!ByteArrayUtils.equals(macResult, 0, data, packetLength + 4, mac.getBlockSize())) + if (!ByteArrayUtils.equals(macResult, 0, data, packetLength + 4, mac.getBlockSize())) { throw new TransportException(DisconnectReason.MAC_ERROR, "MAC Error"); + } } private SSHPacket decompressed() @@ -131,7 +188,7 @@ private SSHPacket decompressed() private int decryptLength() throws TransportException { - cipher.update(inputBuffer.array(), 0, cipherSize); + decryptBuffer(0, cipherSize); final int len; // Read packet length try { @@ -140,22 +197,26 @@ private int decryptLength() throw new TransportException(be); } - if (isInvalidPacketLength(len)) { // Check packet length validity - log.error("Error decoding packet (invalid length) {}", inputBuffer.printHex()); - throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "invalid packet length: " + len); - } + checkPacketLength(len); return len; } - private static boolean isInvalidPacketLength(int len) { - return len < 5 || len > MAX_PACKET_LEN; + private void decryptBuffer(int offset, int length) { + cipher.update(inputBuffer.array(), offset, length); } - private void decryptPayload(final byte[] data) { - cipher.update(data, cipherSize, packetLength + 4 - cipherSize); + private void checkPacketLength(int len) throws TransportException { + if (len < 5 || len > MAX_PACKET_LEN) { // Check packet length validity + log.error("Error decoding packet (invalid length) {}", inputBuffer.printHex()); + throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "invalid packet length: " + len); + } } +// private void decryptPayload(final byte[] data, int offset, int length) { +// cipher.update(data, cipherSize, packetLength + 4 - cipherSize); +// } + /** * Adds {@code len} bytes from {@code b} to the decoder buffer. When a packet has been successfully decoded, hooks * in to {@link SSHPacketHandler#handle} of the {@link SSHPacketHandler} this decoder was initialized with. diff --git a/src/main/java/net/schmizz/sshj/transport/Encoder.java b/src/main/java/net/schmizz/sshj/transport/Encoder.java index d0ee02e40..9ece20416 100644 --- a/src/main/java/net/schmizz/sshj/transport/Encoder.java +++ b/src/main/java/net/schmizz/sshj/transport/Encoder.java @@ -67,36 +67,58 @@ long encode(SSHPacket buffer) { log.trace("Encoding packet #{}: {}", seq + 1, buffer.printHex()); } - if (usingCompression()) + if (usingCompression()) { compress(buffer); + } final int payloadSize = buffer.available(); + int lengthWithoutPadding; + if (etm) { + // in Encrypt-Then-Mac mode, the length field is not encrypted, so we should keep it out of the + // padding length calculation + lengthWithoutPadding = 1 + payloadSize; // padLength (1 byte) + payload + } else { + lengthWithoutPadding = 4 + 1 + payloadSize; // packetLength (4 bytes) + padLength (1 byte) + payload + } // Compute padding length - int padLen = -(payloadSize + 5) & cipherSize - 1; - if (padLen < cipherSize) + int padLen = cipherSize - (lengthWithoutPadding % cipherSize); + if (padLen < 4) { padLen += cipherSize; + } final int startOfPacket = buffer.rpos() - 5; - final int packetLen = payloadSize + 1 + padLen; + int packetLen = 1 + payloadSize + padLen; // packetLength = padLen (1 byte) + payload + padding + + if (packetLen < 16) { + padLen += cipherSize; + packetLen = 1 + payloadSize + padLen; + } + + final int endOfPadding = startOfPacket + 4 + packetLen; // Put packet header buffer.wpos(startOfPacket); buffer.putUInt32(packetLen); buffer.putByte((byte) padLen); - // Now wpos will mark end of padding - buffer.wpos(startOfPacket + 5 + payloadSize + padLen); + buffer.wpos(endOfPadding); + // Fill padding - prng.fill(buffer.array(), buffer.wpos() - padLen, padLen); + prng.fill(buffer.array(), endOfPadding - padLen, padLen); seq = seq + 1 & 0xffffffffL; - if (mac != null) - putMAC(buffer, startOfPacket, buffer.wpos()); - - cipher.update(buffer.array(), startOfPacket, 4 + packetLen); + if (etm) { + cipher.update(buffer.array(), startOfPacket + 4, packetLen); + putMAC(buffer, startOfPacket, endOfPadding); + } else { + if (mac != null) { + putMAC(buffer, startOfPacket, endOfPadding); + } + cipher.update(buffer.array(), startOfPacket, 4 + packetLen); + } buffer.rpos(startOfPacket); // Make ready-to-read return seq; diff --git a/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java b/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java index fa7fa2516..d4cdd7054 100644 --- a/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java +++ b/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java @@ -30,12 +30,18 @@ public class BaseMAC private final int defbsize; private final int bsize; private final byte[] tmp; + private final boolean etm; private javax.crypto.Mac mac; public BaseMAC(String algorithm, int bsize, int defbsize) { + this(algorithm, bsize, defbsize, false); + } + + public BaseMAC(String algorithm, int bsize, int defbsize, boolean isEtm) { this.algorithm = algorithm; this.bsize = bsize; this.defbsize = defbsize; + this.etm = isEtm; tmp = new byte[defbsize]; } @@ -112,4 +118,8 @@ public void update(long i) { update(tmp, 0, 4); } + @Override + public boolean isEtm() { + return etm; + } } diff --git a/src/main/java/net/schmizz/sshj/transport/mac/MAC.java b/src/main/java/net/schmizz/sshj/transport/mac/MAC.java index 3699414f3..2e9abec4b 100644 --- a/src/main/java/net/schmizz/sshj/transport/mac/MAC.java +++ b/src/main/java/net/schmizz/sshj/transport/mac/MAC.java @@ -15,7 +15,9 @@ */ package net.schmizz.sshj.transport.mac; -/** Message Authentication Code for use in SSH. It usually wraps a javax.crypto.Mac class. */ +/** + * Message Authentication Code for use in SSH. It usually wraps a javax.crypto.Mac class. + */ public interface MAC { byte[] doFinal(); @@ -33,4 +35,40 @@ public interface MAC { void update(byte[] foo, int start, int len); void update(long foo); + + /** + * Indicates that an Encrypt-Then-Mac algorithm was selected. + *

+ * This has the following implementation details. + * 1.5 transport: Protocol 2 Encrypt-then-MAC MAC algorithms + *

+ * OpenSSH supports MAC algorithms, whose names contain "-etm", that + * perform the calculations in a different order to that defined in RFC + * 4253. These variants use the so-called "encrypt then MAC" ordering, + * calculating the MAC over the packet ciphertext rather than the + * plaintext. This ordering closes a security flaw in the SSH transport + * protocol, where decryption of unauthenticated ciphertext provided a + * "decryption oracle" that could, in conjunction with cipher flaws, reveal + * session plaintext. + *

+ * Specifically, the "-etm" MAC algorithms modify the transport protocol + * to calculate the MAC over the packet ciphertext and to send the packet + * length unencrypted. This is necessary for the transport to obtain the + * length of the packet and location of the MAC tag so that it may be + * verified without decrypting unauthenticated data. + *

+ * As such, the MAC covers: + *

+ * mac = MAC(key, sequence_number || packet_length || encrypted_packet) + *

+ * where "packet_length" is encoded as a uint32 and "encrypted_packet" + * contains: + *

+ * byte padding_length + * byte[n1] payload; n1 = packet_length - padding_length - 1 + * byte[n2] random padding; n2 = padding_length + * + * @return Whether the MAC algorithm is an Encrypt-Then-Mac algorithm + */ + boolean isEtm(); } diff --git a/src/test/java/net/schmizz/sshj/transport/mac/BaseMacTest.java b/src/test/java/net/schmizz/sshj/transport/mac/BaseMacTest.java index 4c548016b..c0e6e1bef 100644 --- a/src/test/java/net/schmizz/sshj/transport/mac/BaseMacTest.java +++ b/src/test/java/net/schmizz/sshj/transport/mac/BaseMacTest.java @@ -23,6 +23,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class BaseMacTest { private static final Charset CHARSET = Charset.forName("US-ASCII"); @@ -40,11 +41,9 @@ public void testResizeTooBigKeys() { @Test(expected = SSHRuntimeException.class) public void testUnknownAlgorithm() { - // hopefully a algorithm with that name won't be created :-) - BaseMAC hmac = new BaseMAC("AlgorithmThatDoesNotExist", 20, 20); + BaseMAC hmac = new BaseMAC("AlgorithmThatDoesNotExist", 20, 20, false); hmac.init((KEY + "foo").getBytes(CHARSET)); - hmac.update(PLAIN_TEXT); - assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC)); + fail("Should not initialize a non-existent MAC"); } @Test From 17c368f9c243eb18940dfaaa90bb04b93f52054b Mon Sep 17 00:00:00 2001 From: OlivierSalasc Date: Thu, 27 Sep 2018 14:49:25 +0200 Subject: [PATCH 05/19] add Buffer capacity check for type UInt64 (#454) --- .../java/net/schmizz/sshj/common/Buffer.java | 1 + .../net/schmizz/sshj/common/BufferTest.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/net/schmizz/sshj/common/Buffer.java b/src/main/java/net/schmizz/sshj/common/Buffer.java index 183de8d35..c11852d78 100644 --- a/src/main/java/net/schmizz/sshj/common/Buffer.java +++ b/src/main/java/net/schmizz/sshj/common/Buffer.java @@ -372,6 +372,7 @@ public T putUInt64(BigInteger uint64) { @SuppressWarnings("unchecked") private T putUInt64Unchecked(long uint64) { + ensureCapacity(8); data[wpos++] = (byte) (uint64 >> 56); data[wpos++] = (byte) (uint64 >> 48); data[wpos++] = (byte) (uint64 >> 40); diff --git a/src/test/java/net/schmizz/sshj/common/BufferTest.java b/src/test/java/net/schmizz/sshj/common/BufferTest.java index 98a7a2033..7e61ad9a5 100644 --- a/src/test/java/net/schmizz/sshj/common/BufferTest.java +++ b/src/test/java/net/schmizz/sshj/common/BufferTest.java @@ -146,4 +146,28 @@ public void shouldHaveSameUInt64EncodingForBigIntegerAndLong() { assertArrayEquals("Value: " + value, bytesLong, bytesBigInt); } } + + + @Test + public void shouldExpandCapacityOfUInt32(){ + PlainBuffer buf = new PlainBuffer(); + for(int i=0;i Date: Tue, 23 Oct 2018 10:47:34 +0200 Subject: [PATCH 06/19] Improving logging for KeyExchanger (#458) --- .../transport/verification/FingerprintVerifier.java | 5 ++++- .../transport/verification/OpenSSHKnownHosts.java | 6 ++++++ .../verification/OpenSSHKnownHostsSpec.groovy | 12 ++++++++++++ .../verification/FingerprintVerifierSpec.groovy | 10 ++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/schmizz/sshj/transport/verification/FingerprintVerifier.java b/src/main/java/net/schmizz/sshj/transport/verification/FingerprintVerifier.java index 24045ed87..58656bf50 100644 --- a/src/main/java/net/schmizz/sshj/transport/verification/FingerprintVerifier.java +++ b/src/main/java/net/schmizz/sshj/transport/verification/FingerprintVerifier.java @@ -26,7 +26,6 @@ import net.schmizz.sshj.common.Buffer; import net.schmizz.sshj.common.SSHRuntimeException; import net.schmizz.sshj.common.SecurityUtils; -import net.schmizz.sshj.transport.verification.HostKeyVerifier; public class FingerprintVerifier implements HostKeyVerifier { private static final Pattern MD5_FINGERPRINT_PATTERN = Pattern.compile("[0-9a-f]{2}+(:[0-9a-f]{2}+){15}+"); @@ -121,4 +120,8 @@ public boolean verify(String hostname, int port, PublicKey key) { return Arrays.equals(fingerprintData, digestData); } + @Override + public String toString() { + return "FingerprintVerifier{digestAlgorithm='" + digestAlgorithm + "'}"; + } } \ No newline at end of file diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java index dfcaebf8d..3fbd14010 100644 --- a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java +++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java @@ -440,4 +440,10 @@ public static Marker fromString(String str) { return null; } } + + @Override + public String toString() { + return "OpenSSHKnownHosts{khFile='" + khFile + "'}"; + } + } diff --git a/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy b/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy index 1eb20d90a..ec0029c03 100644 --- a/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy +++ b/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy @@ -132,6 +132,18 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL h << ["schmizz.net", "69.163.155.180"] } + def "should produce meaningful toString()"() { + given: + def f = knownHosts("schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==") + + when: + def knownhosts = new OpenSSHKnownHosts(f) + + def toStringValue = knownhosts.toString() + then: + toStringValue == "OpenSSHKnownHosts{khFile='" + f + "'}" + } + def knownHosts(String s) { def f = temp.newFile("known_hosts") f.write(s) diff --git a/src/test/groovy/net/schmizz/sshj/transport/verification/FingerprintVerifierSpec.groovy b/src/test/groovy/net/schmizz/sshj/transport/verification/FingerprintVerifierSpec.groovy index c7093c125..2293a4d69 100644 --- a/src/test/groovy/net/schmizz/sshj/transport/verification/FingerprintVerifierSpec.groovy +++ b/src/test/groovy/net/schmizz/sshj/transport/verification/FingerprintVerifierSpec.groovy @@ -49,6 +49,16 @@ class FingerprintVerifierSpec extends Specification { } + def "should produce meaningful toString()"() { + given: + def verifier = FingerprintVerifier.getInstance("SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak") + + when: + def toStringValue = verifier.toString() + + then: + toStringValue == "FingerprintVerifier{digestAlgorithm='SHA-1'}" + } def getPublicKey() { def lines = new File("src/test/resources/keytypes/test_ed25519.pub").readLines() From 971ccf62738add9a1ca17f363ab3fb1711ec7484 Mon Sep 17 00:00:00 2001 From: Ben Manes Date: Fri, 16 Nov 2018 00:33:48 -0800 Subject: [PATCH 07/19] Add lock timeout for remote action (fixes #466) (#468) When the remove window size is expanded, a condition is waited on until the remote server acknowledges and completes the action. If the server does not respond, e.g. due to a connectivity issue, then this blocks the client indefinitely. Instead the client waits up to the connection's timeout (500 min default) and fails. This allows users to set a reasonable timeout, fail their operations, and retry accordingly. --- .../sshj/connection/channel/AbstractChannel.java | 3 ++- .../net/schmizz/sshj/connection/channel/Window.java | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java b/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java index 922018102..e31d0f4ee 100644 --- a/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java +++ b/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java @@ -102,7 +102,8 @@ protected AbstractChannel(Connection conn, String type, Charset remoteCharset) { protected void init(int recipient, long remoteWinSize, long remoteMaxPacketSize) { this.recipient = recipient; - rwin = new Window.Remote(remoteWinSize, (int) Math.min(remoteMaxPacketSize, REMOTE_MAX_PACKET_SIZE_CEILING), loggerFactory); + rwin = new Window.Remote(remoteWinSize, (int) Math.min(remoteMaxPacketSize, REMOTE_MAX_PACKET_SIZE_CEILING), + conn.getTimeoutMs(), loggerFactory); out = new ChannelOutputStream(this, trans, rwin); log.debug("Initialized - {}", this); } diff --git a/src/main/java/net/schmizz/sshj/connection/channel/Window.java b/src/main/java/net/schmizz/sshj/connection/channel/Window.java index 0d6faa12b..8839f1e53 100644 --- a/src/main/java/net/schmizz/sshj/connection/channel/Window.java +++ b/src/main/java/net/schmizz/sshj/connection/channel/Window.java @@ -20,6 +20,8 @@ import net.schmizz.sshj.connection.ConnectionException; import org.slf4j.Logger; +import java.util.concurrent.TimeUnit; + public abstract class Window { protected final Logger log; @@ -73,17 +75,23 @@ public String toString() { /** Controls how much data we can send before an adjustment notification from remote end is required. */ public static final class Remote extends Window { + private final long timeoutMs; - public Remote(long initialWinSize, int maxPacketSize, LoggerFactory loggerFactory) { + public Remote(long initialWinSize, int maxPacketSize, long timeoutMs, LoggerFactory loggerFactory) { super(initialWinSize, maxPacketSize, loggerFactory); + this.timeoutMs = timeoutMs; } public long awaitExpansion(long was) throws ConnectionException { synchronized (lock) { + long end = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs); while (size <= was) { log.debug("Waiting, need size to grow from {} bytes", was); try { - lock.wait(); + lock.wait(timeoutMs); + if ((size <= was) && ((System.nanoTime() - end) > 0)) { + throw new ConnectionException("Timeout when trying to expand the window size"); + } } catch (InterruptedException ie) { throw new ConnectionException(ie); } From 8721269d0f2e7345829a8849e523c95901551735 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 16 Nov 2018 10:07:21 +0100 Subject: [PATCH 08/19] Added EdDSA as first signature factory (Fixed #470) --- src/main/java/net/schmizz/sshj/DefaultConfig.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index 5586f8f7f..fe310ccdd 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -107,7 +107,8 @@ public void setLoggerFactory(LoggerFactory loggerFactory) { protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) { if (bouncyCastleRegistered) { - setKeyExchangeFactories(new Curve25519SHA256.Factory(), + setKeyExchangeFactories( + new Curve25519SHA256.Factory(), new DHGexSHA256.Factory(), new ECDHNistP.Factory521(), new ECDHNistP.Factory384(), @@ -209,12 +210,12 @@ protected void initCipherFactories() { protected void initSignatureFactories() { setSignatureFactories( + new SignatureEdDSA.Factory(), new SignatureECDSA.Factory256(), new SignatureECDSA.Factory384(), new SignatureECDSA.Factory521(), new SignatureRSA.Factory(), - new SignatureDSA.Factory(), - new SignatureEdDSA.Factory() + new SignatureDSA.Factory() ); } From aa201fa08c57b2f2bfd89e1aa341cd55bf645aeb Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 16 Nov 2018 10:39:20 +0100 Subject: [PATCH 09/19] Add AES256-CBC to OpenSSHKeyV1KeyFile (Fixes #467) --- .../keyprovider/OpenSSHKeyV1KeyFile.java | 5 ++++- .../sshj/keyprovider/OpenSSHKeyFileTest.java | 16 ++++++++++++---- .../resources/keytypes/ed25519_aes256cbc.pem | 8 ++++++++ .../resources/keytypes/ed25519_aes256cbc.pem.pub | 1 + 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/keytypes/ed25519_aes256cbc.pem create mode 100644 src/test/resources/keytypes/ed25519_aes256cbc.pem.pub diff --git a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java index 7e46b7364..b34cef592 100644 --- a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java +++ b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java @@ -21,6 +21,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.schmizz.sshj.common.*; import net.schmizz.sshj.common.Buffer.PlainBuffer; +import net.schmizz.sshj.transport.cipher.BlockCipher; import net.schmizz.sshj.transport.cipher.Cipher; import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; @@ -106,7 +107,7 @@ private KeyPair readDecodedKeyPair(final PlainBuffer keyBuffer) throws IOExcepti logger.debug("Reading unencrypted keypair"); return readUnencrypted(privateKeyBuffer, publicKey); } else { - logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + kdfOptions); + logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + Arrays.toString(kdfOptions)); PlainBuffer decrypted = decryptBuffer(privateKeyBuffer, cipherName, kdfName, kdfOptions); return readUnencrypted(decrypted, publicKey); // throw new IOException("Cannot read encrypted keypair with " + cipherName + " yet."); @@ -141,6 +142,8 @@ private void initializeCipher(String kdfName, byte[] kdfOptions, Cipher cipher) private Cipher createCipher(String cipherName) { if (cipherName.equals(BlockCiphers.AES256CTR().getName())) { return BlockCiphers.AES256CTR().create(); + } else if (cipherName.equals(BlockCiphers.AES256CBC().getName())) { + return BlockCiphers.AES256CBC().create(); } throw new IllegalStateException("Cipher '" + cipherName + "' not currently implemented for openssh-key-v1 format"); } diff --git a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java index 3969e6d03..1c7a6745f 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.PrivateKey; @@ -189,12 +188,21 @@ public void shouldLoadED25519PrivateKey() throws IOException { } @Test - public void shouldLoadProtectedED25519PrivateKey() throws IOException { + public void shouldLoadProtectedED25519PrivateKeyAes256CTR() throws IOException { + checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_protected", "sshjtest"); + } + + @Test + public void shouldLoadProtectedED25519PrivateKeyAes256CBC() throws IOException { + checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256cbc.pem", "foobar"); + } + + private void checkOpenSSHKeyV1(String key, String password) throws IOException { OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); - keyFile.init(new File("src/test/resources/keytypes/ed25519_protected"), new PasswordFinder() { + keyFile.init(new File(key), new PasswordFinder() { @Override public char[] reqPassword(Resource resource) { - return "sshjtest".toCharArray(); + return password.toCharArray(); } @Override diff --git a/src/test/resources/keytypes/ed25519_aes256cbc.pem b/src/test/resources/keytypes/ed25519_aes256cbc.pem new file mode 100644 index 000000000..971d522f0 --- /dev/null +++ b/src/test/resources/keytypes/ed25519_aes256cbc.pem @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABBLQVXV9f +Wpw8AL9RTpAr//AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdk +jTTBDF1GNz+228nuWprPV+NbQauAAAAAoGHEO7x3fSRBohvrIR52U4XD3uqRnhrPYm01k1 +f4HHNNv46m92Zw6JKIB9Trrvp0sdMI8MVb79bN45rbn6mvpABtWl6T5TOTyMnKzDfAOx9c +FTaasWFmgtgkXOsu5pLrYBAQgCHWbzjjz6KoV1DmD4SAn9Ojf9Oh+YdAEKZcsvklgpu+Kj +nzN/DR0jt7Nzep2kNCLAS24QEkvQeATVSDiL8= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/resources/keytypes/ed25519_aes256cbc.pem.pub b/src/test/resources/keytypes/ed25519_aes256cbc.pem.pub new file mode 100644 index 000000000..14e490205 --- /dev/null +++ b/src/test/resources/keytypes/ed25519_aes256cbc.pem.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local From 254f739ac1116eee7db943b19b312c9143eba4d1 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 16 Nov 2018 11:12:58 +0100 Subject: [PATCH 10/19] Upgraded sshd to 2.1.0 --- build.gradle | 5 ++++- .../channel/forwarded/RemotePortForwarderTest.java | 2 +- .../java/com/hierynomus/sshj/test/SshFixture.java | 11 ++++------- .../sshj/userauth/method/AuthPasswordTest.java | 13 +++++++++---- .../sshj/keyprovider/OpenSSHKeyFileTest.java | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 8251bb373..410663c8a 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ targetCompatibility = 1.6 configurations.compile.transitive = false def bouncycastleVersion = "1.60" +def sshdVersion = "2.1.0" dependencies { signature 'org.codehaus.mojo.signature:java16:1.1@signature' @@ -62,7 +63,9 @@ dependencies { testCompile "junit:junit:4.11" testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' testCompile "org.mockito:mockito-core:2.9.2" - testCompile "org.apache.sshd:sshd-core:1.2.0" + testCompile "org.apache.sshd:sshd-core:$sshdVersion" + testCompile "org.apache.sshd:sshd-sftp:$sshdVersion" + testCompile "org.apache.sshd:sshd-scp:$sshdVersion" testRuntime "ch.qos.logback:logback-classic:1.1.2" testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17' testCompile 'org.apache.httpcomponents:httpclient:4.5.2' diff --git a/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java b/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java index dc05613d4..eda13fab1 100644 --- a/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java +++ b/src/test/java/com/hierynomus/sshj/connection/channel/forwarded/RemotePortForwarderTest.java @@ -54,7 +54,7 @@ public class RemotePortForwarderTest { @Before public void setUp() throws IOException { - fixture.getServer().setTcpipForwardingFilter(new AcceptAllForwardingFilter()); + fixture.getServer().setForwardingFilter(new AcceptAllForwardingFilter()); File file = httpServer.getDocRoot().newFile("index.html"); FileUtil.writeToFile(file, "

Hi!

"); } diff --git a/src/test/java/com/hierynomus/sshj/test/SshFixture.java b/src/test/java/com/hierynomus/sshj/test/SshFixture.java index ae2a0e2bc..0db605961 100644 --- a/src/test/java/com/hierynomus/sshj/test/SshFixture.java +++ b/src/test/java/com/hierynomus/sshj/test/SshFixture.java @@ -20,12 +20,11 @@ import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.util.gss.BogusGSSAuthenticator; import org.apache.sshd.common.NamedFactory; -import org.apache.sshd.common.keyprovider.AbstractClassLoadableResourceKeyPairProvider; -import org.apache.sshd.common.util.SecurityUtils; -import org.apache.sshd.server.Command; -import org.apache.sshd.server.CommandFactory; +import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.auth.password.PasswordAuthenticator; +import org.apache.sshd.server.command.Command; +import org.apache.sshd.server.command.CommandFactory; import org.apache.sshd.server.scp.ScpCommandFactory; import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.shell.ProcessShellFactory; @@ -35,7 +34,6 @@ import java.io.IOException; import java.net.ServerSocket; import java.util.Arrays; -import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -108,8 +106,7 @@ public SSHClient connectClient(SSHClient client) throws IOException { private SshServer defaultSshServer() { SshServer sshServer = SshServer.setUpDefaultServer(); sshServer.setPort(randomPort()); - AbstractClassLoadableResourceKeyPairProvider fileKeyPairProvider = SecurityUtils.createClassLoadableResourceKeyPairProvider(); - fileKeyPairProvider.setResources(Collections.singletonList(hostkey)); + ClassLoadableResourceKeyPairProvider fileKeyPairProvider = new ClassLoadableResourceKeyPairProvider(hostkey); sshServer.setKeyPairProvider(fileKeyPairProvider); sshServer.setPasswordAuthenticator(new PasswordAuthenticator() { @Override diff --git a/src/test/java/com/hierynomus/sshj/userauth/method/AuthPasswordTest.java b/src/test/java/com/hierynomus/sshj/userauth/method/AuthPasswordTest.java index 1e61e45ad..acf4c2ea3 100644 --- a/src/test/java/com/hierynomus/sshj/userauth/method/AuthPasswordTest.java +++ b/src/test/java/com/hierynomus/sshj/userauth/method/AuthPasswordTest.java @@ -27,6 +27,7 @@ import org.apache.sshd.server.auth.password.PasswordAuthenticator; import org.apache.sshd.server.auth.password.PasswordChangeRequiredException; import org.apache.sshd.server.auth.password.UserAuthPassword; +import org.apache.sshd.server.auth.password.UserAuthPasswordFactory; import org.apache.sshd.server.session.ServerSession; import org.junit.Before; import org.junit.Rule; @@ -50,21 +51,25 @@ public class AuthPasswordTest { @Before public void setPasswordAuthenticator() throws IOException { fixture.getServer().setUserAuthFactories(Collections.>singletonList(new NamedFactory() { - @Override public String getName() { - return "password"; + return UserAuthPasswordFactory.NAME; } @Override - public UserAuth create() { + public UserAuth get() { return new UserAuthPassword() { @Override protected Boolean handleClientPasswordChangeRequest(Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword) throws Exception { - return checkPassword(buffer, session, username, newPassword); + return session.getPasswordAuthenticator().authenticate(username, newPassword, session); } }; } + + @Override + public UserAuth create() { + return get(); + } })); fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() { @Override diff --git a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java index 1c7a6745f..2a4628a06 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java @@ -18,13 +18,13 @@ import com.hierynomus.sshj.userauth.certificate.Certificate; import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; import net.schmizz.sshj.userauth.password.PasswordFinder; import net.schmizz.sshj.userauth.password.PasswordUtils; import net.schmizz.sshj.userauth.password.Resource; import net.schmizz.sshj.util.KeyUtil; -import org.apache.sshd.common.util.SecurityUtils; import org.junit.Before; import org.junit.Test; @@ -197,7 +197,7 @@ public void shouldLoadProtectedED25519PrivateKeyAes256CBC() throws IOException { checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256cbc.pem", "foobar"); } - private void checkOpenSSHKeyV1(String key, String password) throws IOException { + private void checkOpenSSHKeyV1(String key, final String password) throws IOException { OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); keyFile.init(new File(key), new PasswordFinder() { @Override From f71d34e1066c6127d41c5ba75aa9f20afca47179 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 16 Nov 2018 11:13:09 +0100 Subject: [PATCH 11/19] Ignore bin/ directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cc50e6a7b..7ecaa3ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ out/ target/ classes/ +bin/ build/ docs/ .gradle/ From 0301d4537f775b5731168a27b9d9135daf8d301e Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 16 Nov 2018 11:48:15 +0100 Subject: [PATCH 12/19] Enable 'curve25519-sha256@libssh.org' in DefaultConfig (Fixes #464) --- src/main/java/net/schmizz/sshj/DefaultConfig.java | 1 + src/main/java/net/schmizz/sshj/SSHClient.java | 4 +--- .../com/hierynomus/sshj/test/BaseAlgorithmTest.java | 9 +++++++-- .../sshj/transport/kex/KeyExchangeTest.java | 13 ++++++++++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index fe310ccdd..dd45b2bfb 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -109,6 +109,7 @@ protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) { if (bouncyCastleRegistered) { setKeyExchangeFactories( new Curve25519SHA256.Factory(), + new Curve25519SHA256.FactoryLibSsh(), new DHGexSHA256.Factory(), new ECDHNistP.Factory521(), new ECDHNistP.Factory384(), diff --git a/src/main/java/net/schmizz/sshj/SSHClient.java b/src/main/java/net/schmizz/sshj/SSHClient.java index 378b24e84..e6f8e34ba 100644 --- a/src/main/java/net/schmizz/sshj/SSHClient.java +++ b/src/main/java/net/schmizz/sshj/SSHClient.java @@ -61,7 +61,6 @@ import java.net.ServerSocket; import java.nio.charset.Charset; import java.security.KeyPair; -import java.security.PublicKey; import java.util.*; /** @@ -360,8 +359,7 @@ public void authPublickey(String username, Iterable keyProviders) * @throws TransportException if there was a transport-layer error */ public void authPublickey(String username, KeyProvider... keyProviders) - throws UserAuthException, - TransportException { + throws UserAuthException, TransportException { authPublickey(username, Arrays.asList(keyProviders)); } diff --git a/src/test/java/com/hierynomus/sshj/test/BaseAlgorithmTest.java b/src/test/java/com/hierynomus/sshj/test/BaseAlgorithmTest.java index 7567ac245..975f7d9e9 100644 --- a/src/test/java/com/hierynomus/sshj/test/BaseAlgorithmTest.java +++ b/src/test/java/com/hierynomus/sshj/test/BaseAlgorithmTest.java @@ -18,6 +18,8 @@ import net.schmizz.sshj.Config; import net.schmizz.sshj.DefaultConfig; import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.transport.random.JCERandom; +import net.schmizz.sshj.transport.random.SingletonRandomFactory; import org.apache.sshd.server.SshServer; import org.junit.After; import org.junit.Rule; @@ -32,6 +34,8 @@ public abstract class BaseAlgorithmTest { private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private SingletonRandomFactory randomFactory = new SingletonRandomFactory(new JCERandom.Factory()); + private DefaultConfig config = new DefaultConfig(); @Rule public SshFixture fixture = new SshFixture(false); @@ -42,11 +46,12 @@ public void stopServer() { @Test public void shouldVerifyAlgorithm() throws IOException { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 10; i++) { logger.info("--> Attempt {}", i); configureServer(fixture.getServer()); fixture.start(); - Config config = getClientConfig(new DefaultConfig()); + config.setRandomFactory(randomFactory); + Config config = getClientConfig(this.config); SSHClient sshClient = fixture.connectClient(fixture.setupClient(config)); assertThat("should be connected", sshClient.isConnected()); sshClient.disconnect(); diff --git a/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java b/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java index ad68318bb..1a9e7a513 100644 --- a/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java +++ b/src/test/java/com/hierynomus/sshj/transport/kex/KeyExchangeTest.java @@ -19,6 +19,7 @@ import net.schmizz.sshj.Config; import net.schmizz.sshj.DefaultConfig; import net.schmizz.sshj.common.Factory; +import net.schmizz.sshj.transport.kex.Curve25519SHA256; import net.schmizz.sshj.transport.kex.DHGexSHA1; import net.schmizz.sshj.transport.kex.DHGexSHA256; import net.schmizz.sshj.transport.kex.ECDHNistP; @@ -38,15 +39,21 @@ @RunWith(Parameterized.class) public class KeyExchangeTest extends BaseAlgorithmTest { - @Parameterized.Parameters + @Parameterized.Parameters(name = "algorithm={0}") public static Collection getParameters() { return Arrays.asList(new Object[][]{ {DHGEXServer.newFactory(BuiltinDHFactories.dhgex), new DHGexSHA1.Factory()}, {DHGEXServer.newFactory(BuiltinDHFactories.dhgex256), new DHGexSHA256.Factory()}, {DHGServer.newFactory(BuiltinDHFactories.ecdhp256), new ECDHNistP.Factory256()}, {DHGServer.newFactory(BuiltinDHFactories.ecdhp384), new ECDHNistP.Factory384()}, - {DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521()} - // Not supported yet by MINA {null, new Curve25519SHA256.Factory()} + {DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg1), DHGroups.Group1SHA1()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg14), DHGroups.Group14SHA1()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg14_256), DHGroups.Group14SHA256()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg15_512), DHGroups.Group15SHA512()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg16_512), DHGroups.Group16SHA512()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg17_512), DHGroups.Group17SHA512()}, + {DHGServer.newFactory(BuiltinDHFactories.dhg18_512), DHGroups.Group18SHA512()}, }); } From 17c09eb4715cc856a0836dc1e7037ee158033daa Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 16 Nov 2018 12:29:45 +0100 Subject: [PATCH 13/19] Fixed integration test --- src/itest/docker-image/Dockerfile | 4 ++++ .../test-container/ssh_host_ed25519_key | 7 +++++++ .../test-container/ssh_host_ed25519_key.pub | 1 + .../com/hierynomus/sshj/IntegrationSpec.groovy | 17 ++++++++++++++--- 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/itest/docker-image/test-container/ssh_host_ed25519_key create mode 100644 src/itest/docker-image/test-container/ssh_host_ed25519_key.pub diff --git a/src/itest/docker-image/Dockerfile b/src/itest/docker-image/Dockerfile index 67977b024..1b8bb5091 100644 --- a/src/itest/docker-image/Dockerfile +++ b/src/itest/docker-image/Dockerfile @@ -4,6 +4,8 @@ ADD id_rsa.pub /home/sshj/.ssh/authorized_keys ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub +ADD test-container/ssh_host_ed25519_key /etc/ssh/ssh_host_ed25519_key +ADD test-container/ssh_host_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub ADD test-container/sshd_config /etc/ssh/sshd_config RUN apk add --no-cache tini @@ -14,6 +16,8 @@ RUN \ chmod 600 /home/sshj/.ssh/authorized_keys && \ chmod 600 /etc/ssh/ssh_host_ecdsa_key && \ chmod 644 /etc/ssh/ssh_host_ecdsa_key.pub && \ + chmod 600 /etc/ssh/ssh_host_ed25519_key && \ + chmod 644 /etc/ssh/ssh_host_ed25519_key.pub && \ chown -R sshj:sshj /home/sshj ENTRYPOINT ["/sbin/tini", "/entrypoint.sh"] \ No newline at end of file diff --git a/src/itest/docker-image/test-container/ssh_host_ed25519_key b/src/itest/docker-image/test-container/ssh_host_ed25519_key new file mode 100644 index 000000000..a58a68fbb --- /dev/null +++ b/src/itest/docker-image/test-container/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBFG9PKAq8FtH0me+LHUE6YaVANCMqy/Znkffzief1W/gAAAKCyyoBkssqA +ZAAAAAtzc2gtZWQyNTUxOQAAACBFG9PKAq8FtH0me+LHUE6YaVANCMqy/Znkffzief1W/g +AAAED+Yfza2xk5LqP9pN6TpvhWYP0L60zOQJpHhbEuiS3LLkUb08oCrwW0fSZ74sdQTphp +UA0IyrL9meR9/OJ5/Vb+AAAAF2FqdmFuZXJwQEhlaW1kYWxsLmxvY2FsAQIDBAUG +-----END OPENSSH PRIVATE KEY----- diff --git a/src/itest/docker-image/test-container/ssh_host_ed25519_key.pub b/src/itest/docker-image/test-container/ssh_host_ed25519_key.pub new file mode 100644 index 000000000..fec9b06f0 --- /dev/null +++ b/src/itest/docker-image/test-container/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEUb08oCrwW0fSZ74sdQTphpUA0IyrL9meR9/OJ5/Vb+ ajvanerp@Heimdall.local diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy index 84a9ee17a..59a70de90 100644 --- a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy +++ b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy @@ -15,23 +15,34 @@ */ package com.hierynomus.sshj +import com.hierynomus.sshj.signature.SignatureEdDSA import net.schmizz.sshj.DefaultConfig import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.signature.SignatureECDSA import net.schmizz.sshj.transport.TransportException import net.schmizz.sshj.userauth.UserAuthException +import spock.lang.Unroll class IntegrationSpec extends IntegrationBaseSpec { - def "should accept correct key"() { + @Unroll + def "should accept correct key for #signatureName"() { given: - SSHClient sshClient = new SSHClient(new DefaultConfig()) - sshClient.addHostKeyVerifier("d3:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3") // test-containers/ssh_host_ecdsa_key's fingerprint + def config = new DefaultConfig() + config.setSignatureFactories(signatureFactory) + SSHClient sshClient = new SSHClient(config) + sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint when: sshClient.connect(SERVER_IP, DOCKER_PORT) then: sshClient.isConnected() + + where: + signatureFactory << [new SignatureECDSA.Factory256(), new SignatureEdDSA.Factory()] + fingerprint << ["d3:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3", "dc:68:38:ce:fc:6f:2c:d6:6d:6b:34:eb:5c:f0:41:6a"] + signatureName = signatureFactory.getName() } def "should decline wrong key"() throws IOException { From b0dee02bf9ba7c3569f4de2d104b9814f0327ea3 Mon Sep 17 00:00:00 2001 From: Pepijn Van Eeckhoudt Date: Mon, 26 Nov 2018 15:16:43 +0100 Subject: [PATCH 14/19] Handle server initiated global requests (#472) * Handle server initiated global requests * Code layout --- .../schmizz/sshj/connection/ConnectionImpl.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java b/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java index 5657d3a1f..cfaee366e 100644 --- a/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java +++ b/src/main/java/net/schmizz/sshj/connection/ConnectionImpl.java @@ -130,6 +130,9 @@ public void handle(Message msg, SSHPacket buf) getChannel(buf).handle(msg, buf); } else if (msg.in(80, 90)) { switch (msg) { + case GLOBAL_REQUEST: + gotGlobalRequest(buf); + break; case REQUEST_SUCCESS: gotGlobalReqResponse(buf); break; @@ -259,6 +262,20 @@ public void notifyError(SSHException error) { channels.clear(); } + private void gotGlobalRequest(SSHPacket buf) + throws ConnectionException, TransportException { + try { + final String requestName = buf.readString(); + boolean wantReply = buf.readBoolean(); + log.debug("Received GLOBAL_REQUEST `{}`; want reply: {}", requestName, wantReply); + if (wantReply) { + trans.write(new SSHPacket(Message.REQUEST_FAILURE)); + } + } catch (Buffer.BufferException be) { + throw new ConnectionException(be); + } + } + @Override public void setTimeoutMs(int timeoutMs) { this.timeoutMs = timeoutMs; From e14fb2f695456a5939687c7e97c21dad9061e18a Mon Sep 17 00:00:00 2001 From: Andremoniy Date: Tue, 27 Nov 2018 10:22:00 +0100 Subject: [PATCH 15/19] Expose the numeric code of the Response.StatusCode #473 (#474) * Expose the numeric code of the Response.StatusCode #473 * Expose the numeric code of the Response.StatusCode #473 --- .../java/net/schmizz/sshj/sftp/Response.java | 3 + .../sshj/sftp/ResponseStatusCodeTest.java | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java diff --git a/src/main/java/net/schmizz/sshj/sftp/Response.java b/src/main/java/net/schmizz/sshj/sftp/Response.java index c269291ae..b6c2b1a57 100644 --- a/src/main/java/net/schmizz/sshj/sftp/Response.java +++ b/src/main/java/net/schmizz/sshj/sftp/Response.java @@ -68,6 +68,9 @@ private StatusCode(int code) { this.code = code; } + public int getCode() { + return code; + } } private final int protocolVersion; diff --git a/src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java b/src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java new file mode 100644 index 000000000..564ee56c2 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.schmizz.sshj.sftp; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ResponseStatusCodeTest { + + @Test + public void shouldReturnProperNumericCodesForStatusCode() { + assertEquals(-1, Response.StatusCode.UNKNOWN.getCode()); + assertEquals(0, Response.StatusCode.OK.getCode()); + assertEquals(1, Response.StatusCode.EOF.getCode()); + assertEquals(2, Response.StatusCode.NO_SUCH_FILE.getCode()); + assertEquals(3, Response.StatusCode.PERMISSION_DENIED.getCode()); + assertEquals(4, Response.StatusCode.FAILURE.getCode()); + assertEquals(5, Response.StatusCode.BAD_MESSAGE.getCode()); + assertEquals(6, Response.StatusCode.NO_CONNECTION.getCode()); + assertEquals(7, Response.StatusCode.CONNECITON_LOST.getCode()); + assertEquals(8, Response.StatusCode.OP_UNSUPPORTED.getCode()); + assertEquals(9, Response.StatusCode.INVALID_HANDLE.getCode()); + assertEquals(10, Response.StatusCode.NO_SUCH_PATH.getCode()); + assertEquals(11, Response.StatusCode.FILE_ALREADY_EXISTS.getCode()); + assertEquals(12, Response.StatusCode.WRITE_PROTECT.getCode()); + assertEquals(13, Response.StatusCode.NO_MEDIA.getCode()); + assertEquals(14, Response.StatusCode.NO_SPACE_ON_FILESYSTEM.getCode()); + assertEquals(15, Response.StatusCode.QUOTA_EXCEEDED.getCode()); + assertEquals(16, Response.StatusCode.UNKNOWN_PRINCIPAL.getCode()); + assertEquals(17, Response.StatusCode.LOCK_CONFLICT.getCode()); + assertEquals(18, Response.StatusCode.DIR_NOT_EMPTY.getCode()); + assertEquals(19, Response.StatusCode.NOT_A_DIRECTORY.getCode()); + assertEquals(20, Response.StatusCode.INVALID_FILENAME.getCode()); + assertEquals(21, Response.StatusCode.LINK_LOOP.getCode()); + assertEquals(22, Response.StatusCode.CANNOT_DELETE.getCode()); + assertEquals(23, Response.StatusCode.INVALID_PARAMETER.getCode()); + assertEquals(24, Response.StatusCode.FILE_IS_A_DIRECTORY.getCode()); + assertEquals(25, Response.StatusCode.BYTE_RANGE_LOCK_CONFLICT.getCode()); + assertEquals(26, Response.StatusCode.BYTE_RANGE_LOCK_REFUSED.getCode()); + assertEquals(27, Response.StatusCode.DELETE_PENDING.getCode()); + assertEquals(28, Response.StatusCode.FILE_CORRUPT.getCode()); + assertEquals(29, Response.StatusCode.OWNER_INVALID.getCode()); + assertEquals(30, Response.StatusCode.GROUP_INVALID.getCode()); + assertEquals(31, Response.StatusCode.NO_MATCHING_BYTE_RANGE_LOCK.getCode()); + } + +} From 00cd335f474fad7d9788d9566fc9707bfb145dd9 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Tue, 27 Nov 2018 11:27:45 +0100 Subject: [PATCH 16/19] Moved tests to spock --- .../schmizz/sshj/sftp/PathHelperSpec.groovy | 62 ++++++++++++ .../sshj/sftp/ResponseStatusCodeSpec.groovy | 64 ++++++++++++ .../net/schmizz/sshj/sftp/PathHelperTest.java | 98 ------------------- .../sshj/sftp/ResponseStatusCodeTest.java | 61 ------------ 4 files changed, 126 insertions(+), 159 deletions(-) create mode 100644 src/test/groovy/net/schmizz/sshj/sftp/PathHelperSpec.groovy create mode 100644 src/test/groovy/net/schmizz/sshj/sftp/ResponseStatusCodeSpec.groovy delete mode 100644 src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java delete mode 100644 src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java diff --git a/src/test/groovy/net/schmizz/sshj/sftp/PathHelperSpec.groovy b/src/test/groovy/net/schmizz/sshj/sftp/PathHelperSpec.groovy new file mode 100644 index 000000000..af835ef97 --- /dev/null +++ b/src/test/groovy/net/schmizz/sshj/sftp/PathHelperSpec.groovy @@ -0,0 +1,62 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.schmizz.sshj.sftp + +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Unroll + +class PathHelperSpec extends Specification { + + @Shared + def pathHelper = new PathHelper(new PathHelper.Canonicalizer() { + /** + * Very basic, it does not try to canonicalize relative bits in the middle of a path. + */ + @Override + String canonicalize(String path) + throws IOException { + if ("" == path || "." == path || "./" == path) + return "/home/me" + if (".." == path || "../" == path) + return "/home" + return path + } + }, "/") + + + @Unroll + def "should correctly componentize path \"#input\""() { + given: + def components = pathHelper.getComponents(input) + + expect: + components.getName() == name + components.getParent() == parent + components.getPath() == path + + where: + input || name | path | parent + "" || "me" | "/home/me" | "/home" + "/" || "/" | "/" | "" + "." || "me" | "/home/me" | "/home" + ".." || "home" | "/home" | "/" + "somefile" || "somefile" | "somefile" | "" + "dir1/dir2" || "dir2" | "dir1/dir2" | "dir1" + "/home/me/../somedir/somefile" || "somefile" | "/home/me/../somedir/somefile" | "/home/me/../somedir" + + } +} diff --git a/src/test/groovy/net/schmizz/sshj/sftp/ResponseStatusCodeSpec.groovy b/src/test/groovy/net/schmizz/sshj/sftp/ResponseStatusCodeSpec.groovy new file mode 100644 index 000000000..01e8cc90b --- /dev/null +++ b/src/test/groovy/net/schmizz/sshj/sftp/ResponseStatusCodeSpec.groovy @@ -0,0 +1,64 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.schmizz.sshj.sftp + +import spock.lang.Specification +import spock.lang.Unroll + +class ResponseStatusCodeSpec extends Specification { + + @Unroll + def "status #status should have status code #code"() { + expect: + code == status.getCode() + + where: + status || code + Response.StatusCode.UNKNOWN || -1 + Response.StatusCode.OK || 0 + Response.StatusCode.EOF || 1 + Response.StatusCode.NO_SUCH_FILE || 2 + Response.StatusCode.PERMISSION_DENIED || 3 + Response.StatusCode.FAILURE || 4 + Response.StatusCode.BAD_MESSAGE || 5 + Response.StatusCode.NO_CONNECTION || 6 + Response.StatusCode.CONNECITON_LOST || 7 + Response.StatusCode.OP_UNSUPPORTED || 8 + Response.StatusCode.INVALID_HANDLE || 9 + Response.StatusCode.NO_SUCH_PATH || 10 + Response.StatusCode.FILE_ALREADY_EXISTS || 11 + Response.StatusCode.WRITE_PROTECT || 12 + Response.StatusCode.NO_MEDIA || 13 + Response.StatusCode.NO_SPACE_ON_FILESYSTEM || 14 + Response.StatusCode.QUOTA_EXCEEDED || 15 + Response.StatusCode.UNKNOWN_PRINCIPAL || 16 + Response.StatusCode.LOCK_CONFLICT || 17 + Response.StatusCode.DIR_NOT_EMPTY || 18 + Response.StatusCode.NOT_A_DIRECTORY || 19 + Response.StatusCode.INVALID_FILENAME || 20 + Response.StatusCode.LINK_LOOP || 21 + Response.StatusCode.CANNOT_DELETE || 22 + Response.StatusCode.INVALID_PARAMETER || 23 + Response.StatusCode.FILE_IS_A_DIRECTORY || 24 + Response.StatusCode.BYTE_RANGE_LOCK_CONFLICT || 25 + Response.StatusCode.BYTE_RANGE_LOCK_REFUSED || 26 + Response.StatusCode.DELETE_PENDING || 27 + Response.StatusCode.FILE_CORRUPT || 28 + Response.StatusCode.OWNER_INVALID || 29 + Response.StatusCode.GROUP_INVALID || 30 + Response.StatusCode.NO_MATCHING_BYTE_RANGE_LOCK || 31 + } +} diff --git a/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java b/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java deleted file mode 100644 index 957ad099e..000000000 --- a/src/test/java/net/schmizz/sshj/sftp/PathHelperTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C)2009 - SSHJ Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.schmizz.sshj.sftp; - -import org.junit.Test; - -import java.io.IOException; - -import static junit.framework.Assert.assertEquals; - -public class PathHelperTest { - - private final PathHelper helper = new PathHelper(new PathHelper.Canonicalizer() { - /** - * Very basic, it does not try to canonicalize relative bits in the middle of a path. - */ - @Override - public String canonicalize(String path) - throws IOException { - if ("".equals(path) || ".".equals(path) || "./".equals(path)) - return "/home/me"; - if ("..".equals(path) || "../".equals(path)) - return "/home"; - return path; - } - }, "/"); - - @Test - public void empty() - throws IOException { - final PathComponents comp = helper.getComponents(""); - assertEquals("me", comp.getName()); - assertEquals("/home", comp.getParent()); - } - - @Test - public void root() - throws IOException { - final PathComponents comp = helper.getComponents("/"); - assertEquals("/", comp.getName()); - assertEquals("", comp.getParent()); - } - - @Test - public void dot() - throws IOException { - final PathComponents comp = helper.getComponents("."); - assertEquals("me", comp.getName()); - assertEquals("/home", comp.getParent()); - } - - @Test - public void dotDot() - throws IOException { - final PathComponents comp = helper.getComponents(".."); - assertEquals("home", comp.getName()); - assertEquals("/", comp.getParent()); - } - - @Test - public void fileInHomeDir() - throws IOException { - final PathComponents comp = helper.getComponents("somefile"); - assertEquals("somefile", comp.getName()); - assertEquals("somefile", comp.getPath()); - assertEquals("", comp.getParent()); - } - - @Test - public void pathInHomeDir() throws IOException { - final PathComponents comp = helper.getComponents("dir1/dir2"); - assertEquals("dir2", comp.getName()); - assertEquals("dir1/dir2", comp.getPath()); - assertEquals("dir1", comp.getParent()); - } - - @Test - public void fileSomeLevelsDeep() - throws IOException { - final PathComponents comp = helper.getComponents("/home/me/../somedir/somefile"); - assertEquals("somefile", comp.getName()); - assertEquals("/home/me/../somedir", comp.getParent()); - } - -} diff --git a/src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java b/src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java deleted file mode 100644 index 564ee56c2..000000000 --- a/src/test/java/net/schmizz/sshj/sftp/ResponseStatusCodeTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C)2009 - SSHJ Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.schmizz.sshj.sftp; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class ResponseStatusCodeTest { - - @Test - public void shouldReturnProperNumericCodesForStatusCode() { - assertEquals(-1, Response.StatusCode.UNKNOWN.getCode()); - assertEquals(0, Response.StatusCode.OK.getCode()); - assertEquals(1, Response.StatusCode.EOF.getCode()); - assertEquals(2, Response.StatusCode.NO_SUCH_FILE.getCode()); - assertEquals(3, Response.StatusCode.PERMISSION_DENIED.getCode()); - assertEquals(4, Response.StatusCode.FAILURE.getCode()); - assertEquals(5, Response.StatusCode.BAD_MESSAGE.getCode()); - assertEquals(6, Response.StatusCode.NO_CONNECTION.getCode()); - assertEquals(7, Response.StatusCode.CONNECITON_LOST.getCode()); - assertEquals(8, Response.StatusCode.OP_UNSUPPORTED.getCode()); - assertEquals(9, Response.StatusCode.INVALID_HANDLE.getCode()); - assertEquals(10, Response.StatusCode.NO_SUCH_PATH.getCode()); - assertEquals(11, Response.StatusCode.FILE_ALREADY_EXISTS.getCode()); - assertEquals(12, Response.StatusCode.WRITE_PROTECT.getCode()); - assertEquals(13, Response.StatusCode.NO_MEDIA.getCode()); - assertEquals(14, Response.StatusCode.NO_SPACE_ON_FILESYSTEM.getCode()); - assertEquals(15, Response.StatusCode.QUOTA_EXCEEDED.getCode()); - assertEquals(16, Response.StatusCode.UNKNOWN_PRINCIPAL.getCode()); - assertEquals(17, Response.StatusCode.LOCK_CONFLICT.getCode()); - assertEquals(18, Response.StatusCode.DIR_NOT_EMPTY.getCode()); - assertEquals(19, Response.StatusCode.NOT_A_DIRECTORY.getCode()); - assertEquals(20, Response.StatusCode.INVALID_FILENAME.getCode()); - assertEquals(21, Response.StatusCode.LINK_LOOP.getCode()); - assertEquals(22, Response.StatusCode.CANNOT_DELETE.getCode()); - assertEquals(23, Response.StatusCode.INVALID_PARAMETER.getCode()); - assertEquals(24, Response.StatusCode.FILE_IS_A_DIRECTORY.getCode()); - assertEquals(25, Response.StatusCode.BYTE_RANGE_LOCK_CONFLICT.getCode()); - assertEquals(26, Response.StatusCode.BYTE_RANGE_LOCK_REFUSED.getCode()); - assertEquals(27, Response.StatusCode.DELETE_PENDING.getCode()); - assertEquals(28, Response.StatusCode.FILE_CORRUPT.getCode()); - assertEquals(29, Response.StatusCode.OWNER_INVALID.getCode()); - assertEquals(30, Response.StatusCode.GROUP_INVALID.getCode()); - assertEquals(31, Response.StatusCode.NO_MATCHING_BYTE_RANGE_LOCK.getCode()); - } - -} From cac340dd438feebbca2d917684d39501ddc2e733 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Thu, 17 Jan 2019 13:01:49 +0100 Subject: [PATCH 17/19] Add support for other keytypes to openssh-key-v1 keyfiles (#485) * Added support for RSA to openssh-key-v1 keyfile * Fixed exception * Added ECDSA support to openssh-key-v1 * Added integration tests for different keytypes --- src/itest/docker-image/Dockerfile | 2 +- src/itest/docker-image/authorized_keys | 7 ++ src/itest/docker-image/id_rsa.pub | 1 - .../sshj/IntegrationBaseSpec.groovy | 2 +- .../hierynomus/sshj/IntegrationSpec.groovy | 18 ++++- .../resources/keyfiles/id_ecdsa_nistp256 | 5 ++ .../resources/keyfiles/id_ecdsa_opensshv1 | 9 +++ .../resources/keyfiles/id_ed25519_opensshv1 | 7 ++ .../id_ed25519_opensshv1_aes256cbc.pem | 8 ++ .../keyfiles/id_ed25519_opensshv1_protected | 8 ++ src/itest/resources/keyfiles/id_rsa | 27 +++++++ src/itest/resources/keyfiles/id_rsa_opensshv1 | 49 ++++++++++++ src/itest/resources/keyfiles/id_unknown_key | 15 ++++ .../keyprovider/OpenSSHKeyV1KeyFile.java | 75 +++++++++++++++---- .../userauth/keyprovider/KeyProviderUtil.java | 2 +- .../sshj/keyprovider/OpenSSHKeyFileTest.java | 16 ++++ src/test/resources/keyformats/ecdsa_opensshv1 | 9 +++ .../resources/keyformats/ecdsa_opensshv1.pub | 1 + src/test/resources/keyformats/rsa_opensshv1 | 49 ++++++++++++ .../resources/keyformats/rsa_opensshv1.pub | 1 + 20 files changed, 288 insertions(+), 23 deletions(-) create mode 100644 src/itest/docker-image/authorized_keys delete mode 100644 src/itest/docker-image/id_rsa.pub create mode 100644 src/itest/resources/keyfiles/id_ecdsa_nistp256 create mode 100644 src/itest/resources/keyfiles/id_ecdsa_opensshv1 create mode 100644 src/itest/resources/keyfiles/id_ed25519_opensshv1 create mode 100644 src/itest/resources/keyfiles/id_ed25519_opensshv1_aes256cbc.pem create mode 100644 src/itest/resources/keyfiles/id_ed25519_opensshv1_protected create mode 100644 src/itest/resources/keyfiles/id_rsa create mode 100644 src/itest/resources/keyfiles/id_rsa_opensshv1 create mode 100644 src/itest/resources/keyfiles/id_unknown_key create mode 100644 src/test/resources/keyformats/ecdsa_opensshv1 create mode 100644 src/test/resources/keyformats/ecdsa_opensshv1.pub create mode 100644 src/test/resources/keyformats/rsa_opensshv1 create mode 100644 src/test/resources/keyformats/rsa_opensshv1.pub diff --git a/src/itest/docker-image/Dockerfile b/src/itest/docker-image/Dockerfile index 1b8bb5091..1f83f58ba 100644 --- a/src/itest/docker-image/Dockerfile +++ b/src/itest/docker-image/Dockerfile @@ -1,6 +1,6 @@ FROM sickp/alpine-sshd:7.5-r2 -ADD id_rsa.pub /home/sshj/.ssh/authorized_keys +ADD authorized_keys /home/sshj/.ssh/authorized_keys ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub diff --git a/src/itest/docker-image/authorized_keys b/src/itest/docker-image/authorized_keys new file mode 100644 index 000000000..f6f7fdf15 --- /dev/null +++ b/src/itest/docker-image/authorized_keys @@ -0,0 +1,7 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOEQcvowiV3igdRO7rKPrZrao1hCQrnC4tgsxqSJdQCbABI+vHrdbJRfWZNuSk48aAtARJzJVmkn/r63EPJgkh8= root@itgcpkerberosstack-cbgateway-0-20151117031915 +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8gohA/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6M= ajvanerp@Heimdall.local +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq2LSHRavxAT7ja2f+5soOUJl/zKSI ajvanerp@Heimdall.xebialabs.com +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local diff --git a/src/itest/docker-image/id_rsa.pub b/src/itest/docker-image/id_rsa.pub deleted file mode 100644 index 6c50ee239..000000000 --- a/src/itest/docker-image/id_rsa.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy index 52e5d5e90..e62b49e6b 100644 --- a/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy +++ b/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy @@ -24,7 +24,7 @@ import spock.lang.Specification class IntegrationBaseSpec extends Specification { protected static final int DOCKER_PORT = 2222 protected static final String USERNAME = "sshj" - protected static final String KEYFILE = "src/test/resources/id_rsa" + protected static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa" protected final static String SERVER_IP = System.getProperty("serverIP", "127.0.0.1") protected static SSHClient getConnectedClient(Config config) { diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy index 59a70de90..fc4c30de8 100644 --- a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy +++ b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy @@ -57,15 +57,27 @@ class IntegrationSpec extends IntegrationBaseSpec { thrown(TransportException.class) } - def "should authenticate"() { + @Unroll + def "should authenticate with key #key"() { given: SSHClient client = getConnectedClient() when: - client.authPublickey(USERNAME, KEYFILE) + def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key") + client.authPublickey(USERNAME, keyProvider) then: client.isAuthenticated() + + where: + key | passphrase +// "id_ecdsa_nistp256" | null // TODO: Need to improve PKCS8 key support. + "id_ecdsa_opensshv1" | null + "id_ed25519_opensshv1" | null + "id_ed25519_opensshv1_aes256cbc.pem" | "foobar" + "id_ed25519_opensshv1_protected" | "sshjtest" + "id_rsa" | null + "id_rsa_opensshv1" | null } def "should not authenticate with wrong key"() { @@ -73,7 +85,7 @@ class IntegrationSpec extends IntegrationBaseSpec { SSHClient client = getConnectedClient() when: - client.authPublickey("sshj", "src/test/resources/id_dsa") + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key") then: thrown(UserAuthException.class) diff --git a/src/itest/resources/keyfiles/id_ecdsa_nistp256 b/src/itest/resources/keyfiles/id_ecdsa_nistp256 new file mode 100644 index 000000000..720e7fc05 --- /dev/null +++ b/src/itest/resources/keyfiles/id_ecdsa_nistp256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJUMlsSlXqCZmCjlN4kV7hzP+p9pu0fwJ8r4m1qle58SoAoGCCqGSM49 +AwEHoUQDQgAE4RBy+jCJXeKB1E7uso+tmtqjWEJCucLi2CzGpIl1AJsAEj68et1s +lF9Zk25KTjxoC0BEnMlWaSf+vrcQ8mCSHw== +-----END EC PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_ecdsa_opensshv1 b/src/itest/resources/keyfiles/id_ecdsa_opensshv1 new file mode 100644 index 000000000..85b41b2cb --- /dev/null +++ b/src/itest/resources/keyfiles/id_ecdsa_opensshv1 @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR0ImZtMAW5iPIKIQPzfYq9TvnoIpC+ +kvRY2UvBh28eK0xyNVfr218cdjvWxVrXqdTxW+IqMLWZMX+oL0YxpC+jAAAAsD+6Oow/uj +qMAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8goh +A/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6 +MAAAAgXNC11pInVAOd3xNphiHMoISeitf6h1IKbDM+niLrL5kAAAAXYWp2YW5lcnBASGVp +bWRhbGwubG9jYWwB +-----END OPENSSH PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_ed25519_opensshv1 b/src/itest/resources/keyfiles/id_ed25519_opensshv1 new file mode 100644 index 000000000..7e62b1f17 --- /dev/null +++ b/src/itest/resources/keyfiles/id_ed25519_opensshv1 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQAAAJDimgR84poE +fAAAAAtzc2gtZWQyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQ +AAAECmsckQycWnfGQK6XtQpaMGODbAkMQOdJNK6XJSipB7dDAdJiRkkBM8yC8seTEoAn2P +fwbLKrkcahZ0xxPoWICJAAAACXJvb3RAc3NoagECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_ed25519_opensshv1_aes256cbc.pem b/src/itest/resources/keyfiles/id_ed25519_opensshv1_aes256cbc.pem new file mode 100644 index 000000000..971d522f0 --- /dev/null +++ b/src/itest/resources/keyfiles/id_ed25519_opensshv1_aes256cbc.pem @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABBLQVXV9f +Wpw8AL9RTpAr//AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdk +jTTBDF1GNz+228nuWprPV+NbQauAAAAAoGHEO7x3fSRBohvrIR52U4XD3uqRnhrPYm01k1 +f4HHNNv46m92Zw6JKIB9Trrvp0sdMI8MVb79bN45rbn6mvpABtWl6T5TOTyMnKzDfAOx9c +FTaasWFmgtgkXOsu5pLrYBAQgCHWbzjjz6KoV1DmD4SAn9Ojf9Oh+YdAEKZcsvklgpu+Kj +nzN/DR0jt7Nzep2kNCLAS24QEkvQeATVSDiL8= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_ed25519_opensshv1_protected b/src/itest/resources/keyfiles/id_ed25519_opensshv1_protected new file mode 100644 index 000000000..d5a6bc02c --- /dev/null +++ b/src/itest/resources/keyfiles/id_ed25519_opensshv1_protected @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB/aWL0WG +iYPOTxGlFwvaCNAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq +2LSHRavxAT7ja2f+5soOUJl/zKSIAAAAsKplAiFbOhzcOJYFYBYm8sqYbvhPF8jKdQFkbo +LAOeq+vQ0YBV9XUWQQM2tmL+RPjykPJZ2thcHLpVp3PfUEgo4bImCt939b3Ji3cEwD3QuK +MIhjhx1KvSJNF/uhjwPJnttwHG+ld8F5Gv7LpTOUmOzXKGLIgYRuwonhs5ezdNv5ERs+Cq +M9p/SW5ehL5KPJhGa5a+ZQXRojwEH7J4Q5xztH1gviTdIEpFWWQBH8rX6y +-----END OPENSSH PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_rsa b/src/itest/resources/keyfiles/id_rsa new file mode 100644 index 000000000..8f5dbc482 --- /dev/null +++ b/src/itest/resources/keyfiles/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoAIBAAKCAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQm +TIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6 +RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmu +hA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPClizt +spKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmL +GGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQIBIwKCAQBXvO4uJlbrLJQDPYAt +1i1ybGcF+rrR/Q34a2dgCpZDEwFiDTlcv1hxx689OXTf5cMPXGDZXX5udd9p7Wxa +NqnIrvVUtQWLdqcZuEeO+gitHr8IyMJf5Lm8C/u5vl1PYOYhO0qxwmrTP1u6fZPh +zWX2X1p5626/sy+TCisCRDeLRyes+Dtfs3bDjUq+zF3D/DmeYY55LUx0XS27uXNS +QuUDMSnymFyj4o+jPK0q/j5w4bB+0rbsij+EP7S//jOFrSEcZgBhhIj0rHA5fo6w +NrgtgRKD3HKFBM3b4VM8TdMbHsmf+nT9DjiDqcs+IxXMGlb1XTjtQFIN2eyRtNLd +eQ0bAoGBAMwgv3rGytRjVbR4TT77eF81svzitOJWRdfXuKB5gqM3jsPR08f1MEtZ +44kaI5ByJ3mBDt/EwNgLRdmBddPrLu3so9VLdRmWKI+KNGxwkcxzJv1xXdicgw+w +S5WgigJryuUbtdylXQTlRArLUKsXULk/MndhGiD+a4fZ3dUtktF9AoGBAMqxh6tr +S0ao0rN4hc9I92gwPubD1+XQr9TJQEEqGv3g5O3BdfDrTvizfaeNArahrIqyO5SK +7iDg0xcHqdxmVmmCJ8CkIWBPXLU6erQ1QNlBJmnzYn5lR0Ksx2h/myjeXztvJKEM +q4xUjAEzWjmwxxU3Y6l3FokvgIU4kOVoE4JdAoGARfyZa+xizHnUPeAae/5yaclE +rnmdGma43En2KGQsyj7vHpEVaSDdW6nKWuRj9wKRMPkMafpQvxnOzjsD06EXZ4RV +bbN4mw7pVcj8B+wUuyAqoAmchMfya8dqXy+6SfkSXS4Sd4knNODkIPVAOqjoegcJ +/QtZamXbuYyGkjuCy3sCgYBLSUEFJ9ohjymwX/cvvAQfYmCBmTLu9b2mzmhSt94j +yI+Lgl8BtnxrANbmdjQ1NLxuB6+61IRVWtIP3kZnzj1aY4sbqq1PqHLkOkrVOFnq +S2YKGJJMND8KIur67ZFm84J1KUgej61uow9uKQRBUEnx8AByJOsdAwPZteys+sVq +7wKBgAc3BL6ttDVYlL8U8fgvTCGuIajItvOQQj1n8RKyRLblYXBKAtY+pbULzmF+ +HscRgJMcwEIosEbTzzBNEVQm6dS6R/Q534C00Fpsw1z/PFTI8AOdUzTROGjuN8Fg +YZoqMQLhk/PB8V4l7yJmPrE971RmJBBDlLDt6hZwOYEI2yF4 +-----END RSA PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_rsa_opensshv1 b/src/itest/resources/keyfiles/id_rsa_opensshv1 new file mode 100644 index 000000000..7263a28ce --- /dev/null +++ b/src/itest/resources/keyfiles/id_rsa_opensshv1 @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/src/itest/resources/keyfiles/id_unknown_key b/src/itest/resources/keyfiles/id_unknown_key new file mode 100644 index 000000000..8e1759d92 --- /dev/null +++ b/src/itest/resources/keyfiles/id_unknown_key @@ -0,0 +1,15 @@ +-----BEGIN DSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,9B6744BB12A8EA8F + +pzZw5s3zDVHYdejZxdTpaRx00Yd1grbJe6mIJGvZRB0Jm0hKXoOX71PUI814mc5+ +a5pzbyO98aciL/Eat5m3P692WQ0yOPMuphRnklsM3s4qrCjp2aRRbWvbyV/QV9bp +Xz2yYvNqU3WJC3UJIaFFMvRo/lC/Wsz9OvHSSl3LnsXXhiOCeaE32etoOYdlk9ro +N9NqDdaw28t9//iiHhuQK4afK6TZkU6DatFljJHILCC416Xh9+DDK9E+CDKzmlcw +jSwtzgFKEhgrT0XKoZR9LJZDolT1YpFy7M3cFRYIuYvJfuLcjxVEldJE900QlaJS +ybb6RxV6SRVwQYXTbIClcXes+oNJMv59DivAfajxECQC5sAynW/FnY1sz0igmz6D +scclJuJIbawqiuV/Lv6bvgzMa/ZXL4b9JeJPuQELa7tCpvj4fpNk1IiftYISlwoT +iG5pL8yLhPL4/fxGnKJzUPCA9mbwiloW2cAZZxTd+Utqmhemcl9gF0JGNR2XeYwS +3obEjbkqMF0WR3AcVZU9B5d9SKUaAzTp4vu5yZtNVEIaiVlnI3hMwWMs2Jgahswo +QF9MCPsRYsxLs7/u4a4qoQ== +-----END DSA PRIVATE KEY----- diff --git a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java index b34cef592..b3a6789b7 100644 --- a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java +++ b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java @@ -21,23 +21,27 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.schmizz.sshj.common.*; import net.schmizz.sshj.common.Buffer.PlainBuffer; -import net.schmizz.sshj.transport.cipher.BlockCipher; import net.schmizz.sshj.transport.cipher.Cipher; import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.KeyFormat; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.mindrot.jbcrypt.BCrypt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PublicKey; +import java.security.*; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPrivateKeySpec; import java.util.Arrays; /** @@ -125,10 +129,12 @@ private PlainBuffer decryptBuffer(PlainBuffer privateKeyBuffer, String cipherNam private void initializeCipher(String kdfName, byte[] kdfOptions, Cipher cipher) throws Buffer.BufferException { if (kdfName.equals(BCRYPT)) { PlainBuffer opts = new PlainBuffer(kdfOptions); - CharBuffer charBuffer = CharBuffer.wrap(pwdf.reqPassword(null)); - ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer); - byte[] passphrase = Arrays.copyOfRange(byteBuffer.array(), - byteBuffer.position(), byteBuffer.limit()); + byte[] passphrase = new byte[0]; + if (pwdf != null) { + CharBuffer charBuffer = CharBuffer.wrap(pwdf.reqPassword(null)); + ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer); + passphrase = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); + } byte[] keyiv = new byte[48]; new BCrypt().pbkdf(passphrase, opts.readBytes(), opts.readUInt32AsInt(), keyiv); byte[] key = Arrays.copyOfRange(keyiv, 0, 32); @@ -183,13 +189,40 @@ private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey pub } // The private key section contains both the public key and the private key String keyType = keyBuffer.readString(); // string keytype - logger.info("Read key type: {}", keyType); - - byte[] pubKey = keyBuffer.readBytes(); // string publickey (again...) - keyBuffer.readUInt32(); - byte[] privKey = new byte[32]; - keyBuffer.readRawBytes(privKey); // string privatekey - keyBuffer.readRawBytes(new byte[32]); // string publickey (again...) + KeyType kt = KeyType.fromString(keyType); + logger.info("Read key type: {}", keyType, kt); + KeyPair kp; + switch (kt) { + case ED25519: + byte[] pubKey = keyBuffer.readBytes(); // string publickey (again...) + keyBuffer.readUInt32(); // length of privatekey+publickey + byte[] privKey = new byte[32]; + keyBuffer.readRawBytes(privKey); // string privatekey + keyBuffer.readRawBytes(new byte[32]); // string publickey (again...) + kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519")))); + break; + case RSA: + BigInteger n = keyBuffer.readMPInt(); // Modulus + BigInteger e = keyBuffer.readMPInt(); // Public Exponent + BigInteger d = keyBuffer.readMPInt(); // Private Exponent + keyBuffer.readMPInt(); // iqmp (q^-1 mod p) + keyBuffer.readMPInt(); // p (Prime 1) + keyBuffer.readMPInt(); // q (Prime 2) + kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory("RSA").generatePrivate(new RSAPrivateKeySpec(n, d))); + break; + case ECDSA256: + kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256")); + break; + case ECDSA384: + kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-384")); + break; + case ECDSA521: + kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-521")); + break; + + default: + throw new IOException("Cannot decode keytype " + keyType + " in openssh-key-v1 files (yet)."); + } String comment = keyBuffer.readString(); // string comment byte[] padding = new byte[keyBuffer.available()]; keyBuffer.readRawBytes(padding); // char[] padding @@ -198,6 +231,16 @@ private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey pub throw new IOException("Padding of key format contained wrong byte at position: " + i); } } - return new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519")))); + return kp; + } + + private PrivateKey createECDSAPrivateKey(KeyType kt, PlainBuffer buffer, String name) throws GeneralSecurityException, Buffer.BufferException { + PublicKey pk = kt.readPubKeyFromBuffer(buffer); // Public key + BigInteger s = new BigInteger(1, buffer.readBytes()); + X9ECParameters ecParams = NISTNamedCurves.getByName(name); + ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN()); + ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec); + return SecurityUtils.getKeyFactory("ECDSA").generatePrivate(pks); + } } diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java index cd003d446..dd94e73cd 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java @@ -89,7 +89,7 @@ private static String readHeader(Reader privateKey) throws IOException { private static KeyFormat keyFormatFromHeader(String header, boolean separatePubKey) { if (header.startsWith("-----BEGIN") && header.endsWith("PRIVATE KEY-----")) { - if (separatePubKey && header.contains(OpenSSHKeyV1KeyFile.OPENSSH_PRIVATE_KEY)) { + if (header.contains(OpenSSHKeyV1KeyFile.OPENSSH_PRIVATE_KEY)) { return KeyFormat.OpenSSHv1; } else if (separatePubKey) { // Can delay asking for password since have unencrypted pubkey diff --git a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java index 2a4628a06..eb84399ad 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java @@ -197,6 +197,22 @@ public void shouldLoadProtectedED25519PrivateKeyAes256CBC() throws IOException { checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256cbc.pem", "foobar"); } + @Test + public void shouldLoadRSAPrivateKeyAsOpenSSHV1() throws IOException { + OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); + keyFile.init(new File("src/test/resources/keyformats/rsa_opensshv1")); + PrivateKey aPrivate = keyFile.getPrivate(); + assertThat(aPrivate.getAlgorithm(), equalTo("RSA")); + } + + @Test + public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException { + OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); + keyFile.init(new File("src/test/resources/keyformats/ecdsa_opensshv1")); + PrivateKey aPrivate = keyFile.getPrivate(); + assertThat(aPrivate.getAlgorithm(), equalTo("ECDSA")); + } + private void checkOpenSSHKeyV1(String key, final String password) throws IOException { OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); keyFile.init(new File(key), new PasswordFinder() { diff --git a/src/test/resources/keyformats/ecdsa_opensshv1 b/src/test/resources/keyformats/ecdsa_opensshv1 new file mode 100644 index 000000000..85b41b2cb --- /dev/null +++ b/src/test/resources/keyformats/ecdsa_opensshv1 @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR0ImZtMAW5iPIKIQPzfYq9TvnoIpC+ +kvRY2UvBh28eK0xyNVfr218cdjvWxVrXqdTxW+IqMLWZMX+oL0YxpC+jAAAAsD+6Oow/uj +qMAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8goh +A/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6 +MAAAAgXNC11pInVAOd3xNphiHMoISeitf6h1IKbDM+niLrL5kAAAAXYWp2YW5lcnBASGVp +bWRhbGwubG9jYWwB +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/resources/keyformats/ecdsa_opensshv1.pub b/src/test/resources/keyformats/ecdsa_opensshv1.pub new file mode 100644 index 000000000..e9f29ff5f --- /dev/null +++ b/src/test/resources/keyformats/ecdsa_opensshv1.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8gohA/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6M= ajvanerp@Heimdall.local diff --git a/src/test/resources/keyformats/rsa_opensshv1 b/src/test/resources/keyformats/rsa_opensshv1 new file mode 100644 index 000000000..7263a28ce --- /dev/null +++ b/src/test/resources/keyformats/rsa_opensshv1 @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/resources/keyformats/rsa_opensshv1.pub b/src/test/resources/keyformats/rsa_opensshv1.pub new file mode 100644 index 000000000..67a8844e7 --- /dev/null +++ b/src/test/resources/keyformats/rsa_opensshv1.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local From 20223d3614881d550e7bbccbd40c9f0d43fdb056 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Thu, 24 Jan 2019 13:19:02 +0100 Subject: [PATCH 18/19] Added release notes --- README.adoc | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/README.adoc b/README.adoc index 9691e6446..f49a72f95 100644 --- a/README.adoc +++ b/README.adoc @@ -10,7 +10,6 @@ image:https://api.codacy.com/project/badge/Grade/14a0a316bb9149739b5ea26dbfa8da8 image:https://codecov.io/gh/hierynomus/sshj/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/hierynomus/sshj"] image:http://www.javadoc.io/badge/com.hierynomus/sshj.svg?color=blue["JavaDocs", link="http://www.javadoc.io/doc/com.hierynomus/sshj"] image:https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj/badge.svg["Maven Central",link="https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj"] -image:https://javadoc-emblem.rhcloud.com/doc/com.hierynomus/sshj/badge.svg["Javadoc",link="http://www.javadoc.io/doc/com.hierynomus/sshj"] To get started, have a look at one of the examples. Hopefully you will find the API pleasant to work with :) @@ -102,16 +101,31 @@ Java 6+. http://www.slf4j.org/download.html[slf4j] is required. http://www.bounc == Reporting bugs Issue tracker: https://github.com/hierynomus/sshj/issues -== Discussion -Google Group: http://groups.google.com/group/sshj-users - == Contributing Fork away! == Release history -SSHJ 0.24.0 (2018-??-??):: +SSHJ 0.27.0 (2019-01-24):: +* Fixed https://github.com/hierynomus/sshj/issues/415[#415]: Fixed wrongly prefixed '/' to path in SFTPClient.mkdirs +* Added support for ETM (Encrypt-then-Mac) MAC algorithms. +* Fixed https://github.com/hierynomus/sshj/issues/454[#454]: Added missing capacity check for Buffer.putUint64 +* Fixed https://github.com/hierynomus/sshj/issues/466[#466]: Added lock timeout for remote action to prevent hanging +* Fixed https://github.com/hierynomus/sshj/issues/470[#470]: Made EdDSA the default (first) signature factory +* Fixed https://github.com/hierynomus/sshj/issues/467[#467]: Added AES256-CBC as cipher mode in openssh-key-v1 support +* Fixed https://github.com/hierynomus/sshj/issues/464[#464]: Enabled curve25519-sha256@openssh.org in DefaultConfig +* Fixed https://github.com/hierynomus/sshj/issues/472[#472]: Handle server initiated global requests +* Fixed https://github.com/hierynomus/sshj/issues/485[#485]: Added support for all keytypes to openssh-key-v1 keyfiles. +SSHJ 0.26.0 (2018-07-24):: +* Fixed https://github.com/hierynomus/sshj/issues/413[#413]: Use UTF-8 for PrivateKeyFileResource +* Fixed https://github.com/hierynomus/sshj/issues/427[#427]: Support encrypted ed25519 openssh-key-v1 files +* Upgraded BouncyCastle to 1.60 +* Added support for hmac-ripemd160@openssh.com MAC +SSHJ 0.24.0 (2018-04-04):: * Added support for hmac-ripemd160 - +* Fixed https://github.com/hierynomus/sshj/issues/382[#382]: Fixed escaping in WildcardHostmatcher +* Added integration testsuite using Docker against OpenSSH +* Fixed https://github.com/hierynomus/sshj/issues/187[#187]: Fixed length bug in Buffer.putString +* Fixed https://github.com/hierynomus/sshj/issues/405[#405]: Continue host verification if first hostkey does not match. SSHJ 0.23.0 (2017-10-13):: * Merged https://github.com/hierynomus/sshj/pulls/372[#372]: Upgrade to 'net.i2p.crypto:eddsa:0.2.0' * Fixed https://github.com/hierynomus/sshj/issues/355[#355] and https://github.com/hierynomus/sshj/issues/354[#354]: Correctly decode signature bytes From 2f7b181306b076e262d566438ef6c145ade158e6 Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Thu, 24 Jan 2019 13:21:11 +0100 Subject: [PATCH 19/19] Release version: 0.27.0 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index f49a72f95..7f4f1a35b 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ = sshj - SSHv2 library for Java Jeroen van Erp :sshj_groupid: com.hierynomus -:sshj_version: 0.26.0 +:sshj_version: 0.27.0 :source-highlighter: pygments image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"]