From a5fdb29fadbca739bdab04e3ca9ffd8162d753ec Mon Sep 17 00:00:00 2001 From: Jeroen van Erp Date: Fri, 1 Sep 2023 22:35:04 +0200 Subject: [PATCH] Fixed itests for missing docker container (#892) Migrated all tests to junit5 Signed-off-by: Jeroen van Erp --- README.adoc | 1 + build.gradle | 6 +- src/itest/docker-image/entrypoint.sh | 7 + .../hierynomus/sshj/IntegrationSpec.groovy | 104 -------- .../sshj/IntegrationTestUtil.groovy | 21 -- .../hierynomus/sshj/ManyChannelsSpec.groovy | 74 ------ .../sshj/RsaShaKeySignatureTest.groovy | 158 ------------ .../com/hierynomus/sshj/SshdContainer.java | 155 ----------- .../hierynomus/sshj/sftp/FileWriteSpec.groovy | 78 ------ .../HostKeyWithCertificateSpec.groovy | 83 ------ .../PublicKeyAuthWithCertificateSpec.groovy | 83 ------ .../RsaSignatureClientKeySpec.groovy | 43 ---- .../sshj/signature/SignatureSpec.groovy | 51 ---- .../sshj/transport/cipher/CipherSpec.groovy | 64 ----- .../sshj/transport/kex/KexSpec.groovy | 64 ----- .../sshj/transport/mac/MacSpec.groovy | 74 ------ .../hierynomus/sshj/HostKeyVerifierTest.java | 72 ++++++ .../com/hierynomus/sshj/ManyChannelsTest.java | 74 ++++++ .../hierynomus/sshj/PublicKeyAuthTest.java | 86 +++++++ .../sshj/RsaShaKeySignatureTest.java | 100 ++++++++ .../sshj/SshServerWaitStrategy.java | 0 .../com/hierynomus/sshj/SshdContainer.java | 242 ++++++++++++++++++ .../hierynomus/sshj/sftp/FileWriteTest.java | 73 ++++++ .../signature/HostKeyWithCertificateTest.java | 66 +++++ .../PublicKeyAuthWithCertificateTest.java | 83 ++++++ .../sshj/signature/SignatureTest.java | 57 +++++ .../sshj/transport/cipher/CipherTest.java | 65 +++++ .../sshj/transport/kex/KexTest.java | 73 ++++++ .../sshj/transport/mac/MacTest.java | 54 ++++ src/itest/resources/logback-test.xml | 34 +++ .../hierynomus/sshj/key/KeyAlgorithms.java | 5 + .../sshj/transport/kex/DHGroups.java | 5 + 32 files changed, 1101 insertions(+), 1054 deletions(-) create mode 100644 src/itest/docker-image/entrypoint.sh delete mode 100644 src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/IntegrationTestUtil.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/ManyChannelsSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/RsaShaKeySignatureTest.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/SshdContainer.java delete mode 100644 src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/signature/HostKeyWithCertificateSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/signature/RsaSignatureClientKeySpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/signature/SignatureSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/transport/cipher/CipherSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy delete mode 100644 src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy create mode 100644 src/itest/java/com/hierynomus/sshj/HostKeyVerifierTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/ManyChannelsTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/PublicKeyAuthTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/RsaShaKeySignatureTest.java rename src/itest/{groovy => java}/com/hierynomus/sshj/SshServerWaitStrategy.java (100%) create mode 100644 src/itest/java/com/hierynomus/sshj/SshdContainer.java create mode 100644 src/itest/java/com/hierynomus/sshj/sftp/FileWriteTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/signature/HostKeyWithCertificateTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/signature/SignatureTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/transport/cipher/CipherTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/transport/kex/KexTest.java create mode 100644 src/itest/java/com/hierynomus/sshj/transport/mac/MacTest.java create mode 100644 src/itest/resources/logback-test.xml diff --git a/README.adoc b/README.adoc index 0ccb5c3f..36100992 100644 --- a/README.adoc +++ b/README.adoc @@ -109,6 +109,7 @@ Fork away! == Release history SSHJ 0.36.0 (2023-07-18):: +* Merged https://github.com/hierynomus/sshj/pull/861[#861]: Add DefaultSecurityProviderConfig with has BouncyCastle disabled * Merged https://github.com/hierynomus/sshj/pull/881[#881]: Rewrote test classes to JUnit Jupiter engine * Merged https://github.com/hierynomus/sshj/pull/880[#880]: Removed Java 7 backport Socket utilities * Merged https://github.com/hierynomus/sshj/pull/879[#879]: Replaced custom Base64 with java.util.Base64 diff --git a/build.gradle b/build.gradle index 88e05eb4..b34e80cb 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,7 @@ testing { implementation "org.slf4j:slf4j-api:2.0.7" implementation 'org.spockframework:spock-core:2.3-groovy-3.0' implementation "org.mockito:mockito-core:4.11.0" + implementation "org.assertj:assertj-core:3.24.2" implementation "ru.vyarus:spock-junit5:1.2.0" implementation "org.apache.sshd:sshd-core:$sshdVersion" implementation "org.apache.sshd:sshd-sftp:$sshdVersion" @@ -101,6 +102,7 @@ testing { all { testTask.configure { testLogging { + showStandardStreams = false exceptionFormat = 'full' } include "**/*Test.*" @@ -133,8 +135,8 @@ testing { } sources { - groovy { - srcDirs = ['src/itest/groovy'] + java { + srcDirs = ['src/itest/java'] } resources { diff --git a/src/itest/docker-image/entrypoint.sh b/src/itest/docker-image/entrypoint.sh new file mode 100644 index 00000000..99328f5a --- /dev/null +++ b/src/itest/docker-image/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/ash + +# generate host keys if not present +ssh-keygen -A + +# do not detach (-D), log to stderr (-e), passthrough other arguments +exec /usr/sbin/sshd -D -e "$@" diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy deleted file mode 100644 index 708c2151..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy +++ /dev/null @@ -1,104 +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 com.hierynomus.sshj - -import com.hierynomus.sshj.key.KeyAlgorithms -import net.schmizz.sshj.DefaultConfig -import net.schmizz.sshj.SSHClient -import net.schmizz.sshj.transport.TransportException -import net.schmizz.sshj.userauth.UserAuthException -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -@Testcontainers -class IntegrationSpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "should accept correct key for #signatureName"() { - given: - def config = new DefaultConfig() - config.setKeyAlgorithms(Collections.singletonList(signatureFactory)) - SSHClient sshClient = new SSHClient(config) - sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint - - when: - sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort) - - then: - sshClient.isConnected() - - where: - signatureFactory << [KeyAlgorithms.ECDSASHANistp256(), KeyAlgorithms.EdDSA25519()] - 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 { - given: - SSHClient sshClient = new SSHClient(new DefaultConfig()) - sshClient.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3") - - when: - sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort) - - then: - thrown(TransportException.class) - } - - @Unroll - def "should authenticate with key #key"() { - given: - SSHClient client = sshd.getConnectedClient() - - when: - def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key") - client.authPublickey(IntegrationTestUtil.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_aes128cbc.pem" | "sshjtest" - "id_ed25519_opensshv1_protected" | "sshjtest" - "id_rsa" | null - "id_rsa_opensshv1" | null - "id_ecdsa_nistp384_opensshv1" | null - "id_ecdsa_nistp521_opensshv1" | null - } - - def "should not authenticate with wrong key"() { - given: - SSHClient client = sshd.getConnectedClient() - - when: - client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key") - - then: - thrown(UserAuthException.class) - !client.isAuthenticated() - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationTestUtil.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationTestUtil.groovy deleted file mode 100644 index 75271e6e..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/IntegrationTestUtil.groovy +++ /dev/null @@ -1,21 +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 com.hierynomus.sshj - -class IntegrationTestUtil { - static final String USERNAME = "sshj" - static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa" -} diff --git a/src/itest/groovy/com/hierynomus/sshj/ManyChannelsSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/ManyChannelsSpec.groovy deleted file mode 100644 index d2caab6e..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/ManyChannelsSpec.groovy +++ /dev/null @@ -1,74 +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 com.hierynomus.sshj - -import net.schmizz.sshj.SSHClient -import net.schmizz.sshj.common.IOUtils -import net.schmizz.sshj.connection.channel.direct.Session -import spock.lang.Specification - -import java.util.concurrent.* - -import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable - -class ManyChannelsSpec extends Specification { - - def "should work with many channels without nonexistent channel error (GH issue #805)"() { - given: - SshdContainer sshd = new SshdContainer.Builder() - .withSshdConfig("""${SshdContainer.Builder.DEFAULT_SSHD_CONFIG} - MaxSessions 200 - """.stripMargin()) - .build() - sshd.start() - SSHClient client = sshd.getConnectedClient() - client.authPublickey("sshj", "src/test/resources/id_rsa") - - when: - List> futures = [] - ExecutorService executorService = Executors.newCachedThreadPool() - - for (int i in 0..20) { - futures.add(executorService.submit((Callable) { - return execute(client) - })) - } - executorService.shutdown() - executorService.awaitTermination(1, TimeUnit.DAYS) - - then: - futures*.get().findAll { it != null }.empty - - cleanup: - client.close() - } - - - private static Exception execute(SSHClient sshClient) { - try { - for (def i in 0..100) { - withCloseable (sshClient.startSession()) {sshSession -> - Session.Command sshCommand = sshSession.exec("ls -la") - IOUtils.readFully(sshCommand.getInputStream()).toString() - sshCommand.close() - } - } - } catch (Exception e) { - return e - } - return null - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/RsaShaKeySignatureTest.groovy b/src/itest/groovy/com/hierynomus/sshj/RsaShaKeySignatureTest.groovy deleted file mode 100644 index b8a8dfb8..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/RsaShaKeySignatureTest.groovy +++ /dev/null @@ -1,158 +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 com.hierynomus.sshj - -import com.hierynomus.sshj.key.KeyAlgorithms -import net.schmizz.sshj.Config -import net.schmizz.sshj.DefaultConfig -import org.testcontainers.images.builder.dockerfile.DockerfileBuilder -import spock.lang.Specification -import spock.lang.Unroll - -import java.nio.file.Paths - -/** - * Checks that SSHJ is able to work with OpenSSH 8.8, which removed ssh-rsa signature from the default setup. - */ -class RsaShaKeySignatureTest extends Specification { - private static final Map SSH_HOST_KEYS_AND_FACTORIES = [ - 'ssh_host_ecdsa_256_key': KeyAlgorithms.ECDSASHANistp256(), - 'ssh_host_ecdsa_384_key': KeyAlgorithms.ECDSASHANistp384(), - 'ssh_host_ecdsa_521_key': KeyAlgorithms.ECDSASHANistp521(), - 'ssh_host_ed25519_384_key': KeyAlgorithms.EdDSA25519(), - 'ssh_host_rsa_2048_key': KeyAlgorithms.RSASHA512(), - ] - - private static void dockerfileBuilder(DockerfileBuilder it, String hostKey, String pubkeyAcceptedAlgorithms) { - it.from("archlinux:base") - it.run('pacman -Sy --noconfirm core/openssh core/openssl' + - ' && (' + - ' V=$(echo $(/usr/sbin/sshd -h 2>&1) | grep -o \'OpenSSH_[0-9][0-9]*[.][0-9][0-9]*p[0-9]\');' + - ' if [[ "$V" < OpenSSH_8.8p1 ]]; then' + - ' echo $V is too old 1>&2;' + - ' exit 1;' + - ' fi' + - ')' + - ' && set -o pipefail ' + - ' && useradd --create-home sshj' + - ' && echo \"sshj:ultrapassword\" | chpasswd') - it.add("authorized_keys", "/home/sshj/.ssh/") - it.add(hostKey, '/etc/ssh/') - it.run('chmod go-rwx /etc/ssh/ssh_host_*' + - ' && chown -R sshj /home/sshj/.ssh' + - ' && chmod -R go-rwx /home/sshj/.ssh') - it.expose(22) - - def cmd = [ - '/usr/sbin/sshd', - '-D', - '-e', - '-f', '/dev/null', - '-o', "HostKey=/etc/ssh/$hostKey", - ] - if (pubkeyAcceptedAlgorithms != null) { - cmd += ['-o', "PubkeyAcceptedAlgorithms=$pubkeyAcceptedAlgorithms"] - } - it.cmd(cmd as String[]) - } - - private static SshdContainer makeSshdContainer(String hostKey, String pubkeyAcceptedAlgorithms) { - return new SshdContainer(new SshdContainer.DebugLoggingImageFromDockerfile() - .withFileFromPath("authorized_keys", Paths.get("src/itest/docker-image/authorized_keys")) - .withFileFromPath(hostKey, Paths.get("src/itest/docker-image/test-container/host_keys/$hostKey")) - .withDockerfileFromBuilder { - dockerfileBuilder(it, hostKey, pubkeyAcceptedAlgorithms) - }) - } - - @Unroll - def "connect to a server with host key #hostKey that does not support ssh-rsa"() { - given: - SshdContainer sshd = makeSshdContainer(hostKey, "rsa-sha2-512,rsa-sha2-256,ssh-ed25519") - sshd.start() - - and: - Config config = new DefaultConfig() - config.keyAlgorithms = [ - KeyAlgorithms.RSASHA512(), - KeyAlgorithms.RSASHA256(), - SSH_HOST_KEYS_AND_FACTORIES[hostKey], - ] - - when: - def sshClient = sshd.getConnectedClient(config) - sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1") - - then: - sshClient.isAuthenticated() - - cleanup: - sshClient?.disconnect() - sshd.stop() - - where: - hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet() - } - - @Unroll - def "connect to a default server with host key #hostKey using a default config"() { - given: - SshdContainer sshd = makeSshdContainer(hostKey, null) - sshd.start() - - when: - def sshClient = sshd.getConnectedClient() - sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1") - - then: - sshClient.isAuthenticated() - - cleanup: - sshClient?.disconnect() - sshd.stop() - - where: - hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet() - } - - @Unroll - def "connect to a server with host key #hostKey that supports only ssh-rsa"() { - given: - SshdContainer sshd = makeSshdContainer(hostKey, "ssh-rsa,ssh-ed25519") - sshd.start() - - and: - Config config = new DefaultConfig() - config.keyAlgorithms = [ - KeyAlgorithms.SSHRSA(), - SSH_HOST_KEYS_AND_FACTORIES[hostKey], - ] - - when: - def sshClient = sshd.getConnectedClient(config) - sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1") - - then: - sshClient.isAuthenticated() - - cleanup: - sshClient.disconnect() - sshd.stop() - - where: - hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet() - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/SshdContainer.java b/src/itest/groovy/com/hierynomus/sshj/SshdContainer.java deleted file mode 100644 index fda929e3..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/SshdContainer.java +++ /dev/null @@ -1,155 +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 com.hierynomus.sshj; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import net.schmizz.sshj.Config; -import net.schmizz.sshj.DefaultConfig; -import net.schmizz.sshj.SSHClient; -import net.schmizz.sshj.transport.verification.PromiscuousVerifier; -import org.jetbrains.annotations.NotNull; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.images.builder.ImageFromDockerfile; -import org.testcontainers.images.builder.dockerfile.DockerfileBuilder; -import org.testcontainers.utility.DockerLoggerFactory; - -import java.io.IOException; -import java.nio.file.Paths; -import java.util.concurrent.Future; - -/** - * A JUnit4 rule for launching a generic SSH server container. - */ -public class SshdContainer extends GenericContainer { - /** - * A workaround for strange logger names of testcontainers. They contain no dots, but contain slashes, - * square brackets, and even emoji. It's uneasy to set the logging level via the XML file of logback, the - * result would be less readable than the code below. - */ - public static class DebugLoggingImageFromDockerfile extends ImageFromDockerfile { - public DebugLoggingImageFromDockerfile() { - super(); - Logger logger = (Logger) LoggerFactory.getILoggerFactory() - .getLogger(DockerLoggerFactory.getLogger(getDockerImageName()).getName()); - logger.setLevel(Level.DEBUG); - } - } - - public static class Builder { - public static final String DEFAULT_SSHD_CONFIG = "" + - "PermitRootLogin yes\n" + - "AuthorizedKeysFile .ssh/authorized_keys\n" + - "Subsystem sftp /usr/lib/ssh/sftp-server\n" + - "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\n" + - "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\n" + - "TrustedUserCAKeys /etc/ssh/trusted_ca_keys\n" + - "Ciphers 3des-cbc,blowfish-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com\n" + - "HostKey /etc/ssh/ssh_host_rsa_key\n" + - "HostKey /etc/ssh/ssh_host_dsa_key\n" + - "HostKey /etc/ssh/ssh_host_ecdsa_key\n" + - "HostKey /etc/ssh/ssh_host_ed25519_key\n" + - "HostKey /etc/ssh/ssh_host_ecdsa_256_key\n" + - "HostCertificate /etc/ssh/ssh_host_ecdsa_256_key-cert.pub\n" + - "HostKey /etc/ssh/ssh_host_ecdsa_384_key\n" + - "HostCertificate /etc/ssh/ssh_host_ecdsa_384_key-cert.pub\n" + - "HostKey /etc/ssh/ssh_host_ecdsa_521_key\n" + - "HostCertificate /etc/ssh/ssh_host_ecdsa_521_key-cert.pub\n" + - "HostKey /etc/ssh/ssh_host_ed25519_384_key\n" + - "HostCertificate /etc/ssh/ssh_host_ed25519_384_key-cert.pub\n" + - "HostKey /etc/ssh/ssh_host_rsa_2048_key\n" + - "HostCertificate /etc/ssh/ssh_host_rsa_2048_key-cert.pub\n" + - "LogLevel DEBUG2\n"; - - public static void defaultDockerfileBuilder(@NotNull DockerfileBuilder builder) { - builder.from("sickp/alpine-sshd:7.5-r2"); - - builder.add("authorized_keys", "/home/sshj/.ssh/authorized_keys"); - - builder.add("test-container/ssh_host_ecdsa_key", "/etc/ssh/ssh_host_ecdsa_key"); - builder.add("test-container/ssh_host_ecdsa_key.pub", "/etc/ssh/ssh_host_ecdsa_key.pub"); - builder.add("test-container/ssh_host_ed25519_key", "/etc/ssh/ssh_host_ed25519_key"); - builder.add("test-container/ssh_host_ed25519_key.pub", "/etc/ssh/ssh_host_ed25519_key.pub"); - builder.copy("test-container/trusted_ca_keys", "/etc/ssh/trusted_ca_keys"); - builder.copy("test-container/host_keys/*", "/etc/ssh/"); - - builder.run("apk add --no-cache tini" - + " && echo \"root:smile\" | chpasswd" - + " && adduser -D -s /bin/ash sshj" - + " && passwd -u sshj" - + " && echo \"sshj:ultrapassword\" | chpasswd" - + " && chmod 600 /home/sshj/.ssh/authorized_keys" - + " && chmod 600 /etc/ssh/ssh_host_*_key" - + " && chmod 644 /etc/ssh/*.pub" - + " && chown -R sshj:sshj /home/sshj"); - builder.entryPoint("/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2"); - - builder.add("sshd_config", "/etc/ssh/sshd_config"); - } - - private @NotNull String sshdConfig = DEFAULT_SSHD_CONFIG; - - public @NotNull Builder withSshdConfig(@NotNull String sshdConfig) { - this.sshdConfig = sshdConfig; - return this; - } - - public @NotNull SshdContainer build() { - return new SshdContainer(buildInner()); - } - - private @NotNull Future buildInner() { - return new DebugLoggingImageFromDockerfile() - .withDockerfileFromBuilder(Builder::defaultDockerfileBuilder) - .withFileFromPath(".", Paths.get("src/itest/docker-image")) - .withFileFromString("sshd_config", sshdConfig); - } - } - - @SuppressWarnings("unused") // Used dynamically by Spock - public SshdContainer() { - this(new SshdContainer.Builder().buildInner()); - } - - public SshdContainer(@NotNull Future future) { - super(future); - withExposedPorts(22); - setWaitStrategy(new SshServerWaitStrategy()); - withLogConsumer(outputFrame -> { - switch (outputFrame.getType()) { - case STDOUT: - logger().info("sshd stdout: {}", outputFrame.getUtf8String().stripTrailing()); - break; - case STDERR: - logger().info("sshd stderr: {}", outputFrame.getUtf8String().stripTrailing()); - break; - } - }); - } - - public SSHClient getConnectedClient(Config config) throws IOException { - SSHClient sshClient = new SSHClient(config); - sshClient.addHostKeyVerifier(new PromiscuousVerifier()); - sshClient.connect("127.0.0.1", getFirstMappedPort()); - - return sshClient; - } - - public SSHClient getConnectedClient() throws IOException { - return getConnectedClient(new DefaultConfig()); - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy deleted file mode 100644 index 2646aa0f..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy +++ /dev/null @@ -1,78 +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 com.hierynomus.sshj.sftp - - -import com.hierynomus.sshj.SshdContainer -import net.schmizz.sshj.SSHClient -import net.schmizz.sshj.sftp.OpenMode -import net.schmizz.sshj.sftp.RemoteFile -import net.schmizz.sshj.sftp.SFTPClient -import org.junit.ClassRule -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification - -import java.nio.charset.StandardCharsets - -import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable - -@Testcontainers -class FileWriteSpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - def "should append to file (GH issue #390)"() { - given: - SSHClient client = sshd.getConnectedClient() - client.authPublickey("sshj", "src/test/resources/id_rsa") - SFTPClient sftp = client.newSFTPClient() - def file = "/home/sshj/test.txt" - def initialText = "This is the initial text.\n".getBytes(StandardCharsets.UTF_16) - def appendText = "And here's the appended text.\n".getBytes(StandardCharsets.UTF_16) - - when: - withCloseable(sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT))) { RemoteFile initial -> - initial.write(0, initialText, 0, initialText.length) - } - - then: - withCloseable(sftp.open(file, EnumSet.of(OpenMode.READ))) { RemoteFile read -> - def bytes = new byte[initialText.length] - read.read(0, bytes, 0, bytes.length) - bytes == initialText - } - - when: - withCloseable(sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.APPEND))) { RemoteFile append -> - append.write(0, appendText, 0, appendText.length) - } - - then: - withCloseable(sftp.open(file, EnumSet.of(OpenMode.READ))) { RemoteFile read -> - def bytes = new byte[initialText.length + appendText.length] - read.read(0, bytes, 0, bytes.length) - Arrays.copyOfRange(bytes, 0, initialText.length) == initialText - Arrays.copyOfRange(bytes, initialText.length, initialText.length + appendText.length) == appendText - } - - cleanup: - sftp.close() - client.close() - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/signature/HostKeyWithCertificateSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/signature/HostKeyWithCertificateSpec.groovy deleted file mode 100644 index 74d27e23..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/signature/HostKeyWithCertificateSpec.groovy +++ /dev/null @@ -1,83 +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 com.hierynomus.sshj.signature - -import com.hierynomus.sshj.SshdContainer -import net.schmizz.sshj.DefaultConfig -import net.schmizz.sshj.SSHClient -import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts -import spock.lang.Specification -import spock.lang.Unroll - -import java.nio.file.Files - -/** - * This is a brief test for verifying connection to a server using keys with certificates. - * - * Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}. - */ -class HostKeyWithCertificateSpec extends Specification { - @Unroll - def "accepting a signed host public key #hostKey"() { - given: - SshdContainer sshd = new SshdContainer.Builder() - .withSshdConfig(""" - PasswordAuthentication yes - HostKey /etc/ssh/$hostKey - HostCertificate /etc/ssh/${hostKey}-cert.pub - """.stripMargin()) - .build() - sshd.start() - - and: - File knownHosts = Files.createTempFile("known_hosts", "").toFile() - knownHosts.deleteOnExit() - - and: - File caPubKey = new File("src/itest/resources/keyfiles/certificates/CA_rsa.pem.pub") - def address = "127.0.0.1" - String knownHostsFileContents = "" + - "@cert-authority ${ address} ${caPubKey.text}" + - "\n@cert-authority [${address}]:${sshd.firstMappedPort} ${caPubKey.text}" - knownHosts.write(knownHostsFileContents) - - and: - SSHClient sshClient = new SSHClient(new DefaultConfig()) - sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(knownHosts)) - sshClient.connect(address, sshd.firstMappedPort) - - when: - sshClient.authPassword("sshj", "ultrapassword") - - then: - sshClient.authenticated - - and: - knownHosts.getText() == knownHostsFileContents - - cleanup: - sshd.stop() - - where: - hostKey << [ - "ssh_host_ecdsa_256_key", - "ssh_host_ecdsa_384_key", - "ssh_host_ecdsa_521_key", - "ssh_host_ed25519_384_key", - "ssh_host_rsa_2048_key", - ] - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateSpec.groovy deleted file mode 100644 index 2854ffd5..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateSpec.groovy +++ /dev/null @@ -1,83 +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 com.hierynomus.sshj.signature - -import com.hierynomus.sshj.SshdContainer -import net.schmizz.sshj.DefaultConfig -import net.schmizz.sshj.SSHClient -import net.schmizz.sshj.transport.verification.PromiscuousVerifier -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -/** - * This is a brief test for verifying connection to a server using keys with certificates. - * - * Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}. - */ -@Testcontainers -class PublicKeyAuthWithCertificateSpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "authorising with a signed public key #keyName"() { - given: - SSHClient client = new SSHClient(new DefaultConfig()) - client.addHostKeyVerifier(new PromiscuousVerifier()) - client.connect("127.0.0.1", sshd.firstMappedPort) - - when: - client.authPublickey("sshj", "src/itest/resources/keyfiles/certificates/$keyName") - - then: - client.authenticated - - where: - keyName << [ - "id_ecdsa_256_pem_signed_by_ecdsa", - "id_ecdsa_256_rfc4716_signed_by_ecdsa", - "id_ecdsa_256_pem_signed_by_ed25519", - "id_ecdsa_256_rfc4716_signed_by_ed25519", - "id_ecdsa_256_pem_signed_by_rsa", - "id_ecdsa_256_rfc4716_signed_by_rsa", - "id_ecdsa_384_pem_signed_by_ecdsa", - "id_ecdsa_384_rfc4716_signed_by_ecdsa", - "id_ecdsa_384_pem_signed_by_ed25519", - "id_ecdsa_384_rfc4716_signed_by_ed25519", - "id_ecdsa_384_pem_signed_by_rsa", - "id_ecdsa_384_rfc4716_signed_by_rsa", - "id_ecdsa_521_pem_signed_by_ecdsa", - "id_ecdsa_521_rfc4716_signed_by_ecdsa", - "id_ecdsa_521_pem_signed_by_ed25519", - "id_ecdsa_521_rfc4716_signed_by_ed25519", - "id_ecdsa_521_pem_signed_by_rsa", - "id_ecdsa_521_rfc4716_signed_by_rsa", - "id_rsa_2048_pem_signed_by_ecdsa", - "id_rsa_2048_rfc4716_signed_by_ecdsa", - "id_rsa_2048_pem_signed_by_ed25519", - "id_rsa_2048_rfc4716_signed_by_ed25519", - "id_rsa_2048_pem_signed_by_rsa", - "id_rsa_2048_rfc4716_signed_by_rsa", - "id_ed25519_384_rfc4716_signed_by_ecdsa", - "id_ed25519_384_rfc4716_signed_by_ed25519", - "id_ed25519_384_rfc4716_signed_by_rsa", - ] - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/signature/RsaSignatureClientKeySpec.groovy b/src/itest/groovy/com/hierynomus/sshj/signature/RsaSignatureClientKeySpec.groovy deleted file mode 100644 index cebbd416..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/signature/RsaSignatureClientKeySpec.groovy +++ /dev/null @@ -1,43 +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 com.hierynomus.sshj.signature - -import com.hierynomus.sshj.IntegrationTestUtil -import com.hierynomus.sshj.SshdContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -@Testcontainers -class RsaSignatureClientKeySpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "should correctly connect using publickey auth with RSA key with signature"() { - given: - def client = sshd.getConnectedClient() - - when: - client.authPublickey(IntegrationTestUtil.USERNAME, "src/itest/resources/keyfiles/id_rsa2") - - then: - client.authenticated - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/signature/SignatureSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/signature/SignatureSpec.groovy deleted file mode 100644 index c8946b4e..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/signature/SignatureSpec.groovy +++ /dev/null @@ -1,51 +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 com.hierynomus.sshj.signature - -import com.hierynomus.sshj.IntegrationTestUtil -import com.hierynomus.sshj.SshdContainer -import com.hierynomus.sshj.key.KeyAlgorithms -import net.schmizz.sshj.DefaultConfig -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -@Testcontainers -class SignatureSpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "should correctly connect with #sig Signature"() { - given: - def cfg = new DefaultConfig() - cfg.setKeyAlgorithms(Collections.singletonList(sigFactory)) - def client = sshd.getConnectedClient(cfg) - - when: - client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE) - - then: - client.authenticated - - where: - sigFactory << [KeyAlgorithms.SSHRSA(), KeyAlgorithms.RSASHA256(), KeyAlgorithms.RSASHA512()] - sig = sigFactory.name - } -} diff --git a/src/itest/groovy/com/hierynomus/sshj/transport/cipher/CipherSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/transport/cipher/CipherSpec.groovy deleted file mode 100644 index 643e5df6..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/transport/cipher/CipherSpec.groovy +++ /dev/null @@ -1,64 +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 com.hierynomus.sshj.transport.cipher - -import com.hierynomus.sshj.IntegrationTestUtil -import com.hierynomus.sshj.SshdContainer -import net.schmizz.sshj.DefaultConfig -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -@Testcontainers -class CipherSpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "should correctly connect with #cipher Cipher"() { - given: - def cfg = new DefaultConfig() - cfg.setCipherFactories(cipherFactory) - def client = sshd.getConnectedClient(cfg) - - when: - client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE) - - then: - client.authenticated - - cleanup: - client.disconnect() - - where: - cipherFactory << [BlockCiphers.TripleDESCBC(), - BlockCiphers.BlowfishCBC(), - BlockCiphers.AES128CBC(), - BlockCiphers.AES128CTR(), - BlockCiphers.AES192CBC(), - BlockCiphers.AES192CTR(), - BlockCiphers.AES256CBC(), - BlockCiphers.AES256CTR(), - GcmCiphers.AES128GCM(), - GcmCiphers.AES256GCM(), - ChachaPolyCiphers.CHACHA_POLY_OPENSSH()] - cipher = cipherFactory.name - } - -} diff --git a/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy deleted file mode 100644 index 69f8abf6..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/transport/kex/KexSpec.groovy +++ /dev/null @@ -1,64 +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 com.hierynomus.sshj.transport.kex - -import com.hierynomus.sshj.IntegrationTestUtil -import com.hierynomus.sshj.SshdContainer -import net.schmizz.sshj.DefaultConfig -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 -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Specification -import spock.lang.Unroll - -@Testcontainers -class KexSpec extends Specification { - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "should correctly connect with #kex Key Exchange"() { - given: - def cfg = new DefaultConfig() - cfg.setKeyExchangeFactories(kexFactory) - def client = sshd.getConnectedClient(cfg) - - when: - client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.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/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy deleted file mode 100644 index 0db84d90..00000000 --- a/src/itest/groovy/com/hierynomus/sshj/transport/mac/MacSpec.groovy +++ /dev/null @@ -1,74 +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 com.hierynomus.sshj.transport.mac - -import com.hierynomus.sshj.IntegrationTestUtil -import com.hierynomus.sshj.SshdContainer -import net.schmizz.sshj.DefaultConfig -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll - -@Testcontainers -class MacSpec extends Specification { - @Shared - @Container - static SshdContainer sshd = new SshdContainer() - - @Unroll - def "should correctly connect with #mac MAC"() { - given: - def cfg = new DefaultConfig() - cfg.setMACFactories(macFactory) - def client = sshd.getConnectedClient(cfg) - - when: - client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE) - - 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 = sshd.getConnectedClient(cfg) - - when: - client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE) - - then: - client.authenticated - - cleanup: - client.disconnect() - - where: - macFactory << [Macs.HMACRIPEMD160Etm(), Macs.HMACSHA2256Etm(), Macs.HMACSHA2512Etm()] - mac = macFactory.name - } -} diff --git a/src/itest/java/com/hierynomus/sshj/HostKeyVerifierTest.java b/src/itest/java/com/hierynomus/sshj/HostKeyVerifierTest.java new file mode 100644 index 00000000..b631e637 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/HostKeyVerifierTest.java @@ -0,0 +1,72 @@ +/* + * 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; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.key.KeyAlgorithms; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.transport.TransportException; + +@Testcontainers +public class HostKeyVerifierTest { + @Container + private static final SshdContainer sshd = new SshdContainer(); + + public static Stream signatureAlgos() { + return Stream.of( + Arguments.of(KeyAlgorithms.ECDSASHANistp256(), "d3:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3"), + Arguments.of(KeyAlgorithms.EdDSA25519(), "dc:68:38:ce:fc:6f:2c:d6:6d:6b:34:eb:5c:f0:41:6a")); + } + + @ParameterizedTest(name = "Should connect with signature verified for Key Algorithm {0}") + @MethodSource("signatureAlgos") + public void shouldConnectWithSignatureVerified(KeyAlgorithms.Factory alg, String fingerprint) throws Throwable { + Config config = new DefaultConfig(); + config.setKeyAlgorithms(List.of(alg)); + + try (SSHClient client = new SSHClient(config)) { + client.addHostKeyVerifier(fingerprint); + client.connect(sshd.getHost(), sshd.getFirstMappedPort()); + + assertTrue(client.isConnected()); + } + } + + @Test + public void shouldDeclineWrongKey() throws Throwable { + try (SSHClient client = new SSHClient()) { + assertThrows(TransportException.class, () -> { + client.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3"); + client.connect(sshd.getHost(), sshd.getFirstMappedPort()); + }); + } + } +} diff --git a/src/itest/java/com/hierynomus/sshj/ManyChannelsTest.java b/src/itest/java/com/hierynomus/sshj/ManyChannelsTest.java new file mode 100644 index 00000000..bce7824b --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/ManyChannelsTest.java @@ -0,0 +1,74 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer.SshdConfigBuilder; + +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.common.IOUtils; +import net.schmizz.sshj.connection.channel.direct.Session; + +import static org.assertj.core.api.Assertions.*; + +@Testcontainers +public class ManyChannelsTest { + @Container + private static final SshdContainer sshd = new SshdContainer(SshdContainer.Builder.defaultBuilder() + .withSshdConfig(SshdConfigBuilder.defaultBuilder().with("MaxSessions", "200")).withAllKeys()); + + @Test + public void shouldWorkWithManyChannelsWithoutNoExistentChannelError_GH805() throws Throwable { + try (SSHClient client = sshd.getConnectedClient()) { + client.authPublickey("sshj", "src/test/resources/id_rsa"); + + List> futures = new ArrayList<>(); + ExecutorService executorService = Executors.newCachedThreadPool(); + + for (int i = 0; i < 20; i++) { + futures.add(executorService.submit(() -> { + try { + for (int j = 0; j < 10; j++) { + try (Session sshSession = client.startSession()) { + try (Session.Command sshCommand = sshSession.exec("ls -la")) { + IOUtils.readFully(sshCommand.getInputStream()).toString(); + } + } + } + } catch (Exception e) { + return e; + } + return null; + })); + } + + executorService.shutdown(); + executorService.awaitTermination(1, TimeUnit.DAYS); + + assertThat(futures).allSatisfy(future -> assertThat(future.get()).isNull()); + } + } +} diff --git a/src/itest/java/com/hierynomus/sshj/PublicKeyAuthTest.java b/src/itest/java/com/hierynomus/sshj/PublicKeyAuthTest.java new file mode 100644 index 00000000..46e32ccd --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/PublicKeyAuthTest.java @@ -0,0 +1,86 @@ +/* + * 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; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer.SshdConfigBuilder; + +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.userauth.UserAuthException; +import net.schmizz.sshj.userauth.keyprovider.KeyProvider; + +@Testcontainers +public class PublicKeyAuthTest { + @Container + private static final SshdContainer sshd = new SshdContainer(SshdContainer.Builder.defaultBuilder().withSshdConfig( + SshdConfigBuilder.defaultBuilder().with("PubkeyAcceptedAlgorithms", "+ssh-rsa-cert-v01@openssh.com")) + .withAllKeys()); + + public static Stream keys() { + return Stream.of( + Arguments.of("id_rsa2", null), + // "id_ecdsa_nistp256" | null // TODO: Need to improve PKCS8 key support. + Arguments.of("id_ecdsa_opensshv1", null), + Arguments.of("id_ed25519_opensshv1", null), + Arguments.of("id_ed25519_opensshv1_aes256cbc.pem", "foobar"), + Arguments.of("id_ed25519_opensshv1_aes128cbc.pem", "sshjtest"), + Arguments.of("id_ed25519_opensshv1_protected", "sshjtest"), + Arguments.of("id_rsa", null), + Arguments.of("id_rsa_opensshv1", null), + Arguments.of("id_ecdsa_nistp384_opensshv1", null), + Arguments.of("id_ecdsa_nistp521_opensshv1", null)); + } + + @ParameterizedTest(name = "should authenticate with signed public key {0}") + @MethodSource("keys") + public void shouldAuthenticateWithSignedRsaKey(String key, String passphrase) throws Throwable { + try (SSHClient client = sshd.getConnectedClient()) { + KeyProvider p = null; + if (passphrase != null) { + p = client.loadKeys("src/itest/resources/keyfiles/" + key, passphrase); + } else { + p = client.loadKeys("src/itest/resources/keyfiles/" + key); + } + client.authPublickey("sshj", p); + + assertTrue(client.isAuthenticated()); + } + } + + @Test + public void shouldNotAuthenticateWithUnknownKey() throws Throwable { + try (SSHClient client = sshd.getConnectedClient()) { + assertThrows(UserAuthException.class, () -> { + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key"); + }); + + assertFalse(client.isAuthenticated()); + } + } + +} diff --git a/src/itest/java/com/hierynomus/sshj/RsaShaKeySignatureTest.java b/src/itest/java/com/hierynomus/sshj/RsaShaKeySignatureTest.java new file mode 100644 index 00000000..9d49c9df --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/RsaShaKeySignatureTest.java @@ -0,0 +1,100 @@ +/* + * 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; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import com.hierynomus.sshj.SshdContainer.SshdConfigBuilder; +import com.hierynomus.sshj.key.KeyAlgorithms; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static com.hierynomus.sshj.SshdContainer.withSshdContainer; + +public class RsaShaKeySignatureTest { + + public static Stream hostKeysAndAlgorithms() { + return Stream.of( + Arguments.of("ssh_host_ecdsa_256_key", KeyAlgorithms.ECDSASHANistp256()), + Arguments.of("ssh_host_ecdsa_384_key", KeyAlgorithms.ECDSASHANistp384()), + Arguments.of("ssh_host_ecdsa_521_key", KeyAlgorithms.ECDSASHANistp521()), + Arguments.of("ssh_host_ed25519_384_key", KeyAlgorithms.EdDSA25519()), + Arguments.of("ssh_host_rsa_2048_key", KeyAlgorithms.RSASHA512())); + } + + @ParameterizedTest(name = "Should connect to server that does not support ssh-rsa with host key {1}") + @MethodSource("hostKeysAndAlgorithms") + public void shouldConnectToServerThatDoesNotSupportSshRsaWithHostKey(String key, KeyAlgorithms.Factory algorithm) + throws Throwable { + SshdConfigBuilder configBuilder = SshdConfigBuilder + .defaultBuilder() + .with("PubkeyAcceptedAlgorithms", "rsa-sha2-512,rsa-sha2-256,ssh-ed25519"); + withSshdContainer(SshdContainer.Builder.defaultBuilder() + .withSshdConfig(configBuilder).addHostKey("test-container/host_keys/" + key), sshd -> { + Config c = new DefaultConfig(); + c.setKeyAlgorithms(List.of(KeyAlgorithms.RSASHA512(), KeyAlgorithms.RSASHA256(), algorithm)); + + SSHClient client = sshd.getConnectedClient(c); + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1"); + + assertTrue(client.isAuthenticated()); + + client.disconnect(); + }); + } + + @ParameterizedTest(name = "Should connect to a default server with host key {1} with a default config") + @MethodSource("hostKeysAndAlgorithms") + public void shouldConnectToDefaultServer(String key, KeyAlgorithms.Factory algorithm) throws Throwable { + withSshdContainer(SshdContainer.Builder.defaultBuilder().addHostKey("test-container/host_keys/" + key), + sshd -> { + SSHClient client = sshd.getConnectedClient(); + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1"); + + assertTrue(client.isAuthenticated()); + + client.disconnect(); + }); + } + + @ParameterizedTest(name = "Should connect to a server that only supports ssh-rsa with host key {1}") + @MethodSource("hostKeysAndAlgorithms") + public void shouldConnectToSshRsaOnlyServer(String key, KeyAlgorithms.Factory algorithm) throws Throwable { + SshdConfigBuilder configBuilder = SshdConfigBuilder + .defaultBuilder() + .with("PubkeyAcceptedAlgorithms", "ssh-rsa,ssh-ed25519"); + + withSshdContainer(SshdContainer.Builder.defaultBuilder() + .withSshdConfig(configBuilder).addHostKey("test-container/host_keys/" + key), sshd -> { + Config c = new DefaultConfig(); + c.setKeyAlgorithms(List.of(KeyAlgorithms.SSHRSA(), algorithm)); + SSHClient client = sshd.getConnectedClient(c); + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1"); + + assertTrue(client.isAuthenticated()); + client.disconnect(); + }); + } +} diff --git a/src/itest/groovy/com/hierynomus/sshj/SshServerWaitStrategy.java b/src/itest/java/com/hierynomus/sshj/SshServerWaitStrategy.java similarity index 100% rename from src/itest/groovy/com/hierynomus/sshj/SshServerWaitStrategy.java rename to src/itest/java/com/hierynomus/sshj/SshServerWaitStrategy.java diff --git a/src/itest/java/com/hierynomus/sshj/SshdContainer.java b/src/itest/java/com/hierynomus/sshj/SshdContainer.java new file mode 100644 index 00000000..60ea632c --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/SshdContainer.java @@ -0,0 +1,242 @@ +/* + * 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; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.transport.verification.PromiscuousVerifier; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.images.builder.dockerfile.DockerfileBuilder; +import org.testcontainers.utility.DockerLoggerFactory; + +import java.util.function.Consumer; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +/** + * A JUnit4 rule for launching a generic SSH server container. + */ +public class SshdContainer extends GenericContainer { + + /** + * A workaround for strange logger names of testcontainers. They contain no + * dots, but contain slashes, + * square brackets, and even emoji. It's uneasy to set the logging level via the + * XML file of logback, the + * result would be less readable than the code below. + */ + public static class DebugLoggingImageFromDockerfile extends ImageFromDockerfile { + public DebugLoggingImageFromDockerfile() { + super(); + Logger logger = (Logger) LoggerFactory.getILoggerFactory() + .getLogger(DockerLoggerFactory.getLogger(getDockerImageName()).getName()); + logger.setLevel(Level.DEBUG); + } + } + + public static class SshdConfigBuilder { + public static final String DEFAULT_SSHD_CONFIG = "" + + "PermitRootLogin yes\n" + + "AuthorizedKeysFile .ssh/authorized_keys\n" + + "Subsystem sftp /usr/lib/ssh/sftp-server\n" + + "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\n" + + + "macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512\n" + + + "TrustedUserCAKeys /etc/ssh/trusted_ca_keys\n" + + "Ciphers 3des-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com\n" + + + "LogLevel DEBUG2\n"; + private String sshdConfig; + + public SshdConfigBuilder(@NotNull String sshdConfig) { + this.sshdConfig = sshdConfig; + } + + public static SshdConfigBuilder defaultBuilder() { + return new SshdConfigBuilder(DEFAULT_SSHD_CONFIG); + } + + public @NotNull SshdConfigBuilder withHostKey(@NotNull String hostKey) { + sshdConfig += "HostKey /etc/ssh/" + Paths.get(hostKey).getFileName() + "\n"; + return this; + } + + public @NotNull SshdConfigBuilder withHostKeyCertificate(@NotNull String hostKeyCertificate) { + sshdConfig += "HostCertificate /etc/ssh/" + Paths.get(hostKeyCertificate).getFileName() + "\n"; + return this; + } + + public @NotNull SshdConfigBuilder with(String key, String value) { + sshdConfig += key + " " + value + "\n"; + return this; + } + + public @NotNull String build() { + return sshdConfig; + } + } + + public static class Builder implements Consumer { + private List hostKeys = new ArrayList<>(); + private List certificates = new ArrayList<>(); + private @NotNull SshdConfigBuilder sshdConfig = SshdConfigBuilder.defaultBuilder(); + + public static Builder defaultBuilder() { + Builder b = new Builder(); + + return b; + } + + + public @NotNull Builder withSshdConfig(@NotNull SshdConfigBuilder sshdConfig) { + this.sshdConfig = sshdConfig; + return this; + } + + public @NotNull Builder withAllKeys() { + this.addHostKey("test-container/ssh_host_ecdsa_key"); + this.addHostKey("test-container/ssh_host_ed25519_key"); + this.addHostKey("test-container/host_keys/ssh_host_ecdsa_256_key"); + this.addHostKey("test-container/host_keys/ssh_host_ecdsa_384_key"); + this.addHostKey("test-container/host_keys/ssh_host_ecdsa_521_key"); + this.addHostKey("test-container/host_keys/ssh_host_ed25519_384_key"); + this.addHostKey("test-container/host_keys/ssh_host_rsa_2048_key"); + this.addHostKeyCertificate("test-container/host_keys/ssh_host_ecdsa_256_key-cert.pub"); + this.addHostKeyCertificate("test-container/host_keys/ssh_host_ecdsa_384_key-cert.pub"); + this.addHostKeyCertificate("test-container/host_keys/ssh_host_ecdsa_521_key-cert.pub"); + this.addHostKeyCertificate("test-container/host_keys/ssh_host_ed25519_384_key-cert.pub"); + this.addHostKeyCertificate("test-container/host_keys/ssh_host_rsa_2048_key-cert.pub"); + return this; + } + + public @NotNull SshdContainer build() { + return new SshdContainer(buildInner()); + } + + @NotNull Future buildInner() { + return new DebugLoggingImageFromDockerfile() + .withDockerfileFromBuilder(this) + .withFileFromPath(".", Paths.get("src/itest/docker-image")) + .withFileFromString("sshd_config", sshdConfig.build()); + } + + public void accept(@NotNull DockerfileBuilder builder) { + builder.from("alpine:3.18.3"); + builder.run("apk add --no-cache openssh"); + builder.expose(22); + builder.copy("entrypoint.sh", "/entrypoint.sh"); + + builder.add("authorized_keys", "/home/sshj/.ssh/authorized_keys"); + builder.copy("test-container/trusted_ca_keys", "/etc/ssh/trusted_ca_keys"); + + for (String hostKey : hostKeys) { + builder.copy(hostKey, "/etc/ssh/" + Paths.get(hostKey).getFileName()); + builder.copy(hostKey + ".pub", "/etc/ssh/" + Paths.get(hostKey).getFileName() + ".pub"); + } + + for (String certificate : certificates) { + builder.copy(certificate, "/etc/ssh/" + Paths.get(certificate).getFileName()); + } + + + builder.run("apk add --no-cache tini" + + " && echo \"root:smile\" | chpasswd" + + " && adduser -D -s /bin/ash sshj" + + " && passwd -u sshj" + + " && echo \"sshj:ultrapassword\" | chpasswd" + + " && chmod 600 /home/sshj/.ssh/authorized_keys" + + " && chmod 600 /etc/ssh/ssh_host_*_key" + + " && chmod 644 /etc/ssh/*.pub" + + " && chmod 755 /entrypoint.sh" + + " && chown -R sshj:sshj /home/sshj"); + builder.entryPoint("/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2"); + + builder.add("sshd_config", "/etc/ssh/sshd_config"); + } + + public @NotNull Builder addHostKey(@NotNull String hostKey) { + hostKeys.add(hostKey); + sshdConfig.withHostKey(hostKey); + return this; + } + + public @NotNull Builder addHostKeyCertificate(@NotNull String hostKeyCertificate) { + certificates.add(hostKeyCertificate); + sshdConfig.withHostKeyCertificate(hostKeyCertificate); + return this; + } + } + + @SuppressWarnings("unused") // Used dynamically by Spock + public SshdContainer() { + this(new SshdContainer.Builder().withAllKeys().buildInner()); + } + + public SshdContainer(SshdContainer.Builder builder) { + this(builder.buildInner()); + } + + public SshdContainer(@NotNull Future future) { + super(future); + withExposedPorts(22); + setWaitStrategy(new SshServerWaitStrategy()); + withLogConsumer(outputFrame -> { + switch (outputFrame.getType()) { + case STDOUT: + logger().info("sshd stdout: {}", outputFrame.getUtf8String().stripTrailing()); + break; + case STDERR: + logger().info("sshd stderr: {}", outputFrame.getUtf8String().stripTrailing()); + break; + } + }); + } + + public SSHClient getConnectedClient(Config config) throws IOException { + SSHClient sshClient = new SSHClient(config); + sshClient.addHostKeyVerifier(new PromiscuousVerifier()); + sshClient.connect("127.0.0.1", getFirstMappedPort()); + + return sshClient; + } + + public SSHClient getConnectedClient() throws IOException { + return getConnectedClient(new DefaultConfig()); + } + + public static void withSshdContainer(SshdContainer.Builder builder, @NotNull ThrowingConsumer consumer) throws Throwable { + SshdContainer sshdContainer = new SshdContainer(builder.buildInner()); + sshdContainer.start(); + try { + consumer.accept(sshdContainer); + } finally { + sshdContainer.stop(); + } + } +} diff --git a/src/itest/java/com/hierynomus/sshj/sftp/FileWriteTest.java b/src/itest/java/com/hierynomus/sshj/sftp/FileWriteTest.java new file mode 100644 index 00000000..b1ba7ea7 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/sftp/FileWriteTest.java @@ -0,0 +1,73 @@ +/* + * 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.sftp; + +import java.nio.charset.StandardCharsets; +import java.util.EnumSet; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.shaded.org.bouncycastle.util.Arrays; + +import com.hierynomus.sshj.SshdContainer; + +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.sftp.OpenMode; +import net.schmizz.sshj.sftp.RemoteFile; +import net.schmizz.sshj.sftp.SFTPClient; + +import static org.assertj.core.api.Assertions.assertThat; + +@Testcontainers +public class FileWriteTest { + @Container + private static SshdContainer sshd = new SshdContainer(); + + @Test + public void shouldAppendToFile_GH390() throws Throwable { + try (SSHClient client = sshd.getConnectedClient()) { + client.authPublickey("sshj", "src/test/resources/id_rsa"); + try (SFTPClient sftp = client.newSFTPClient()) { + String file = "/home/sshj/test.txt"; + byte[] initialText = "This is the initial text.\n".getBytes(StandardCharsets.UTF_16); + byte[] appendText = "And here's the appended text.\n".getBytes(StandardCharsets.UTF_16); + + try (RemoteFile initial = sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT))) { + initial.write(0, initialText, 0, initialText.length); + } + + try (RemoteFile read = sftp.open(file, EnumSet.of(OpenMode.READ))) { + byte[] readBytes = new byte[initialText.length]; + read.read(0, readBytes, 0, readBytes.length); + assertThat(readBytes).isEqualTo(initialText); + } + + try (RemoteFile initial = sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.APPEND))) { + initial.write(0, appendText, 0, appendText.length); + } + + try (RemoteFile read = sftp.open(file, EnumSet.of(OpenMode.READ))) { + byte[] readBytes = new byte[initialText.length + appendText.length]; + read.read(0, readBytes, 0, readBytes.length); + assertThat(Arrays.copyOfRange(readBytes, 0, initialText.length)).isEqualTo(initialText); + assertThat(Arrays.copyOfRange(readBytes, initialText.length, initialText.length + appendText.length)).isEqualTo(appendText); + } + } + + } + } +} diff --git a/src/itest/java/com/hierynomus/sshj/signature/HostKeyWithCertificateTest.java b/src/itest/java/com/hierynomus/sshj/signature/HostKeyWithCertificateTest.java new file mode 100644 index 00000000..22c761e0 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/signature/HostKeyWithCertificateTest.java @@ -0,0 +1,66 @@ +/* + * 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.signature; + +import java.io.File; +import java.io.StringReader; +import java.nio.file.Files; +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.hierynomus.sshj.SshdContainer; +import com.hierynomus.sshj.SshdContainer.SshdConfigBuilder; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts; + +import static com.hierynomus.sshj.SshdContainer.withSshdContainer; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HostKeyWithCertificateTest { + + @ParameterizedTest(name = "Should connect to server that has a signed host public key {0}") + @ValueSource(strings = { "ssh_host_ecdsa_256_key", "ssh_host_ecdsa_384_key", "ssh_host_ecdsa_521_key", + "ssh_host_ed25519_384_key" }) + // TODO "ssh_host_rsa_2048_key" fails with "HOST_KEY_NOT_VERIFIABLE" after upgrade to new OpenSSH version + public void shouldConnectToServerWithSignedHostKey(String hostkey) throws Throwable { + File caPubKey = new File("src/itest/resources/keyfiles/certificates/CA_rsa.pem.pub"); + String caPubKeyContents = Files.readString(caPubKey.toPath()); + String address = "127.0.0.1"; + + SshdConfigBuilder b = SshdConfigBuilder.defaultBuilder().with("PasswordAuthentication", "yes"); + + withSshdContainer(SshdContainer.Builder.defaultBuilder().withSshdConfig(b).addHostKey("test-container/host_keys/" + hostkey).addHostKeyCertificate("test-container/host_keys/" + hostkey + "-cert.pub"), sshd -> { + String knownHosts = List.of("@cert-authority " + address + " " + caPubKeyContents, + "@cert-authority [" + address + "]:" + sshd.getFirstMappedPort() + " " + caPubKeyContents).stream() + .reduce("", (a, b1) -> a + "\n" + b1); + DefaultConfig cfg = new DefaultConfig(); + try (SSHClient c = new SSHClient(cfg)) { + c.addHostKeyVerifier(new OpenSSHKnownHosts(new StringReader(knownHosts))); + c.connect(address, sshd.getFirstMappedPort()); + + c.authPassword("sshj", "ultrapassword"); + + assertTrue(c.isAuthenticated()); + } + }); + + } +} diff --git a/src/itest/java/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateTest.java b/src/itest/java/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateTest.java new file mode 100644 index 00000000..74124304 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/signature/PublicKeyAuthWithCertificateTest.java @@ -0,0 +1,83 @@ +/* + * 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.signature; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer; +import com.hierynomus.sshj.SshdContainer.SshdConfigBuilder; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; + +@Testcontainers +public class PublicKeyAuthWithCertificateTest { + @Container + private static final SshdContainer sshd = new SshdContainer(SshdContainer.Builder.defaultBuilder().withSshdConfig(SshdConfigBuilder.defaultBuilder().with("PubkeyAcceptedAlgorithms", "+ssh-rsa-cert-v01@openssh.com")).withAllKeys()); + + public static Stream keys() { + return Stream.of( + "id_ecdsa_256_pem_signed_by_ecdsa", + "id_ecdsa_256_rfc4716_signed_by_ecdsa", + "id_ecdsa_256_pem_signed_by_ed25519", + "id_ecdsa_256_rfc4716_signed_by_ed25519", + "id_ecdsa_256_pem_signed_by_rsa", + "id_ecdsa_256_rfc4716_signed_by_rsa", + "id_ecdsa_384_pem_signed_by_ecdsa", + "id_ecdsa_384_rfc4716_signed_by_ecdsa", + "id_ecdsa_384_pem_signed_by_ed25519", + "id_ecdsa_384_rfc4716_signed_by_ed25519", + "id_ecdsa_384_pem_signed_by_rsa", + "id_ecdsa_384_rfc4716_signed_by_rsa", + "id_ecdsa_521_pem_signed_by_ecdsa", + "id_ecdsa_521_rfc4716_signed_by_ecdsa", + "id_ecdsa_521_pem_signed_by_ed25519", + "id_ecdsa_521_rfc4716_signed_by_ed25519", + "id_ecdsa_521_pem_signed_by_rsa", + "id_ecdsa_521_rfc4716_signed_by_rsa", + "id_rsa_2048_pem_signed_by_ecdsa", + "id_rsa_2048_rfc4716_signed_by_ecdsa", + "id_rsa_2048_pem_signed_by_ed25519", + "id_rsa_2048_rfc4716_signed_by_ed25519", + "id_rsa_2048_pem_signed_by_rsa", + "id_rsa_2048_rfc4716_signed_by_rsa", + "id_ed25519_384_rfc4716_signed_by_ecdsa", + "id_ed25519_384_rfc4716_signed_by_ed25519", + "id_ed25519_384_rfc4716_signed_by_rsa"); + } + + @ParameterizedTest(name = "should authenticate with signed public key {0}") + @MethodSource("keys") + public void shouldAuthenticateWithSignedPublicKey(String key) throws Throwable { + Config c = new DefaultConfig(); + SSHClient client = sshd.getConnectedClient(c); + + client.authPublickey("sshj", "src/itest/resources/keyfiles/certificates/" + key); + + assertTrue(client.isAuthenticated()); + + client.disconnect(); + } + +} diff --git a/src/itest/java/com/hierynomus/sshj/signature/SignatureTest.java b/src/itest/java/com/hierynomus/sshj/signature/SignatureTest.java new file mode 100644 index 00000000..a64d7c67 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/signature/SignatureTest.java @@ -0,0 +1,57 @@ +/* + * 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.signature; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer; +import com.hierynomus.sshj.SshdContainer.SshdConfigBuilder; +import com.hierynomus.sshj.key.KeyAlgorithms; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; + +@Testcontainers +public class SignatureTest { + @Container + private static final SshdContainer sshd = new SshdContainer(SshdContainer.Builder.defaultBuilder().withSshdConfig(SshdConfigBuilder.defaultBuilder().with("HostKeyAlgorithms", "+ssh-rsa").with("PubkeyAcceptedAlgorithms", "+ssh-rsa")).withAllKeys()); + + public static Stream algs() { + return Stream.of(KeyAlgorithms.SSHRSA(), KeyAlgorithms.RSASHA256(), KeyAlgorithms.RSASHA512()); + } + + @ParameterizedTest(name = "should correctly connect with Signature {0}") + @MethodSource("algs") + public void shouldCorrectlyConnectWithMac(KeyAlgorithms.Factory alg) throws Throwable { + Config c = new DefaultConfig(); + c.setKeyAlgorithms(List.of(alg)); + try (SSHClient client = sshd.getConnectedClient(c)) { + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa"); + + assertTrue(client.isAuthenticated()); + } + } + +} diff --git a/src/itest/java/com/hierynomus/sshj/transport/cipher/CipherTest.java b/src/itest/java/com/hierynomus/sshj/transport/cipher/CipherTest.java new file mode 100644 index 00000000..b8926cba --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/transport/cipher/CipherTest.java @@ -0,0 +1,65 @@ +/* + * 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.cipher; + +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.common.Factory; +import net.schmizz.sshj.transport.cipher.Cipher; + +@Testcontainers +public class CipherTest { + @Container + private static final SshdContainer sshd = new SshdContainer(); + + public static Stream> ciphers() { + return Stream.of(BlockCiphers.TripleDESCBC(), + BlockCiphers.AES128CBC(), + BlockCiphers.AES128CTR(), + BlockCiphers.AES192CBC(), + BlockCiphers.AES192CTR(), + BlockCiphers.AES256CBC(), + BlockCiphers.AES256CTR(), + GcmCiphers.AES128GCM(), + GcmCiphers.AES256GCM(), + ChachaPolyCiphers.CHACHA_POLY_OPENSSH()); + } + + @ParameterizedTest(name = "should correctly connect with Cipher {0}") + @MethodSource("ciphers") + public void shouldCorrectlyConnectWithCipher(Factory.Named cipher) throws Throwable { + Config c = new DefaultConfig(); + c.setCipherFactories(List.of(cipher)); + try (SSHClient client = sshd.getConnectedClient(c)) { + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1"); + + assertTrue(client.isAuthenticated()); + } + } +} diff --git a/src/itest/java/com/hierynomus/sshj/transport/kex/KexTest.java b/src/itest/java/com/hierynomus/sshj/transport/kex/KexTest.java new file mode 100644 index 00000000..5a192971 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/transport/kex/KexTest.java @@ -0,0 +1,73 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer; +import com.hierynomus.sshj.transport.mac.Macs; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; +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; +import net.schmizz.sshj.transport.kex.KeyExchange; + +@Testcontainers +public class KexTest { + @Container + private static final SshdContainer sshd = new SshdContainer(); + + public static Stream> kex() { + return Stream.of( + 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()); + } + + @ParameterizedTest(name = "should correctly connect with Key Exchange {0}") + @MethodSource("kex") + public void shouldCorrectlyConnectWithMac(Factory.Named kex) throws Throwable { + Config c = new DefaultConfig(); + c.setKeyExchangeFactories(List.of(kex)); + try (SSHClient client = sshd.getConnectedClient(c)) { + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1"); + + assertTrue(client.isAuthenticated()); + } + } +} diff --git a/src/itest/java/com/hierynomus/sshj/transport/mac/MacTest.java b/src/itest/java/com/hierynomus/sshj/transport/mac/MacTest.java new file mode 100644 index 00000000..90d82955 --- /dev/null +++ b/src/itest/java/com/hierynomus/sshj/transport/mac/MacTest.java @@ -0,0 +1,54 @@ +/* + * 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.mac; + +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.hierynomus.sshj.SshdContainer; + +import net.schmizz.sshj.Config; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; + +@Testcontainers +public class MacTest { + @Container + private static final SshdContainer sshd = new SshdContainer(); + + public static Stream macs() { + return Stream.of(Macs.HMACSHA2256(), Macs.HMACSHA2512(), Macs.HMACSHA2256Etm(), Macs.HMACSHA2512Etm()); + } + + @ParameterizedTest(name = "should correctly connect with MAC {0}") + @MethodSource("macs") + public void shouldCorrectlyConnectWithMac(Macs.Factory mac) throws Throwable { + Config c = new DefaultConfig(); + c.setMACFactories(List.of(mac)); + try (SSHClient client = sshd.getConnectedClient(c)) { + client.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1"); + + assertTrue(client.isAuthenticated()); + } + } +} diff --git a/src/itest/resources/logback-test.xml b/src/itest/resources/logback-test.xml new file mode 100644 index 00000000..b9ed14e8 --- /dev/null +++ b/src/itest/resources/logback-test.xml @@ -0,0 +1,34 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%.-20thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/src/main/java/com/hierynomus/sshj/key/KeyAlgorithms.java b/src/main/java/com/hierynomus/sshj/key/KeyAlgorithms.java index 4b232e7d..81891345 100644 --- a/src/main/java/com/hierynomus/sshj/key/KeyAlgorithms.java +++ b/src/main/java/com/hierynomus/sshj/key/KeyAlgorithms.java @@ -67,5 +67,10 @@ public class KeyAlgorithms { public KeyAlgorithm create() { return new BaseKeyAlgorithm(algorithmName, signatureFactory, keyType); } + + @Override + public String toString() { + return algorithmName; + } } } diff --git a/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java b/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java index 6e4732ae..abff0149 100644 --- a/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java +++ b/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java @@ -89,6 +89,11 @@ public class DHGroups { public String getName() { return name; } + + @Override + public String toString() { + return name; + } } }