Fixed itests for missing docker container (#892)

Migrated all tests to junit5

Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
Jeroen van Erp
2023-09-01 22:35:04 +02:00
committed by GitHub
parent 3069138482
commit a5fdb29fad
32 changed files with 1101 additions and 1054 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 "$@"

View File

@@ -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()
}
}

View File

@@ -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"
}

View File

@@ -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<Future<Exception>> futures = []
ExecutorService executorService = Executors.newCachedThreadPool()
for (int i in 0..20) {
futures.add(executorService.submit((Callable<Exception>) {
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
}
}

View File

@@ -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<String, KeyAlgorithms.Factory> 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()
}
}

View File

@@ -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<SshdContainer> {
/**
* 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<String> 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<String> 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());
}
}

View File

@@ -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()
}
}

View File

@@ -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",
]
}
}

View File

@@ -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",
]
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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<Arguments> 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());
});
}
}
}

View File

@@ -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<Future<Exception>> 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());
}
}
}

View File

@@ -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<Arguments> 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());
}
}
}

View File

@@ -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<Arguments> 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();
});
}
}

View File

@@ -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<SshdContainer> {
/**
* 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<DockerfileBuilder> {
private List<String> hostKeys = new ArrayList<>();
private List<String> 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<String> 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<String> 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<SshdContainer> consumer) throws Throwable {
SshdContainer sshdContainer = new SshdContainer(builder.buildInner());
sshdContainer.start();
try {
consumer.accept(sshdContainer);
} finally {
sshdContainer.stop();
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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());
}
});
}
}

View File

@@ -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<String> 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();
}
}

View File

@@ -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<KeyAlgorithms.Factory> 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());
}
}
}

View File

@@ -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<Factory.Named<Cipher>> 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> 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());
}
}
}

View File

@@ -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<Factory.Named<KeyExchange>> 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<KeyExchange> 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());
}
}
}

View File

@@ -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.Factory> 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());
}
}
}

View File

@@ -0,0 +1,34 @@
<!--
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.
-->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%.-20thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.apache.sshd.server" level="debug" />
<logger name="net.schmizz.sshj" level="debug"/>
<logger name="net.schmizz.sshj.transport" level="trace" />
</configuration>

View File

@@ -67,5 +67,10 @@ public class KeyAlgorithms {
public KeyAlgorithm create() {
return new BaseKeyAlgorithm(algorithmName, signatureFactory, keyType);
}
@Override
public String toString() {
return algorithmName;
}
}
}

View File

@@ -89,6 +89,11 @@ public class DHGroups {
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
}