Compare commits

..

2 Commits

Author SHA1 Message Date
Jeroen van Erp
d7dd73b9c8 Retry authentication with all remaining auth methods after partial success
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2022-09-24 10:02:07 +02:00
Jeroen van Erp
d628c47bae Upgrade Gradle
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2022-09-23 22:42:24 +02:00
122 changed files with 5625 additions and 2311 deletions

View File

@@ -6,20 +6,20 @@ name: Build SSHJ
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
java12:
name: Build with Java 11
name: Build with Java 12
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Java 11
uses: actions/setup-java@v3
- name: Set up JDK 12
uses: actions/setup-java@v1
with:
distribution: 'temurin'
java-version: 11
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
@@ -29,14 +29,14 @@ jobs:
integration:
name: Integration test
runs-on: ubuntu-latest
needs: [java12]
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- uses: actions/setup-java@v3
- uses: actions/setup-java@v1
with:
distribution: 'temurin'
java-version: 11
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle

View File

@@ -13,13 +13,12 @@ jobs:
name: Build with Java 12
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 12
uses: actions/setup-java@v2
uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
@@ -30,20 +29,21 @@ jobs:
needs: [java12]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v2
- uses: actions/setup-java@v1
with:
distribution: 'zulu'
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Release
run: ./gradlew clean publishToSonatype closeAndReleaseSonatypeStagingRepository
run: ./gradlew release -Prelease.disableChecks -Prelease.pushTagsOnly -Prelease.customUsername=${{ github.actor }} -Prelease.customPassword=${{ github.token }}
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNINGKEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNINGKEYID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNINGPASSWORD }}
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }}

View File

@@ -1,7 +1,7 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.36.0
:sshj_version: 0.32.0
:source-highlighter: pygments
image:https://github.com/hierynomus/sshj/actions/workflows/gradle.yml/badge.svg[link="https://github.com/hierynomus/sshj/actions/workflows/gradle.yml"]
@@ -95,11 +95,7 @@ If you need something that is not included, it shouldn't be too hard to add (do
http://ssh-comparison.quendi.de/comparison.html[SSH Implementation Comparison]
== Dependencies
- Java 8 or higher
- https://www.slf4j.org/[SLF4J 2.0.0]
- https://www.bouncycastle.org[Bouncy Castle]
Java 6+. http://www.slf4j.org/download.html[slf4j] is required. http://www.bouncycastle.org/java.html[bouncycastle] is highly recommended and required for using some of the crypto algorithms. http://www.jcraft.com/jzlib/[jzlib] is required for using zlib compression.
== Reporting bugs
Issue tracker: https://github.com/hierynomus/sshj/issues
@@ -108,24 +104,6 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
Fork away!
== Release history
SSHJ 0.36.0 (2023-09-04)::
* Rewrote Integration tests to JUnit5
* Merged https://github.com/hierynomus/sshj/pull/851[#851]: Fix race condition in key exchange causing intermittent SSH_MSG_UNIMPLEMENTED
* 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
* Merged https://github.com/hierynomus/sshj/pull/852[#852]: Removed unused bcrypt password hashing methods
* Merged https://github.com/hierynomus/sshj/pull/874[#874]: Java 8 minimum version + dependency upgrades
* Merged https://github.com/hierynomus/sshj/pull/876[#876]: Change `newStatefulSFTPClient` to return `StatefulSFTPClient`
* Merged https://github.com/hierynomus/sshj/pull/860[#860]: Upgrade to Gradle 7.6.1
* Merged https://github.com/hierynomus/sshj/pull/838[#838]: Replaced Curve25519 class with X25519 Key agreement
* Merged https://github.com/hierynomus/sshj/pull/772[#772]: Remove dependency on jzlib
SSHJ 0.35.0 (2023-01-30)::
* Merged https://github.com/hierynomus/sshj/pull/835[#835]: TimeoutException message improved
* Merged https://github.com/hierynomus/sshj/pull/815[#815]: Support authPassword on FreeBSD
* Merged https://github.com/hierynomus/sshj/pull/813[#813]: Prevent `CHANNEL_CLOSE` between isOpen and write call.
* Merged https://github.com/hierynomus/sshj/pull/811[#811]: Add `Transport.isKeyExchangeREquired` to prevent unnecessary KEXINIT
SSHJ 0.34.0 (2022-08-10)::
* Merged https://github.com/hierynomus/sshj/pull/743[#743]: Use default client credentials for AuthGssApiWithMic
* Merged https://github.com/hierynomus/sshj/pull/801[#801]: Restore thread interrupt status after catching InterruptedException

View File

@@ -23,6 +23,7 @@ dependencies {
compile "org.slf4j:slf4j-api:1.7.7"
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
compile "com.jcraft:jzlib:1.1.3"
testCompile "junit:junit:4.11"
testCompile "org.mockito:mockito-core:1.9.5"

View File

@@ -1,27 +1,25 @@
plugins {
id "java"
id "jvm-test-suite"
id "groovy"
id "jacoco"
id "com.github.blindpirate.osgi" version '0.0.6'
id "maven-publish"
id "signing"
id 'pl.allegro.tech.build.axion-release' version '1.15.3'
id 'pl.allegro.tech.build.axion-release' version '1.13.3'
id "com.bmuschko.docker-remote-api" version "7.1.0"
id "com.github.hierynomus.license" version "0.16.1"
id "com.bmuschko.docker-remote-api" version "9.2.1"
id 'ru.vyarus.github-info' version '1.5.0'
id "io.github.gradle-nexus.publish-plugin" version "1.3.0"
id 'ru.vyarus.github-info' version '1.2.0'
id "io.github.gradle-nexus.publish-plugin" version "1.0.0"
}
group = "com.hierynomus"
ext.moduleName = "${project.group}.${project.name}"
defaultTasks "build"
repositories {
mavenCentral()
}
group = "com.hierynomus"
defaultTasks ["build"]
ext.moduleName = "${project.group}.${project.name}"
scmVersion {
tag {
prefix = 'v'
@@ -35,21 +33,30 @@ scmVersion {
project.version = scmVersion.version
compileJava {
options.release = 8
}
configurations.implementation.transitive = false
def bouncycastleVersion = "1.75"
def sshdVersion = "2.10.0"
def bouncycastleVersion = "1.70"
def sshdVersion = "2.8.0"
dependencies {
implementation "org.slf4j:slf4j-api:2.0.7"
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.slf4j:slf4j-api:1.7.36"
implementation "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
implementation "com.jcraft:jzlib:1.1.3"
implementation "com.hierynomus:asn-one:0.6.0"
implementation "net.i2p.crypto:eddsa:0.3.0"
testImplementation "junit:junit:4.13.2"
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.4'
testImplementation "org.mockito:mockito-core:4.2.0"
testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion"
testImplementation "org.apache.sshd:sshd-scp:$sshdVersion"
testImplementation "ch.qos.logback:logback-classic:1.2.11"
testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.13'
testImplementation 'org.testcontainers:testcontainers:1.16.2'
}
license {
@@ -59,17 +66,13 @@ license {
java = 'SLASHSTAR_STYLE'
}
excludes([
'**/djb/Curve25519.java',
'**/sshj/common/Base64.java',
'**/com/hierynomus/sshj/userauth/keyprovider/bcrypt/*.java',
'**/files/test_file_*.txt',
])
}
java {
withJavadocJar()
withSourcesJar()
}
if (!JavaVersion.current().isJava9Compatible()) {
throw new GradleScriptException("Minimum compilation version is Java 9")
}
@@ -81,82 +84,10 @@ if (JavaVersion.current().isJava8Compatible()) {
}
}
testing {
suites {
configureEach {
useJUnitJupiter()
dependencies {
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"
implementation "org.apache.sshd:sshd-scp:$sshdVersion"
implementation "ch.qos.logback:logback-classic:1.3.8"
implementation 'org.glassfish.grizzly:grizzly-http-server:3.0.1'
}
targets {
all {
testTask.configure {
testLogging {
showStandardStreams = false
exceptionFormat = 'full'
}
include "**/*Test.*"
include "**/*Spec.*"
afterSuite { descriptor, result ->
def indicator = "\u001B[32m✓\u001b[0m"
if (result.failedTestCount > 0) {
indicator = "\u001B[31m✘\u001b[0m"
}
logger.lifecycle("$indicator Test ${descriptor.name}; Executed: ${result.testCount}/\u001B[32m${result.successfulTestCount}\u001B[0m/\u001B[31m${result.failedTestCount}\u001B[0m")
}
}
}
}
}
test {
sources {
groovy {
srcDirs = ['src/test/groovy']
}
}
}
integrationTest(JvmTestSuite) {
dependencies {
implementation project()
implementation 'org.testcontainers:testcontainers:1.18.3'
implementation 'org.testcontainers:junit-jupiter:1.18.3'
}
sources {
java {
srcDirs = ['src/itest/java']
}
resources {
srcDirs = ['src/itest/resources']
}
}
targets {
all {
testTask.configure {
shouldRunAfter(test)
}
}
}
}
}
compileJava {
options.compilerArgs.addAll(['--release', '7'])
}
project.tasks.compileGroovy.onlyIf { false }
task writeSshjVersionProperties {
doLast {
project.file("${project.buildDir}/resources/main").mkdirs()
@@ -190,6 +121,12 @@ jar {
}
}
java {
withJavadocJar()
withSourcesJar()
}
sourcesJar {
manifest {
attributes(
@@ -203,6 +140,53 @@ sourcesJar {
}
}
configurations {
integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
}
sourceSets {
integrationTest {
groovy {
compileClasspath += sourceSets.main.output + sourceSets.test.output
runtimeClasspath += sourceSets.main.output + sourceSets.test.output
srcDir file('src/itest/groovy')
}
resources.srcDir file('src/itest/resources')
}
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}
tasks.withType(Test) {
testLogging {
exceptionFormat = 'full'
}
include "**/*Test.*"
include "**/*Spec.*"
if (!project.hasProperty("allTests")) {
useJUnit {
excludeCategories 'com.hierynomus.sshj.test.SlowTests'
excludeCategories 'com.hierynomus.sshj.test.KnownFailingTests'
}
}
afterSuite { descriptor, result ->
if (descriptor.className != null) {
def indicator = "\u001B[32m✓\u001b[0m"
if (result.failedTestCount > 0) {
indicator = "\u001B[31m✘\u001b[0m"
}
logger.lifecycle("$indicator Test ${descriptor.name}; Executed: ${result.testCount}/\u001B[32m${result.successfulTestCount}\u001B[0m/\u001B[31m${result.failedTestCount}\u001B[0m")
}
}
}
project.tasks.compileGroovy.onlyIf { false }
github {
user 'hierynomus'
license 'Apache'
@@ -288,11 +272,17 @@ nexusPublishing {
jacocoTestReport {
reports {
xml.required = true
html.required = true
xml.enabled true
html.enabled true
}
}
task forkedUploadRelease(type: GradleBuild) {
buildFile = project.buildFile
tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
}
project.tasks.release.dependsOn([project.tasks.integrationTest, project.tasks.build])
project.tasks.release.finalizedBy(project.tasks.forkedUploadRelease)
project.tasks.jacocoTestReport.dependsOn(project.tasks.test)
project.tasks.check.dependsOn(project.tasks.jacocoTestReport)

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,7 +0,0 @@
#!/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

@@ -0,0 +1,102 @@
/*
* 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.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class IntegrationSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -13,16 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
package com.hierynomus.sshj
import net.schmizz.sshj.common.SecurityUtils;
/**
* SSHJ Configuration that uses the default Security Provider configuration from java.security and disables Bouncy Castle registration
*/
public class DefaultSecurityProviderConfig extends DefaultConfig {
static {
// Disable Bouncy Castle Provider registration prior to invoking constructors
SecurityUtils.setRegisterBouncyCastle(false);
}
class IntegrationTestUtil {
static final String USERNAME = "sshj"
static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa"
}

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

@@ -0,0 +1,159 @@
/*
* 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('yes | pacman -Sy core/openssh' +
' && (' +
' 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', 'LogLevel=DEBUG2',
'-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

@@ -0,0 +1,155 @@
/*
* 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

@@ -0,0 +1,75 @@
/*
* 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.IntegrationTestUtil
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 spock.lang.Shared
import spock.lang.Specification
import java.nio.charset.StandardCharsets
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
class FileWriteSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
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

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

@@ -0,0 +1,81 @@
/*
* 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.junit.ClassRule
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}.
*/
class PublicKeyAuthWithCertificateSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -0,0 +1,41 @@
/*
* 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.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class RsaSignatureClientKeySpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -0,0 +1,49 @@
/*
* 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.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class SignatureSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -0,0 +1,62 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.cipher
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class CipherSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -0,0 +1,64 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class KexSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -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.transport.mac
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class MacSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@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

@@ -1,72 +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 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

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

@@ -1,86 +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 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

@@ -1,100 +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 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

@@ -1,242 +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.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

@@ -1,73 +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 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

@@ -1,66 +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 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

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

@@ -1,57 +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 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

@@ -1,65 +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 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

@@ -1,73 +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 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

@@ -1,54 +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 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

@@ -1,34 +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.
-->
<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

@@ -0,0 +1,78 @@
/*
* 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.backport;
import net.schmizz.sshj.common.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
public class Jdk7HttpProxySocket extends Socket {
private Proxy httpProxy = null;
public Jdk7HttpProxySocket(Proxy proxy) {
super(proxy.type() == Proxy.Type.HTTP ? Proxy.NO_PROXY : proxy);
if (proxy.type() == Proxy.Type.HTTP) {
this.httpProxy = proxy;
}
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
if (httpProxy != null) {
connectHttpProxy(endpoint, timeout);
} else {
super.connect(endpoint, timeout);
}
}
private void connectHttpProxy(SocketAddress endpoint, int timeout) throws IOException {
super.connect(httpProxy.address(), timeout);
if (!(endpoint instanceof InetSocketAddress)) {
throw new SocketException("Expected an InetSocketAddress to connect to, got: " + endpoint);
}
InetSocketAddress isa = (InetSocketAddress) endpoint;
String httpConnect = "CONNECT " + isa.getHostName() + ":" + isa.getPort() + " HTTP/1.0\n\n";
getOutputStream().write(httpConnect.getBytes(IOUtils.UTF8));
checkAndFlushProxyResponse();
}
private void checkAndFlushProxyResponse()throws IOException {
InputStream socketInput = getInputStream();
byte[] tmpBuffer = new byte[512];
int len = socketInput.read(tmpBuffer, 0, tmpBuffer.length);
if (len == 0) {
throw new SocketException("Empty response from proxy");
}
String proxyResponse = new String(tmpBuffer, 0, len, IOUtils.UTF8);
// Expecting HTTP/1.x 200 OK
if (proxyResponse.contains("200")) {
// Flush any outstanding message in buffer
if (socketInput.available() > 0) {
socketInput.skip(socketInput.available());
}
// Proxy Connect Successful
} else {
throw new SocketException("Fail to create Socket\nResponse was:" + proxyResponse);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.backport;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
public class Sockets {
/**
* Java 7 and up have Socket implemented as Closeable, whereas Java6 did not have this inheritance.
* @param socket The socket to wrap as Closeable
* @return The (potentially wrapped) Socket as a Closeable.
*/
public static Closeable asCloseable(final Socket socket) {
if (Closeable.class.isAssignableFrom(socket.getClass())) {
return Closeable.class.cast(socket);
} else {
return new Closeable() {
@Override
public void close() throws IOException {
socket.close();
}
};
}
}
}

View File

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

View File

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

View File

@@ -15,13 +15,13 @@
*/
package com.hierynomus.sshj.transport.verification;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.transport.mac.MAC;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Pattern;
@@ -85,12 +85,12 @@ public class KnownHostMatchers {
private String hashHost(String host) throws IOException {
sha1.init(getSaltyBytes());
return "|1|" + salt + "|" + Base64.getEncoder().encodeToString(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
}
private byte[] getSaltyBytes() {
private byte[] getSaltyBytes() throws IOException {
if (saltyBytes == null) {
saltyBytes = Base64.getDecoder().decode(salt);
saltyBytes = Base64.decode(salt);
}
return saltyBytes;
}

View File

@@ -15,6 +15,7 @@
*/
package com.hierynomus.sshj.userauth.keyprovider;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.KeyType;
@@ -23,7 +24,6 @@ import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.security.PublicKey;
import java.util.Base64;
public class OpenSSHKeyFileUtil {
private OpenSSHKeyFileUtil() {
@@ -56,7 +56,7 @@ public class OpenSSHKeyFileUtil {
if (parts.length >= 2) {
return new ParsedPubKey(
KeyType.fromString(parts[0]),
new Buffer.PlainBuffer(Base64.getDecoder().decode(parts[1])).readPublicKey()
new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey()
);
} else {
throw new IOException("Got line with only one column");

View File

@@ -21,13 +21,8 @@ import com.hierynomus.sshj.transport.cipher.BlockCiphers;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.common.Buffer.PlainBuffer;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
@@ -52,7 +47,6 @@ import java.security.*;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Arrays;
import java.util.Base64;
/**
* Reads a key file in the new OpenSSH format.
@@ -105,7 +99,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
}
String keyFile = readKeyFile(reader);
byte[] decode = Base64.getDecoder().decode(keyFile);
byte[] decode = Base64.decode(keyFile);
PlainBuffer keyBuffer = new PlainBuffer(decode);
return readDecodedKeyPair(keyBuffer);

View File

@@ -14,9 +14,11 @@
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
import java.io.UnsupportedEncodingException;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* BCrypt implements OpenBSD-style Blowfish password hashing using
@@ -28,6 +30,32 @@ import java.security.NoSuchAlgorithmException;
* based on Bruce Schneier's Blowfish cipher. The work factor of
* the algorithm is parameterised, so it can be increased as
* computers get faster.
* <p>
* Usage is really simple. To hash a password for the first time,
* call the hashpw method with a random salt, like this:
* <p>
* <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br>
* </code>
* <p>
* To check whether a plaintext password matches one that has been
* hashed previously, use the checkpw method:
* <p>
* <code>
* if (BCrypt.checkpw(candidate_password, stored_hash))<br>
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It matches");<br>
* else<br>
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br>
* </code>
* <p>
* The gensalt() method takes an optional parameter (log_rounds)
* that determines the computational complexity of the hashing:
* <p>
* <code>
* String strong_salt = BCrypt.gensalt(10)<br>
* String stronger_salt = BCrypt.gensalt(12)<br>
* </code>
* <p>
* The amount of work increases exponentially (2**log_rounds), so
* each increment is twice as much work. The default log_rounds is
* 10, and the valid range is 4 to 30.
@@ -36,18 +64,22 @@ import java.security.NoSuchAlgorithmException;
* @version 0.2
*/
public class BCrypt {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int[] P_orig = {
private static final int P_orig[] = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static final int[] S_orig = {
private static final int S_orig[] = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
@@ -312,9 +344,149 @@ public class BCrypt {
0x66697368, 0x53776174, 0x44796e61, 0x6d697465,
};
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
// this "ciphertext", but it is really plaintext or an IV. We keep
// the name to make code comparison easier.
static private final int bf_crypt_ciphertext[] = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
static private final char base64_code[] = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
static private final byte index_64[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Expanded Blowfish key
private int[] P;
private int[] S;
private int P[];
private int S[];
/**
* Encode a byte array using bcrypt's slightly-modified base64
* encoding scheme. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
*
* @param d the byte array to encode
* @param len the number of bytes to encode
* @return base64-encoded string
* @exception IllegalArgumentException if the length is invalid
*/
private static String encode_base64(byte d[], int len)
throws IllegalArgumentException {
int off = 0;
StringBuffer rs = new StringBuffer();
int c1, c2;
if (len <= 0 || len > d.length)
throw new IllegalArgumentException ("Invalid len");
while (off < len) {
c1 = d[off++] & 0xff;
rs.append(base64_code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.append(base64_code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.append(base64_code[c1 & 0x3f]);
rs.append(base64_code[c2 & 0x3f]);
}
return rs.toString();
}
/**
* Look up the 3 bits base64-encoded by the specified character,
* range-checking againt conversion table
* @param x the base64-encoded value
* @return the decoded value of x
*/
private static byte char64(char x) {
if ((int)x < 0 || (int)x > index_64.length)
return -1;
return index_64[(int)x];
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a
* byte array. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
* @param s the string to decode
* @param maxolen the maximum number of bytes to decode
* @return an array containing the decoded bytes
* @throws IllegalArgumentException if maxolen is invalid
*/
private static byte[] decode_base64(String s, int maxolen)
throws IllegalArgumentException {
StringBuffer rs = new StringBuffer();
int off = 0, slen = s.length(), olen = 0;
byte ret[];
byte c1, c2, c3, c4, o;
if (maxolen <= 0)
throw new IllegalArgumentException ("Invalid maxolen");
while (off < slen - 1 && olen < maxolen) {
c1 = char64(s.charAt(off++));
c2 = char64(s.charAt(off++));
if (c1 == -1 || c2 == -1)
break;
o = (byte)(c1 << 2);
o |= (c2 & 0x30) >> 4;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c3 = char64(s.charAt(off++));
if (c3 == -1)
break;
o = (byte)((c2 & 0x0f) << 4);
o |= (c3 & 0x3c) >> 2;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c4 = char64(s.charAt(off++));
o = (byte)((c3 & 0x03) << 6);
o |= c4;
rs.append((char)o);
++olen;
}
ret = new byte[olen];
for (off = 0; off < olen; off++)
ret[off] = (byte)rs.charAt(off);
return ret;
}
/**
* Blowfish encipher a single 64-bit block encoded as
@@ -322,7 +494,7 @@ public class BCrypt {
* @param lr an array containing the two 32-bit half blocks
* @param off the position in the array of the blocks
*/
private void encipher(int[] lr, int off) {
private final void encipher(int lr[], int off) {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
@@ -352,7 +524,7 @@ public class BCrypt {
* current offset into data
* @return the next word of material from data
*/
private static int streamtoword(byte[] data, int[] offp) {
private static int streamtoword(byte data[], int offp[]) {
int i;
int word = 0;
int off = offp[0];
@@ -370,18 +542,18 @@ public class BCrypt {
* Initialise the Blowfish key schedule
*/
private void init_key() {
P = P_orig.clone();
S = S_orig.clone();
P = (int[])P_orig.clone();
S = (int[])S_orig.clone();
}
/**
* Key the Blowfish cipher
* @param key an array containing the key
*/
private void key(byte[] key) {
private void key(byte key[]) {
int i;
int[] koffp = { 0 };
int[] lr = { 0, 0 };
int koffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
@@ -407,10 +579,10 @@ public class BCrypt {
* @param data salt information
* @param key password information
*/
private void ekskey(byte[] data, byte[] key) {
private void ekskey(byte data[], byte key[]) {
int i;
int[] koffp = { 0 }, doffp = { 0 };
int[] lr = { 0, 0 };
int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
@@ -515,4 +687,183 @@ public class BCrypt {
throw new RuntimeException(e);
}
}
/**
* Perform the central password hashing step in the
* bcrypt scheme
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number
* of rounds of hashing to apply
* @param cdata the plaintext to encrypt
* @return an array containing the binary hashed password
*/
public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
int cdata[]) {
int rounds, i, j;
int clen = cdata.length;
byte ret[];
if (log_rounds < 4 || log_rounds > 30)
throw new IllegalArgumentException ("Bad number of rounds");
rounds = 1 << log_rounds;
if (salt.length != BCRYPT_SALT_LEN)
throw new IllegalArgumentException ("Bad salt length");
init_key();
ekskey(salt, password);
for (i = 0; i != rounds; i++) {
key(password);
key(salt);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++)
encipher(cdata, j << 1);
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
return ret;
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated
* using BCrypt.gensalt)
* @return the hashed password
*/
public static String hashpw(String password, String salt) {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
char minor = (char)0;
int rounds, off = 0;
StringBuffer rs = new StringBuffer();
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
throw new IllegalArgumentException ("Invalid salt version");
if (salt.charAt(2) == '$')
off = 3;
else {
minor = salt.charAt(2);
if (minor != 'a' || salt.charAt(3) != '$')
throw new IllegalArgumentException ("Invalid salt revision");
off = 4;
}
// Extract number of rounds
if (salt.charAt(off + 2) > '$')
throw new IllegalArgumentException ("Missing salt rounds");
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
}
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds,
(int[])bf_crypt_ciphertext.clone());
rs.append("$2");
if (minor >= 'a')
rs.append(minor);
rs.append("$");
if (rounds < 10)
rs.append("0");
if (rounds > 30) {
throw new IllegalArgumentException(
"rounds exceeds maximum (30)");
}
rs.append(Integer.toString(rounds));
rs.append("$");
rs.append(encode_base64(saltb, saltb.length));
rs.append(encode_base64(hashed,
bf_crypt_ciphertext.length * 4 - 1));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @param random an instance of SecureRandom to use
* @return an encoded salt value
*/
public static String gensalt(int log_rounds, SecureRandom random) {
StringBuffer rs = new StringBuffer();
byte rnd[] = new byte[BCRYPT_SALT_LEN];
random.nextBytes(rnd);
rs.append("$2a$");
if (log_rounds < 10)
rs.append("0");
if (log_rounds > 30) {
throw new IllegalArgumentException(
"log_rounds exceeds maximum (30)");
}
rs.append(Integer.toString(log_rounds));
rs.append("$");
rs.append(encode_base64(rnd, rnd.length));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @return an encoded salt value
*/
public static String gensalt(int log_rounds) {
return gensalt(log_rounds, new SecureRandom());
}
/**
* Generate a salt for use with the BCrypt.hashpw() method,
* selecting a reasonable default for the number of hashing
* rounds to apply
* @return an encoded salt value
*/
public static String gensalt() {
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
/**
* Check that a plaintext password matches a previously hashed
* one
* @param plaintext the plaintext password to verify
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
byte hashed_bytes[];
byte try_bytes[];
try {
String try_pw = hashpw(plaintext, hashed);
hashed_bytes = hashed.getBytes("UTF-8");
try_bytes = try_pw.getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
return false;
}
if (hashed_bytes.length != try_bytes.length)
return false;
byte ret = 0;
for (int i = 0; i < try_bytes.length; i++)
ret |= hashed_bytes[i] ^ try_bytes[i];
return ret == 0;
}
}

View File

@@ -0,0 +1,918 @@
/* Ported from C to Java by Dmitry Skiba [sahn0], 23/02/08.
* Original: http://cds.xs4all.nl:8081/ecdh/
*/
/* Generic 64-bit integer implementation of Curve25519 ECDH
* Written by Matthijs van Duin, 200608242056
* Public domain.
*
* Based on work by Daniel J Bernstein, http://cr.yp.to/ecdh.html
*/
package djb;
public class Curve25519 {
/* key size */
public static final int KEY_SIZE = 32;
/* 0 */
public static final byte[] ZERO = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/* the prime 2^255-19 */
public static final byte[] PRIME = {
(byte)237, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)255,
(byte)255, (byte)255, (byte)255, (byte)127
};
/* group order (a prime near 2^252+2^124) */
public static final byte[] ORDER = {
(byte)237, (byte)211, (byte)245, (byte)92,
(byte)26, (byte)99, (byte)18, (byte)88,
(byte)214, (byte)156, (byte)247, (byte)162,
(byte)222, (byte)249, (byte)222, (byte)20,
(byte)0, (byte)0, (byte)0, (byte)0,
(byte)0, (byte)0, (byte)0, (byte)0,
(byte)0, (byte)0, (byte)0, (byte)0,
(byte)0, (byte)0, (byte)0, (byte)16
};
/********* KEY AGREEMENT *********/
/* Private key clamping
* k [out] your private key for key agreement
* k [in] 32 random bytes
*/
public static final void clamp(byte[] k) {
k[31] &= 0x7F;
k[31] |= 0x40;
k[ 0] &= 0xF8;
}
/* Key-pair generation
* P [out] your public key
* s [out] your private key for signing
* k [out] your private key for key agreement
* k [in] 32 random bytes
* s may be NULL if you don't care
*
* WARNING: if s is not NULL, this function has data-dependent timing */
public static final void keygen(byte[] P, byte[] s, byte[] k) {
clamp(k);
core(P, s, k, null);
}
/* Key agreement
* Z [out] shared secret (needs hashing before use)
* k [in] your private key for key agreement
* P [in] peer's public key
*/
public static final void curve(byte[] Z, byte[] k, byte[] P) {
core(Z, null, k, P);
}
/********* DIGITAL SIGNATURES *********/
/* deterministic EC-KCDSA
*
* s is the private key for signing
* P is the corresponding public key
* Z is the context data (signer public key or certificate, etc)
*
* signing:
*
* m = hash(Z, message)
* x = hash(m, s)
* keygen25519(Y, NULL, x);
* r = hash(Y);
* h = m XOR r
* sign25519(v, h, x, s);
*
* output (v,r) as the signature
*
* verification:
*
* m = hash(Z, message);
* h = m XOR r
* verify25519(Y, v, h, P)
*
* confirm r == hash(Y)
*
* It would seem to me that it would be simpler to have the signer directly do
* h = hash(m, Y) and send that to the recipient instead of r, who can verify
* the signature by checking h == hash(m, Y). If there are any problems with
* such a scheme, please let me know.
*
* Also, EC-KCDSA (like most DS algorithms) picks x random, which is a waste of
* perfectly good entropy, but does allow Y to be calculated in advance of (or
* parallel to) hashing the message.
*/
/* Signature generation primitive, calculates (x-h)s mod q
* v [out] signature value
* h [in] signature hash (of message, signature pub key, and context data)
* x [in] signature private key
* s [in] private key for signing
* returns true on success, false on failure (use different x or h)
*/
public static final boolean sign(byte[] v, byte[] h, byte[] x, byte[] s) {
// v = (x - h) s mod q
int w, i;
byte[] h1 = new byte[32], x1 = new byte[32];
byte[] tmp1 = new byte[64];
byte[] tmp2 = new byte[64];
// Don't clobber the arguments, be nice!
cpy32(h1, h);
cpy32(x1, x);
// Reduce modulo group order
byte[] tmp3=new byte[32];
divmod(tmp3, h1, 32, ORDER, 32);
divmod(tmp3, x1, 32, ORDER, 32);
// v = x1 - h1
// If v is negative, add the group order to it to become positive.
// If v was already positive we don't have to worry about overflow
// when adding the order because v < ORDER and 2*ORDER < 2^256
mula_small(v, x1, 0, h1, 32, -1);
mula_small(v, v , 0, ORDER, 32, 1);
// tmp1 = (x-h)*s mod q
mula32(tmp1, v, s, 32, 1);
divmod(tmp2, tmp1, 64, ORDER, 32);
for (w = 0, i = 0; i < 32; i++)
w |= v[i] = tmp1[i];
return w != 0;
}
/* Signature verification primitive, calculates Y = vP + hG
* Y [out] signature public key
* v [in] signature value
* h [in] signature hash
* P [in] public key
*/
public static final void verify(byte[] Y, byte[] v, byte[] h, byte[] P) {
/* Y = v abs(P) + h G */
byte[] d=new byte[32];
long10[]
p=new long10[]{new long10(),new long10()},
s=new long10[]{new long10(),new long10()},
yx=new long10[]{new long10(),new long10(),new long10()},
yz=new long10[]{new long10(),new long10(),new long10()},
t1=new long10[]{new long10(),new long10(),new long10()},
t2=new long10[]{new long10(),new long10(),new long10()};
int vi = 0, hi = 0, di = 0, nvh=0, i, j, k;
/* set p[0] to G and p[1] to P */
set(p[0], 9);
unpack(p[1], P);
/* set s[0] to P+G and s[1] to P-G */
/* s[0] = (Py^2 + Gy^2 - 2 Py Gy)/(Px - Gx)^2 - Px - Gx - 486662 */
/* s[1] = (Py^2 + Gy^2 + 2 Py Gy)/(Px - Gx)^2 - Px - Gx - 486662 */
x_to_y2(t1[0], t2[0], p[1]); /* t2[0] = Py^2 */
sqrt(t1[0], t2[0]); /* t1[0] = Py or -Py */
j = is_negative(t1[0]); /* ... check which */
t2[0]._0 += 39420360; /* t2[0] = Py^2 + Gy^2 */
mul(t2[1], BASE_2Y, t1[0]);/* t2[1] = 2 Py Gy or -2 Py Gy */
sub(t1[j], t2[0], t2[1]); /* t1[0] = Py^2 + Gy^2 - 2 Py Gy */
add(t1[1-j], t2[0], t2[1]);/* t1[1] = Py^2 + Gy^2 + 2 Py Gy */
cpy(t2[0], p[1]); /* t2[0] = Px */
t2[0]._0 -= 9; /* t2[0] = Px - Gx */
sqr(t2[1], t2[0]); /* t2[1] = (Px - Gx)^2 */
recip(t2[0], t2[1], 0); /* t2[0] = 1/(Px - Gx)^2 */
mul(s[0], t1[0], t2[0]); /* s[0] = t1[0]/(Px - Gx)^2 */
sub(s[0], s[0], p[1]); /* s[0] = t1[0]/(Px - Gx)^2 - Px */
s[0]._0 -= 9 + 486662; /* s[0] = X(P+G) */
mul(s[1], t1[1], t2[0]); /* s[1] = t1[1]/(Px - Gx)^2 */
sub(s[1], s[1], p[1]); /* s[1] = t1[1]/(Px - Gx)^2 - Px */
s[1]._0 -= 9 + 486662; /* s[1] = X(P-G) */
mul_small(s[0], s[0], 1); /* reduce s[0] */
mul_small(s[1], s[1], 1); /* reduce s[1] */
/* prepare the chain */
for (i = 0; i < 32; i++) {
vi = (vi >> 8) ^ (v[i] & 0xFF) ^ ((v[i] & 0xFF) << 1);
hi = (hi >> 8) ^ (h[i] & 0xFF) ^ ((h[i] & 0xFF) << 1);
nvh = ~(vi ^ hi);
di = (nvh & (di & 0x80) >> 7) ^ vi;
di ^= nvh & (di & 0x01) << 1;
di ^= nvh & (di & 0x02) << 1;
di ^= nvh & (di & 0x04) << 1;
di ^= nvh & (di & 0x08) << 1;
di ^= nvh & (di & 0x10) << 1;
di ^= nvh & (di & 0x20) << 1;
di ^= nvh & (di & 0x40) << 1;
d[i] = (byte)di;
}
di = ((nvh & (di & 0x80) << 1) ^ vi) >> 8;
/* initialize state */
set(yx[0], 1);
cpy(yx[1], p[di]);
cpy(yx[2], s[0]);
set(yz[0], 0);
set(yz[1], 1);
set(yz[2], 1);
/* y[0] is (even)P + (even)G
* y[1] is (even)P + (odd)G if current d-bit is 0
* y[1] is (odd)P + (even)G if current d-bit is 1
* y[2] is (odd)P + (odd)G
*/
vi = 0;
hi = 0;
/* and go for it! */
for (i = 32; i--!=0; ) {
vi = (vi << 8) | (v[i] & 0xFF);
hi = (hi << 8) | (h[i] & 0xFF);
di = (di << 8) | (d[i] & 0xFF);
for (j = 8; j--!=0; ) {
mont_prep(t1[0], t2[0], yx[0], yz[0]);
mont_prep(t1[1], t2[1], yx[1], yz[1]);
mont_prep(t1[2], t2[2], yx[2], yz[2]);
k = ((vi ^ vi >> 1) >> j & 1)
+ ((hi ^ hi >> 1) >> j & 1);
mont_dbl(yx[2], yz[2], t1[k], t2[k], yx[0], yz[0]);
k = (di >> j & 2) ^ ((di >> j & 1) << 1);
mont_add(t1[1], t2[1], t1[k], t2[k], yx[1], yz[1],
p[di >> j & 1]);
mont_add(t1[2], t2[2], t1[0], t2[0], yx[2], yz[2],
s[((vi ^ hi) >> j & 2) >> 1]);
}
}
k = (vi & 1) + (hi & 1);
recip(t1[0], yz[k], 0);
mul(t1[1], yx[k], t1[0]);
pack(t1[1], Y);
}
///////////////////////////////////////////////////////////////////////////
/* sahn0:
* Using this class instead of long[10] to avoid bounds checks. */
private static final class long10 {
public long10() {}
public long10(
long _0, long _1, long _2, long _3, long _4,
long _5, long _6, long _7, long _8, long _9)
{
this._0=_0; this._1=_1; this._2=_2;
this._3=_3; this._4=_4; this._5=_5;
this._6=_6; this._7=_7; this._8=_8;
this._9=_9;
}
public long _0,_1,_2,_3,_4,_5,_6,_7,_8,_9;
}
/********************* radix 2^8 math *********************/
private static final void cpy32(byte[] d, byte[] s) {
int i;
for (i = 0; i < 32; i++)
d[i] = s[i];
}
/* p[m..n+m-1] = q[m..n+m-1] + z * x */
/* n is the size of x */
/* n+m is the size of p and q */
private static final int mula_small(byte[] p,byte[] q,int m,byte[] x,int n,int z) {
int v=0;
for (int i=0;i<n;++i) {
v+=(q[i+m] & 0xFF)+z*(x[i] & 0xFF);
p[i+m]=(byte)v;
v>>=8;
}
return v;
}
/* p += x * y * z where z is a small integer
* x is size 32, y is size t, p is size 32+t
* y is allowed to overlap with p+32 if you don't care about the upper half */
private static final int mula32(byte[] p, byte[] x, byte[] y, int t, int z) {
final int n = 31;
int w = 0;
int i = 0;
for (; i < t; i++) {
int zy = z * (y[i] & 0xFF);
w += mula_small(p, p, i, x, n, zy) +
(p[i+n] & 0xFF) + zy * (x[n] & 0xFF);
p[i+n] = (byte)w;
w >>= 8;
}
p[i+n] = (byte)(w + (p[i+n] & 0xFF));
return w >> 8;
}
/* divide r (size n) by d (size t), returning quotient q and remainder r
* quotient is size n-t+1, remainder is size t
* requires t > 0 && d[t-1] != 0
* requires that r[-1] and d[-1] are valid memory locations
* q may overlap with r+t */
private static final void divmod(byte[] q, byte[] r, int n, byte[] d, int t) {
int rn = 0;
int dt = ((d[t-1] & 0xFF) << 8);
if (t>1) {
dt |= (d[t-2] & 0xFF);
}
while (n-- >= t) {
int z = (rn << 16) | ((r[n] & 0xFF) << 8);
if (n>0) {
z |= (r[n-1] & 0xFF);
}
z/=dt;
rn += mula_small(r,r, n-t+1, d, t, -z);
q[n-t+1] = (byte)((z + rn) & 0xFF); /* rn is 0 or -1 (underflow) */
mula_small(r,r, n-t+1, d, t, -rn);
rn = (r[n] & 0xFF);
r[n] = 0;
}
r[t-1] = (byte)rn;
}
private static final int numsize(byte[] x,int n) {
while (n--!=0 && x[n]==0)
;
return n+1;
}
/* Returns x if a contains the gcd, y if b.
* Also, the returned buffer contains the inverse of a mod b,
* as 32-byte signed.
* x and y must have 64 bytes space for temporary use.
* requires that a[-1] and b[-1] are valid memory locations */
private static final byte[] egcd32(byte[] x,byte[] y,byte[] a,byte[] b) {
int an, bn = 32, qn, i;
for (i = 0; i < 32; i++)
x[i] = y[i] = 0;
x[0] = 1;
an = numsize(a, 32);
if (an==0)
return y; /* division by zero */
byte[] temp=new byte[32];
while (true) {
qn = bn - an + 1;
divmod(temp, b, bn, a, an);
bn = numsize(b, bn);
if (bn==0)
return x;
mula32(y, x, temp, qn, -1);
qn = an - bn + 1;
divmod(temp, a, an, b, bn);
an = numsize(a, an);
if (an==0)
return y;
mula32(x, y, temp, qn, -1);
}
}
/********************* radix 2^25.5 GF(2^255-19) math *********************/
private static final int P25=33554431; /* (1 << 25) - 1 */
private static final int P26=67108863; /* (1 << 26) - 1 */
/* Convert to internal format from little-endian byte format */
private static final void unpack(long10 x,byte[] m) {
x._0 = ((m[0] & 0xFF)) | ((m[1] & 0xFF))<<8 |
(m[2] & 0xFF)<<16 | ((m[3] & 0xFF)& 3)<<24;
x._1 = ((m[3] & 0xFF)&~ 3)>>2 | (m[4] & 0xFF)<<6 |
(m[5] & 0xFF)<<14 | ((m[6] & 0xFF)& 7)<<22;
x._2 = ((m[6] & 0xFF)&~ 7)>>3 | (m[7] & 0xFF)<<5 |
(m[8] & 0xFF)<<13 | ((m[9] & 0xFF)&31)<<21;
x._3 = ((m[9] & 0xFF)&~31)>>5 | (m[10] & 0xFF)<<3 |
(m[11] & 0xFF)<<11 | ((m[12] & 0xFF)&63)<<19;
x._4 = ((m[12] & 0xFF)&~63)>>6 | (m[13] & 0xFF)<<2 |
(m[14] & 0xFF)<<10 | (m[15] & 0xFF) <<18;
x._5 = (m[16] & 0xFF) | (m[17] & 0xFF)<<8 |
(m[18] & 0xFF)<<16 | ((m[19] & 0xFF)& 1)<<24;
x._6 = ((m[19] & 0xFF)&~ 1)>>1 | (m[20] & 0xFF)<<7 |
(m[21] & 0xFF)<<15 | ((m[22] & 0xFF)& 7)<<23;
x._7 = ((m[22] & 0xFF)&~ 7)>>3 | (m[23] & 0xFF)<<5 |
(m[24] & 0xFF)<<13 | ((m[25] & 0xFF)&15)<<21;
x._8 = ((m[25] & 0xFF)&~15)>>4 | (m[26] & 0xFF)<<4 |
(m[27] & 0xFF)<<12 | ((m[28] & 0xFF)&63)<<20;
x._9 = ((m[28] & 0xFF)&~63)>>6 | (m[29] & 0xFF)<<2 |
(m[30] & 0xFF)<<10 | (m[31] & 0xFF) <<18;
}
/* Check if reduced-form input >= 2^255-19 */
private static final boolean is_overflow(long10 x) {
return (
((x._0 > P26-19)) &&
((x._1 & x._3 & x._5 & x._7 & x._9) == P25) &&
((x._2 & x._4 & x._6 & x._8) == P26)
) || (x._9 > P25);
}
/* Convert from internal format to little-endian byte format. The
* number must be in a reduced form which is output by the following ops:
* unpack, mul, sqr
* set -- if input in range 0 .. P25
* If you're unsure if the number is reduced, first multiply it by 1. */
private static final void pack(long10 x,byte[] m) {
int ld = 0, ud = 0;
long t;
ld = (is_overflow(x)?1:0) - ((x._9 < 0)?1:0);
ud = ld * -(P25+1);
ld *= 19;
t = ld + x._0 + (x._1 << 26);
m[ 0] = (byte)t;
m[ 1] = (byte)(t >> 8);
m[ 2] = (byte)(t >> 16);
m[ 3] = (byte)(t >> 24);
t = (t >> 32) + (x._2 << 19);
m[ 4] = (byte)t;
m[ 5] = (byte)(t >> 8);
m[ 6] = (byte)(t >> 16);
m[ 7] = (byte)(t >> 24);
t = (t >> 32) + (x._3 << 13);
m[ 8] = (byte)t;
m[ 9] = (byte)(t >> 8);
m[10] = (byte)(t >> 16);
m[11] = (byte)(t >> 24);
t = (t >> 32) + (x._4 << 6);
m[12] = (byte)t;
m[13] = (byte)(t >> 8);
m[14] = (byte)(t >> 16);
m[15] = (byte)(t >> 24);
t = (t >> 32) + x._5 + (x._6 << 25);
m[16] = (byte)t;
m[17] = (byte)(t >> 8);
m[18] = (byte)(t >> 16);
m[19] = (byte)(t >> 24);
t = (t >> 32) + (x._7 << 19);
m[20] = (byte)t;
m[21] = (byte)(t >> 8);
m[22] = (byte)(t >> 16);
m[23] = (byte)(t >> 24);
t = (t >> 32) + (x._8 << 12);
m[24] = (byte)t;
m[25] = (byte)(t >> 8);
m[26] = (byte)(t >> 16);
m[27] = (byte)(t >> 24);
t = (t >> 32) + ((x._9 + ud) << 6);
m[28] = (byte)t;
m[29] = (byte)(t >> 8);
m[30] = (byte)(t >> 16);
m[31] = (byte)(t >> 24);
}
/* Copy a number */
private static final void cpy(long10 out, long10 in) {
out._0=in._0; out._1=in._1;
out._2=in._2; out._3=in._3;
out._4=in._4; out._5=in._5;
out._6=in._6; out._7=in._7;
out._8=in._8; out._9=in._9;
}
/* Set a number to value, which must be in range -185861411 .. 185861411 */
private static final void set(long10 out, int in) {
out._0=in; out._1=0;
out._2=0; out._3=0;
out._4=0; out._5=0;
out._6=0; out._7=0;
out._8=0; out._9=0;
}
/* Add/subtract two numbers. The inputs must be in reduced form, and the
* output isn't, so to do another addition or subtraction on the output,
* first multiply it by one to reduce it. */
private static final void add(long10 xy, long10 x, long10 y) {
xy._0 = x._0 + y._0; xy._1 = x._1 + y._1;
xy._2 = x._2 + y._2; xy._3 = x._3 + y._3;
xy._4 = x._4 + y._4; xy._5 = x._5 + y._5;
xy._6 = x._6 + y._6; xy._7 = x._7 + y._7;
xy._8 = x._8 + y._8; xy._9 = x._9 + y._9;
}
private static final void sub(long10 xy, long10 x, long10 y) {
xy._0 = x._0 - y._0; xy._1 = x._1 - y._1;
xy._2 = x._2 - y._2; xy._3 = x._3 - y._3;
xy._4 = x._4 - y._4; xy._5 = x._5 - y._5;
xy._6 = x._6 - y._6; xy._7 = x._7 - y._7;
xy._8 = x._8 - y._8; xy._9 = x._9 - y._9;
}
/* Multiply a number by a small integer in range -185861411 .. 185861411.
* The output is in reduced form, the input x need not be. x and xy may point
* to the same buffer. */
private static final long10 mul_small(long10 xy, long10 x, long y) {
long t;
t = (x._8*y);
xy._8 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x._9*y);
xy._9 = (t & ((1 << 25) - 1));
t = 19 * (t >> 25) + (x._0*y);
xy._0 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x._1*y);
xy._1 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x._2*y);
xy._2 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x._3*y);
xy._3 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x._4*y);
xy._4 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x._5*y);
xy._5 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x._6*y);
xy._6 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x._7*y);
xy._7 = (t & ((1 << 25) - 1));
t = (t >> 25) + xy._8;
xy._8 = (t & ((1 << 26) - 1));
xy._9 += (t >> 26);
return xy;
}
/* Multiply two numbers. The output is in reduced form, the inputs need not
* be. */
private static final long10 mul(long10 xy, long10 x, long10 y) {
/* sahn0:
* Using local variables to avoid class access.
* This seem to improve performance a bit...
*/
long
x_0=x._0,x_1=x._1,x_2=x._2,x_3=x._3,x_4=x._4,
x_5=x._5,x_6=x._6,x_7=x._7,x_8=x._8,x_9=x._9;
long
y_0=y._0,y_1=y._1,y_2=y._2,y_3=y._3,y_4=y._4,
y_5=y._5,y_6=y._6,y_7=y._7,y_8=y._8,y_9=y._9;
long t;
t = (x_0*y_8) + (x_2*y_6) + (x_4*y_4) + (x_6*y_2) +
(x_8*y_0) + 2 * ((x_1*y_7) + (x_3*y_5) +
(x_5*y_3) + (x_7*y_1)) + 38 *
(x_9*y_9);
xy._8 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x_0*y_9) + (x_1*y_8) + (x_2*y_7) +
(x_3*y_6) + (x_4*y_5) + (x_5*y_4) +
(x_6*y_3) + (x_7*y_2) + (x_8*y_1) +
(x_9*y_0);
xy._9 = (t & ((1 << 25) - 1));
t = (x_0*y_0) + 19 * ((t >> 25) + (x_2*y_8) + (x_4*y_6)
+ (x_6*y_4) + (x_8*y_2)) + 38 *
((x_1*y_9) + (x_3*y_7) + (x_5*y_5) +
(x_7*y_3) + (x_9*y_1));
xy._0 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x_0*y_1) + (x_1*y_0) + 19 * ((x_2*y_9)
+ (x_3*y_8) + (x_4*y_7) + (x_5*y_6) +
(x_6*y_5) + (x_7*y_4) + (x_8*y_3) +
(x_9*y_2));
xy._1 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x_0*y_2) + (x_2*y_0) + 19 * ((x_4*y_8)
+ (x_6*y_6) + (x_8*y_4)) + 2 * (x_1*y_1)
+ 38 * ((x_3*y_9) + (x_5*y_7) +
(x_7*y_5) + (x_9*y_3));
xy._2 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x_0*y_3) + (x_1*y_2) + (x_2*y_1) +
(x_3*y_0) + 19 * ((x_4*y_9) + (x_5*y_8) +
(x_6*y_7) + (x_7*y_6) +
(x_8*y_5) + (x_9*y_4));
xy._3 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x_0*y_4) + (x_2*y_2) + (x_4*y_0) + 19 *
((x_6*y_8) + (x_8*y_6)) + 2 * ((x_1*y_3) +
(x_3*y_1)) + 38 *
((x_5*y_9) + (x_7*y_7) + (x_9*y_5));
xy._4 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x_0*y_5) + (x_1*y_4) + (x_2*y_3) +
(x_3*y_2) + (x_4*y_1) + (x_5*y_0) + 19 *
((x_6*y_9) + (x_7*y_8) + (x_8*y_7) +
(x_9*y_6));
xy._5 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x_0*y_6) + (x_2*y_4) + (x_4*y_2) +
(x_6*y_0) + 19 * (x_8*y_8) + 2 * ((x_1*y_5) +
(x_3*y_3) + (x_5*y_1)) + 38 *
((x_7*y_9) + (x_9*y_7));
xy._6 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x_0*y_7) + (x_1*y_6) + (x_2*y_5) +
(x_3*y_4) + (x_4*y_3) + (x_5*y_2) +
(x_6*y_1) + (x_7*y_0) + 19 * ((x_8*y_9) +
(x_9*y_8));
xy._7 = (t & ((1 << 25) - 1));
t = (t >> 25) + xy._8;
xy._8 = (t & ((1 << 26) - 1));
xy._9 += (t >> 26);
return xy;
}
/* Square a number. Optimization of mul25519(x2, x, x) */
private static final long10 sqr(long10 x2, long10 x) {
long
x_0=x._0,x_1=x._1,x_2=x._2,x_3=x._3,x_4=x._4,
x_5=x._5,x_6=x._6,x_7=x._7,x_8=x._8,x_9=x._9;
long t;
t = (x_4*x_4) + 2 * ((x_0*x_8) + (x_2*x_6)) + 38 *
(x_9*x_9) + 4 * ((x_1*x_7) + (x_3*x_5));
x2._8 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x_0*x_9) + (x_1*x_8) + (x_2*x_7) +
(x_3*x_6) + (x_4*x_5));
x2._9 = (t & ((1 << 25) - 1));
t = 19 * (t >> 25) + (x_0*x_0) + 38 * ((x_2*x_8) +
(x_4*x_6) + (x_5*x_5)) + 76 * ((x_1*x_9)
+ (x_3*x_7));
x2._0 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * (x_0*x_1) + 38 * ((x_2*x_9) +
(x_3*x_8) + (x_4*x_7) + (x_5*x_6));
x2._1 = (t & ((1 << 25) - 1));
t = (t >> 25) + 19 * (x_6*x_6) + 2 * ((x_0*x_2) +
(x_1*x_1)) + 38 * (x_4*x_8) + 76 *
((x_3*x_9) + (x_5*x_7));
x2._2 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x_0*x_3) + (x_1*x_2)) + 38 *
((x_4*x_9) + (x_5*x_8) + (x_6*x_7));
x2._3 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x_2*x_2) + 2 * (x_0*x_4) + 38 *
((x_6*x_8) + (x_7*x_7)) + 4 * (x_1*x_3) + 76 *
(x_5*x_9);
x2._4 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x_0*x_5) + (x_1*x_4) + (x_2*x_3))
+ 38 * ((x_6*x_9) + (x_7*x_8));
x2._5 = (t & ((1 << 25) - 1));
t = (t >> 25) + 19 * (x_8*x_8) + 2 * ((x_0*x_6) +
(x_2*x_4) + (x_3*x_3)) + 4 * (x_1*x_5) +
76 * (x_7*x_9);
x2._6 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x_0*x_7) + (x_1*x_6) + (x_2*x_5) +
(x_3*x_4)) + 38 * (x_8*x_9);
x2._7 = (t & ((1 << 25) - 1));
t = (t >> 25) + x2._8;
x2._8 = (t & ((1 << 26) - 1));
x2._9 += (t >> 26);
return x2;
}
/* Calculates a reciprocal. The output is in reduced form, the inputs need not
* be. Simply calculates y = x^(p-2) so it's not too fast. */
/* When sqrtassist is true, it instead calculates y = x^((p-5)/8) */
private static final void recip(long10 y, long10 x, int sqrtassist) {
long10
t0=new long10(),
t1=new long10(),
t2=new long10(),
t3=new long10(),
t4=new long10();
int i;
/* the chain for x^(2^255-21) is straight from djb's implementation */
sqr(t1, x); /* 2 == 2 * 1 */
sqr(t2, t1); /* 4 == 2 * 2 */
sqr(t0, t2); /* 8 == 2 * 4 */
mul(t2, t0, x); /* 9 == 8 + 1 */
mul(t0, t2, t1); /* 11 == 9 + 2 */
sqr(t1, t0); /* 22 == 2 * 11 */
mul(t3, t1, t2); /* 31 == 22 + 9
== 2^5 - 2^0 */
sqr(t1, t3); /* 2^6 - 2^1 */
sqr(t2, t1); /* 2^7 - 2^2 */
sqr(t1, t2); /* 2^8 - 2^3 */
sqr(t2, t1); /* 2^9 - 2^4 */
sqr(t1, t2); /* 2^10 - 2^5 */
mul(t2, t1, t3); /* 2^10 - 2^0 */
sqr(t1, t2); /* 2^11 - 2^1 */
sqr(t3, t1); /* 2^12 - 2^2 */
for (i = 1; i < 5; i++) {
sqr(t1, t3);
sqr(t3, t1);
} /* t3 */ /* 2^20 - 2^10 */
mul(t1, t3, t2); /* 2^20 - 2^0 */
sqr(t3, t1); /* 2^21 - 2^1 */
sqr(t4, t3); /* 2^22 - 2^2 */
for (i = 1; i < 10; i++) {
sqr(t3, t4);
sqr(t4, t3);
} /* t4 */ /* 2^40 - 2^20 */
mul(t3, t4, t1); /* 2^40 - 2^0 */
for (i = 0; i < 5; i++) {
sqr(t1, t3);
sqr(t3, t1);
} /* t3 */ /* 2^50 - 2^10 */
mul(t1, t3, t2); /* 2^50 - 2^0 */
sqr(t2, t1); /* 2^51 - 2^1 */
sqr(t3, t2); /* 2^52 - 2^2 */
for (i = 1; i < 25; i++) {
sqr(t2, t3);
sqr(t3, t2);
} /* t3 */ /* 2^100 - 2^50 */
mul(t2, t3, t1); /* 2^100 - 2^0 */
sqr(t3, t2); /* 2^101 - 2^1 */
sqr(t4, t3); /* 2^102 - 2^2 */
for (i = 1; i < 50; i++) {
sqr(t3, t4);
sqr(t4, t3);
} /* t4 */ /* 2^200 - 2^100 */
mul(t3, t4, t2); /* 2^200 - 2^0 */
for (i = 0; i < 25; i++) {
sqr(t4, t3);
sqr(t3, t4);
} /* t3 */ /* 2^250 - 2^50 */
mul(t2, t3, t1); /* 2^250 - 2^0 */
sqr(t1, t2); /* 2^251 - 2^1 */
sqr(t2, t1); /* 2^252 - 2^2 */
if (sqrtassist!=0) {
mul(y, x, t2); /* 2^252 - 3 */
} else {
sqr(t1, t2); /* 2^253 - 2^3 */
sqr(t2, t1); /* 2^254 - 2^4 */
sqr(t1, t2); /* 2^255 - 2^5 */
mul(y, t1, t0); /* 2^255 - 21 */
}
}
/* checks if x is "negative", requires reduced input */
private static final int is_negative(long10 x) {
return (int)(((is_overflow(x) || (x._9 < 0))?1:0) ^ (x._0 & 1));
}
/* a square root */
private static final void sqrt(long10 x, long10 u) {
long10 v=new long10(), t1=new long10(), t2=new long10();
add(t1, u, u); /* t1 = 2u */
recip(v, t1, 1); /* v = (2u)^((p-5)/8) */
sqr(x, v); /* x = v^2 */
mul(t2, t1, x); /* t2 = 2uv^2 */
t2._0--; /* t2 = 2uv^2-1 */
mul(t1, v, t2); /* t1 = v(2uv^2-1) */
mul(x, u, t1); /* x = uv(2uv^2-1) */
}
/********************* Elliptic curve *********************/
/* y^2 = x^3 + 486662 x^2 + x over GF(2^255-19) */
/* t1 = ax + az
* t2 = ax - az */
private static final void mont_prep(long10 t1, long10 t2, long10 ax, long10 az) {
add(t1, ax, az);
sub(t2, ax, az);
}
/* A = P + Q where
* X(A) = ax/az
* X(P) = (t1+t2)/(t1-t2)
* X(Q) = (t3+t4)/(t3-t4)
* X(P-Q) = dx
* clobbers t1 and t2, preserves t3 and t4 */
private static final void mont_add(long10 t1, long10 t2, long10 t3, long10 t4,long10 ax, long10 az, long10 dx) {
mul(ax, t2, t3);
mul(az, t1, t4);
add(t1, ax, az);
sub(t2, ax, az);
sqr(ax, t1);
sqr(t1, t2);
mul(az, t1, dx);
}
/* B = 2 * Q where
* X(B) = bx/bz
* X(Q) = (t3+t4)/(t3-t4)
* clobbers t1 and t2, preserves t3 and t4 */
private static final void mont_dbl(long10 t1, long10 t2, long10 t3, long10 t4,long10 bx, long10 bz) {
sqr(t1, t3);
sqr(t2, t4);
mul(bx, t1, t2);
sub(t2, t1, t2);
mul_small(bz, t2, 121665);
add(t1, t1, bz);
mul(bz, t1, t2);
}
/* Y^2 = X^3 + 486662 X^2 + X
* t is a temporary */
private static final void x_to_y2(long10 t, long10 y2, long10 x) {
sqr(t, x);
mul_small(y2, x, 486662);
add(t, t, y2);
t._0++;
mul(y2, t, x);
}
/* P = kG and s = sign(P)/k */
private static final void core(byte[] Px, byte[] s, byte[] k, byte[] Gx) {
long10
dx=new long10(),
t1=new long10(),
t2=new long10(),
t3=new long10(),
t4=new long10();
long10[]
x=new long10[]{new long10(),new long10()},
z=new long10[]{new long10(),new long10()};
int i, j;
/* unpack the base */
if (Gx!=null)
unpack(dx, Gx);
else
set(dx, 9);
/* 0G = point-at-infinity */
set(x[0], 1);
set(z[0], 0);
/* 1G = G */
cpy(x[1], dx);
set(z[1], 1);
for (i = 32; i--!=0; ) {
if (i==0) {
i=0;
}
for (j = 8; j--!=0; ) {
/* swap arguments depending on bit */
int bit1 = (k[i] & 0xFF) >> j & 1;
int bit0 = ~(k[i] & 0xFF) >> j & 1;
long10 ax = x[bit0];
long10 az = z[bit0];
long10 bx = x[bit1];
long10 bz = z[bit1];
/* a' = a + b */
/* b' = 2 b */
mont_prep(t1, t2, ax, az);
mont_prep(t3, t4, bx, bz);
mont_add(t1, t2, t3, t4, ax, az, dx);
mont_dbl(t1, t2, t3, t4, bx, bz);
}
}
recip(t1, z[0], 0);
mul(dx, x[0], t1);
pack(dx, Px);
/* calculate s such that s abs(P) = G .. assumes G is std base point */
if (s!=null) {
x_to_y2(t2, t1, dx); /* t1 = Py^2 */
recip(t3, z[1], 0); /* where Q=P+G ... */
mul(t2, x[1], t3); /* t2 = Qx */
add(t2, t2, dx); /* t2 = Qx + Px */
t2._0 += 9 + 486662; /* t2 = Qx + Px + Gx + 486662 */
dx._0 -= 9; /* dx = Px - Gx */
sqr(t3, dx); /* t3 = (Px - Gx)^2 */
mul(dx, t2, t3); /* dx = t2 (Px - Gx)^2 */
sub(dx, dx, t1); /* dx = t2 (Px - Gx)^2 - Py^2 */
dx._0 -= 39420360; /* dx = t2 (Px - Gx)^2 - Py^2 - Gy^2 */
mul(t1, dx, BASE_R2Y); /* t1 = -Py */
if (is_negative(t1)!=0) /* sign is 1, so just copy */
cpy32(s, k);
else /* sign is -1, so negate */
mula_small(s, ORDER_TIMES_8, 0, k, 32, -1);
/* reduce s mod q
* (is this needed? do it just in case, it's fast anyway) */
//divmod((dstptr) t1, s, 32, order25519, 32);
/* take reciprocal of s mod q */
byte[] temp1=new byte[32];
byte[] temp2=new byte[64];
byte[] temp3=new byte[64];
cpy32(temp1, ORDER);
cpy32(s, egcd32(temp2, temp3, s, temp1));
if ((s[31] & 0x80)!=0)
mula_small(s, s, 0, ORDER, 32, 1);
}
}
/* smallest multiple of the order that's >= 2^255 */
private static final byte[] ORDER_TIMES_8 = {
(byte)104, (byte)159, (byte)174, (byte)231,
(byte)210, (byte)24, (byte)147, (byte)192,
(byte)178, (byte)230, (byte)188, (byte)23,
(byte)245, (byte)206, (byte)247, (byte)166,
(byte)0, (byte)0, (byte)0, (byte)0,
(byte)0, (byte)0, (byte)0, (byte)0,
(byte)0, (byte)0, (byte)0, (byte)0,
(byte)0, (byte)0, (byte)0, (byte)128
};
/* constants 2Gy and 1/(2Gy) */
private static final long10 BASE_2Y = new long10(
39999547, 18689728, 59995525, 1648697, 57546132,
24010086, 19059592, 5425144, 63499247, 16420658
);
private static final long10 BASE_R2Y = new long10(
5744, 8160848, 4790893, 13779497, 35730846,
12541209, 49101323, 30047407, 40071253, 6226132
);
}

View File

@@ -136,7 +136,7 @@ public class Promise<V, T extends Throwable> {
throws T {
final V value = tryRetrieve(timeout, unit);
if (value == null)
throw chainer.chain(new TimeoutException("Timeout expired: " + timeout + " " + unit));
throw chainer.chain(new TimeoutException("Timeout expired"));
else
return value;
}

View File

@@ -19,6 +19,8 @@ import com.hierynomus.sshj.key.KeyAlgorithm;
import com.hierynomus.sshj.key.KeyAlgorithms;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
import java.util.Arrays;
@@ -32,6 +34,7 @@ public class AndroidConfig
SecurityUtils.registerSecurityProvider("org.spongycastle.jce.provider.BouncyCastleProvider");
}
@Override
protected void initKeyAlgorithms() {
setKeyAlgorithms(Arrays.<Factory.Named<KeyAlgorithm>>asList(
@@ -40,4 +43,10 @@ public class AndroidConfig
KeyAlgorithms.SSHDSA()
));
}
@Override
protected void initRandomFactory(boolean ignored) {
setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory()));
}
}

View File

@@ -29,6 +29,7 @@ import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.NoneCompression;
import net.schmizz.sshj.transport.kex.Curve25519SHA256;
@@ -42,11 +43,7 @@ import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import org.slf4j.Logger;
import java.util.Arrays;
import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Properties;
import java.util.*;
/**
* A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
@@ -65,6 +62,8 @@ import java.util.Properties;
* <li>{@link net.schmizz.sshj.ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li>
* </ul>
* <p/>
* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This
* prevents using ciphers with strength greater than 128.
*/
public class DefaultConfig
extends ConfigImpl {
@@ -74,10 +73,11 @@ public class DefaultConfig
public DefaultConfig() {
setLoggerFactory(LoggerFactory.DEFAULT);
setVersion(readVersionFromProperties());
initKeyExchangeFactories();
final boolean bouncyCastleRegistered = SecurityUtils.isBouncyCastleRegistered();
initKeyExchangeFactories(bouncyCastleRegistered);
initKeyAlgorithms();
initRandomFactory();
initFileKeyProviderFactories();
initRandomFactory(bouncyCastleRegistered);
initFileKeyProviderFactories(bouncyCastleRegistered);
initCipherFactories();
initCompressionFactories();
initMACFactories();
@@ -102,32 +102,35 @@ public class DefaultConfig
log = loggerFactory.getLogger(getClass());
}
protected void initKeyExchangeFactories() {
setKeyExchangeFactories(
new Curve25519SHA256.Factory(),
new Curve25519SHA256.FactoryLibSsh(),
new DHGexSHA256.Factory(),
new ECDHNistP.Factory521(),
new ECDHNistP.Factory384(),
new ECDHNistP.Factory256(),
new DHGexSHA1.Factory(),
DHGroups.Group1SHA1(),
DHGroups.Group14SHA1(),
DHGroups.Group14SHA256(),
DHGroups.Group15SHA512(),
DHGroups.Group16SHA512(),
DHGroups.Group17SHA512(),
DHGroups.Group18SHA512(),
ExtendedDHGroups.Group14SHA256AtSSH(),
ExtendedDHGroups.Group15SHA256(),
ExtendedDHGroups.Group15SHA256AtSSH(),
ExtendedDHGroups.Group15SHA384AtSSH(),
ExtendedDHGroups.Group16SHA256(),
ExtendedDHGroups.Group16SHA384AtSSH(),
ExtendedDHGroups.Group16SHA512AtSSH(),
ExtendedDHGroups.Group18SHA512AtSSH(),
new ExtInfoClientFactory()
);
protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) {
if (bouncyCastleRegistered) {
setKeyExchangeFactories(
new Curve25519SHA256.Factory(),
new Curve25519SHA256.FactoryLibSsh(),
new DHGexSHA256.Factory(),
new ECDHNistP.Factory521(),
new ECDHNistP.Factory384(),
new ECDHNistP.Factory256(),
new DHGexSHA1.Factory(),
DHGroups.Group1SHA1(),
DHGroups.Group14SHA1(),
DHGroups.Group14SHA256(),
DHGroups.Group15SHA512(),
DHGroups.Group16SHA512(),
DHGroups.Group17SHA512(),
DHGroups.Group18SHA512(),
ExtendedDHGroups.Group14SHA256AtSSH(),
ExtendedDHGroups.Group15SHA256(),
ExtendedDHGroups.Group15SHA256AtSSH(),
ExtendedDHGroups.Group15SHA384AtSSH(),
ExtendedDHGroups.Group16SHA256(),
ExtendedDHGroups.Group16SHA384AtSSH(),
ExtendedDHGroups.Group16SHA512AtSSH(),
ExtendedDHGroups.Group18SHA512AtSSH(),
new ExtInfoClientFactory());
} else {
setKeyExchangeFactories(DHGroups.Group1SHA1(), new DHGexSHA1.Factory());
}
}
protected void initKeyAlgorithms() {
@@ -148,19 +151,21 @@ public class DefaultConfig
KeyAlgorithms.SSHDSA()));
}
protected void initRandomFactory() {
protected void initRandomFactory(boolean bouncyCastleRegistered) {
setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory()));
}
protected void initFileKeyProviderFactories() {
setFileKeyProviderFactories(
new OpenSSHKeyV1KeyFile.Factory(),
new PKCS8KeyFile.Factory(),
new OpenSSHKeyFile.Factory(),
new PuTTYKeyFile.Factory()
);
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
if (bouncyCastleRegistered) {
setFileKeyProviderFactories(
new OpenSSHKeyV1KeyFile.Factory(),
new PKCS8KeyFile.Factory(),
new OpenSSHKeyFile.Factory(),
new PuTTYKeyFile.Factory());
}
}
protected void initCipherFactories() {
List<Factory.Named<Cipher>> avail = new LinkedList<Factory.Named<Cipher>>(Arrays.<Factory.Named<Cipher>>asList(
ChachaPolyCiphers.CHACHA_POLY_OPENSSH(),
@@ -198,22 +203,27 @@ public class DefaultConfig
StreamCiphers.Arcfour256())
);
final ListIterator<Factory.Named<Cipher>> factories = avail.listIterator();
while (factories.hasNext()) {
final Factory.Named<Cipher> factory = factories.next();
boolean warn = false;
// Ref. https://issues.apache.org/jira/browse/SSHD-24
// "AES256 and AES192 requires unlimited cryptography extension"
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext(); ) {
final Factory.Named<Cipher> f = i.next();
try {
final Cipher cipher = factory.create();
final byte[] key = new byte[cipher.getBlockSize()];
final byte[] iv = new byte[cipher.getIVSize()];
cipher.init(Cipher.Mode.Encrypt, key, iv);
final Cipher c = f.create();
final byte[] key = new byte[c.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (Exception e) {
log.info("Cipher [{}] disabled: {}", factory.getName(), e.getCause().getMessage());
factories.remove();
warn = true;
log.warn(e.getCause().getMessage());
i.remove();
}
}
if (warn)
log.warn("Disabling high-strength ciphers: cipher strengths apparently limited by JCE policy");
setCipherFactories(avail);
log.debug("Available Ciphers {}", avail);
log.debug("Available cipher factories: {}", avail);
}
protected void initMACFactories() {

View File

@@ -40,6 +40,7 @@ import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
import net.schmizz.sshj.transport.verification.FingerprintVerifier;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import net.schmizz.sshj.userauth.AuthResult;
import net.schmizz.sshj.userauth.UserAuth;
import net.schmizz.sshj.userauth.UserAuthException;
import net.schmizz.sshj.userauth.UserAuthImpl;
@@ -218,13 +219,30 @@ public class SSHClient
throws UserAuthException, TransportException {
checkConnected();
final Deque<UserAuthException> savedEx = new LinkedList<UserAuthException>();
for (AuthMethod method: methods) {
final List<AuthMethod> tried = new LinkedList<AuthMethod>();
for (Iterator<AuthMethod> it = methods.iterator(); it.hasNext();) {
AuthMethod method = it.next();
method.setLoggerFactory(loggerFactory);
try {
if (auth.authenticate(username, (Service) conn, method, trans.getTimeoutMs()))
AuthResult result = auth.authenticate(username, (Service) conn, method, trans.getTimeoutMs());
if (result == AuthResult.SUCCESS) {
return;
} else if (result == AuthResult.PARTIAL) {
// Put all remaining methods in the tried list, so that we can try them for the second round of authentication
while (it.hasNext()) {
tried.add(it.next());
}
auth(username, tried);
return;
}
tried.add(method);
} catch (UserAuthException e) {
savedEx.push(e);
tried.add(method);
}
}
throw new UserAuthException("Exhausted available authentication methods", savedEx.peek());
@@ -742,7 +760,7 @@ public class SSHClient
*
* @throws IOException if there is an error starting the {@code sftp} subsystem
*/
public StatefulSFTPClient newStatefulSFTPClient()
public SFTPClient newStatefulSFTPClient()
throws IOException {
checkConnected();
checkAuthenticated();
@@ -810,7 +828,12 @@ public class SSHClient
ThreadNameProvider.setThreadName(conn.getKeepAlive(), trans);
keepAliveThread.start();
}
doKex();
if (trans.isKeyExchangeRequired()) {
log.debug("Initiating Key Exchange for new connection");
doKex();
} else {
log.debug("Key Exchange already completed for new connection");
}
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,9 @@ class ECDSAVariationsAdapter {
static PublicKey readPubKeyFromBuffer(Buffer<?> buf, String variation) throws GeneralSecurityException {
String algorithm = BASE_ALGORITHM_NAME + variation;
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + algorithm);
}
try {
// final String algo = buf.readString(); it has been already read
final String curveName = buf.readString();

View File

@@ -259,11 +259,6 @@ public class SecurityUtils {
return false;
}
/**
* Configure whether to register the Bouncy Castle Security Provider. Must be called prior to other methods
*
* @param registerBouncyCastle Enable or disable Bouncy Castle Provider registration on subsequent method invocation
*/
public static synchronized void setRegisterBouncyCastle(boolean registerBouncyCastle) {
SecurityUtils.registerBouncyCastle = registerBouncyCastle;
registrationDone = false;

View File

@@ -22,6 +22,8 @@ import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import static com.hierynomus.sshj.backport.Sockets.asCloseable;
public class SocketStreamCopyMonitor
extends Thread {
@@ -41,7 +43,7 @@ public class SocketStreamCopyMonitor
await(y);
} catch (IOException ignored) {
} finally {
IOUtils.closeQuietly(channel, socket);
IOUtils.closeQuietly(channel, asCloseable(socket));
}
}

View File

@@ -29,6 +29,8 @@ import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.TimeUnit;
import static com.hierynomus.sshj.backport.Sockets.asCloseable;
public class LocalPortForwarder {
public static class ForwardedChannel
@@ -76,7 +78,7 @@ public class LocalPortForwarder {
chan.open();
chan.start();
} catch (IOException e) {
IOUtils.closeQuietly(chan, socket);
IOUtils.closeQuietly(chan, asCloseable(socket));
throw e;
}
}

View File

@@ -136,25 +136,13 @@ final class KeyExchanger
void startKex(boolean waitForDone)
throws TransportException {
if (!kexOngoing.getAndSet(true)) {
if (isKeyExchangeAllowed()) {
log.debug("Initiating key exchange");
done.clear();
sendKexInit();
} else {
kexOngoing.set(false);
}
done.clear();
sendKexInit();
}
if (waitForDone)
waitForDone();
}
/**
* Key exchange can be initiated exactly once while connecting or later after authentication when re-keying.
*/
private boolean isKeyExchangeAllowed() {
return !isKexDone() || transport.isAuthenticated();
}
void waitForDone()
throws TransportException {
done.await(transport.getTimeoutMs(), TimeUnit.MILLISECONDS);

View File

@@ -71,6 +71,13 @@ public interface Transport
void doKex()
throws TransportException;
/**
* Is Key Exchange required based on current transport status
*
* @return Key Exchange required status
*/
boolean isKeyExchangeRequired();
/** @return the version string used by this client to identify itself to an SSH server, e.g. "SSHJ_3_0" */
String getClientVersion();

View File

@@ -254,6 +254,16 @@ public final class TransportImpl
kexer.startKex(true);
}
/**
* Is Key Exchange required returns true when Key Exchange is not done and when Key Exchange is not ongoing
*
* @return Key Exchange required status
*/
@Override
public boolean isKeyExchangeRequired() {
return !kexer.isKexDone() && !kexer.isKexOngoing();
}
public boolean isKexDone() {
return kexer.isKexDone();
}

View File

@@ -15,16 +15,18 @@
*/
package net.schmizz.sshj.transport.compression;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import com.jcraft.jzlib.Deflater;
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.compression.Compression;
public class ZlibCompression implements Compression {
/** ZLib based Compression. */
public class ZlibCompression
implements Compression {
/** Named factory for the ZLib Compression. */
public static class Factory
@@ -50,15 +52,19 @@ public class ZlibCompression implements Compression {
@Override
public void init(Mode mode) {
switch (mode) {
case DEFLATE:
deflater = new Deflater(Deflater.DEFAULT_COMPRESSION);
break;
case INFLATE:
inflater = new Inflater();
break;
default:
assert false;
try {
switch (mode) {
case DEFLATE:
deflater = new Deflater(JZlib.Z_DEFAULT_COMPRESSION);
break;
case INFLATE:
inflater = new Inflater();
break;
default:
assert false;
}
} catch (GZIPException gze) {
}
}
@@ -69,28 +75,43 @@ public class ZlibCompression implements Compression {
@Override
public void compress(Buffer buffer) {
deflater.setInput(buffer.array(), buffer.rpos(), buffer.available());
deflater.setNextIn(buffer.array());
deflater.setNextInIndex(buffer.rpos());
deflater.setAvailIn(buffer.available());
buffer.wpos(buffer.rpos());
do {
final int len = deflater.deflate(tempBuf, 0, BUF_SIZE, Deflater.SYNC_FLUSH);
buffer.putRawBytes(tempBuf, 0, len);
} while (!deflater.needsInput());
deflater.setNextOut(tempBuf);
deflater.setNextOutIndex(0);
deflater.setAvailOut(BUF_SIZE);
final int status = deflater.deflate(JZlib.Z_PARTIAL_FLUSH);
if (status == JZlib.Z_OK) {
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - deflater.getAvailOut());
} else {
throw new SSHRuntimeException("compress: deflate returned " + status);
}
} while (deflater.getAvailOut() == 0);
}
@Override
public void uncompress(Buffer from, Buffer to)
throws TransportException {
inflater.setInput(from.array(), from.rpos(), from.available());
inflater.setNextIn(from.array());
inflater.setNextInIndex(from.rpos());
inflater.setAvailIn(from.available());
while (true) {
try {
int len = inflater.inflate(tempBuf, 0, BUF_SIZE);
if(len > 0) {
to.putRawBytes(tempBuf, 0, len);
} else {
inflater.setNextOut(tempBuf);
inflater.setNextOutIndex(0);
inflater.setAvailOut(BUF_SIZE);
final int status = inflater.inflate(JZlib.Z_PARTIAL_FLUSH);
switch (status) {
case JZlib.Z_OK:
to.putRawBytes(tempBuf, 0, BUF_SIZE - inflater.getAvailOut());
break;
case JZlib.Z_BUF_ERROR:
return;
}
} catch (DataFormatException e) {
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned " + e.getMessage());
default:
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned " + status);
}
}
}

View File

@@ -15,89 +15,50 @@
*/
package net.schmizz.sshj.transport.kex;
import com.hierynomus.sshj.common.KeyAlgorithm;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.Random;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.jce.spec.ECParameterSpec;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
/**
* Key Exchange Method using Curve25519 as defined in RFC 8731
*/
public class Curve25519DH extends DHBase {
private static final String ALGORITHM = "X25519";
private static final int ENCODED_ALGORITHM_ID_KEY_LENGTH = 44;
private static final int ALGORITHM_ID_LENGTH = 12;
private static final int KEY_LENGTH = 32;
private final byte[] algorithmId = new byte[ALGORITHM_ID_LENGTH];
private byte[] secretKey;
public Curve25519DH() {
super(ALGORITHM, ALGORITHM);
super(KeyAlgorithm.ECDSA, "ECDH");
}
@Override
void computeK(byte[] f) throws GeneralSecurityException {
byte[] k = new byte[32];
djb.Curve25519.curve(k, secretKey, f);
setK(new BigInteger(1, k));
}
@Override
public void init(AlgorithmParameterSpec params, Factory<Random> randomFactory) throws GeneralSecurityException {
Random random = randomFactory.create();
byte[] secretBytes = new byte[32];
random.fill(secretBytes);
byte[] publicBytes = new byte[32];
djb.Curve25519.keygen(publicBytes, null, secretBytes);
this.secretKey = Arrays.copyOf(secretBytes, secretBytes.length);
setE(publicBytes);
}
/**
* Compute Shared Secret Key using Diffie-Hellman Curve25519 known as X25519
*
* @param peerPublicKey Peer public key bytes
* @throws GeneralSecurityException Thrown on key agreement failures
* TODO want to figure out why BouncyCastle does not work.
* @return The initialized curve25519 parameter spec
*/
@Override
void computeK(final byte[] peerPublicKey) throws GeneralSecurityException {
final KeyFactory keyFactory = SecurityUtils.getKeyFactory(ALGORITHM);
final KeySpec peerPublicKeySpec = getPeerPublicKeySpec(peerPublicKey);
final PublicKey generatedPeerPublicKey = keyFactory.generatePublic(peerPublicKeySpec);
agreement.doPhase(generatedPeerPublicKey, true);
final byte[] sharedSecretKey = agreement.generateSecret();
final BigInteger sharedSecretNumber = new BigInteger(BigInteger.ONE.signum(), sharedSecretKey);
setK(sharedSecretNumber);
}
/**
* Initialize Key Agreement with generated Public and Private Key Pair
*
* @param params Parameters not used
* @param randomFactory Random Factory not used
* @throws GeneralSecurityException Thrown on key agreement initialization failures
*/
@Override
public void init(final AlgorithmParameterSpec params, final Factory<Random> randomFactory) throws GeneralSecurityException {
final KeyPair keyPair = generator.generateKeyPair();
agreement.init(keyPair.getPrivate());
setPublicKey(keyPair.getPublic());
}
private void setPublicKey(final PublicKey publicKey) {
final byte[] encoded = publicKey.getEncoded();
// Encoded public key consists of the algorithm identifier and public key
if (encoded.length == ENCODED_ALGORITHM_ID_KEY_LENGTH) {
final byte[] publicKeyEncoded = new byte[KEY_LENGTH];
System.arraycopy(encoded, ALGORITHM_ID_LENGTH, publicKeyEncoded, 0, KEY_LENGTH);
setE(publicKeyEncoded);
// Save Algorithm Identifier byte array
System.arraycopy(encoded, 0, algorithmId, 0, ALGORITHM_ID_LENGTH);
} else {
throw new IllegalArgumentException(String.format("X25519 unsupported public key length [%d]", encoded.length));
}
}
private KeySpec getPeerPublicKeySpec(final byte[] peerPublicKey) {
final byte[] encodedKeySpec = new byte[ENCODED_ALGORITHM_ID_KEY_LENGTH];
System.arraycopy(algorithmId, 0, encodedKeySpec, 0, ALGORITHM_ID_LENGTH);
System.arraycopy(peerPublicKey, 0, encodedKeySpec, ALGORITHM_ID_LENGTH, KEY_LENGTH);
return new X509EncodedKeySpec(encodedKeySpec);
public static AlgorithmParameterSpec getCurve25519Params() {
X9ECParameters ecP = CustomNamedCurves.getByName("curve25519");
return new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
}
}

View File

@@ -56,6 +56,6 @@ public class Curve25519SHA256 extends AbstractDHG {
@Override
protected void initDH(DHBase dh) throws GeneralSecurityException {
dh.init(null, trans.getConfig().getRandomFactory());
dh.init(Curve25519DH.getCurve25519Params(), trans.getConfig().getRandomFactory());
}
}

View File

@@ -15,15 +15,16 @@
*/
package net.schmizz.sshj.transport.verification;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
@@ -45,40 +46,48 @@ public class FingerprintVerifier implements HostKeyVerifier {
*
* @param fingerprint of an SSH fingerprint in MD5 (hex), SHA-1 (base64) or SHA-256(base64) format
*
* @return Host Key Verifier
* @return
*/
public static HostKeyVerifier getInstance(String fingerprint) {
if (fingerprint.startsWith("SHA1:")) {
return new FingerprintVerifier("SHA-1", fingerprint.substring(5));
}
if (fingerprint.startsWith("SHA256:")) {
return new FingerprintVerifier("SHA-256", fingerprint.substring(7));
}
final String md5;
if (fingerprint.startsWith("MD5:")) {
md5 = fingerprint.substring(4); // remove the MD5: prefix
} else {
md5 = fingerprint;
}
if (!MD5_FINGERPRINT_PATTERN.matcher(md5).matches()) {
throw new SSHRuntimeException("Invalid MD5 fingerprint: " + fingerprint);
}
// Use the old default fingerprint verifier for md5 fingerprints
return (new HostKeyVerifier() {
@Override
public boolean verify(String h, int p, PublicKey k) {
return SecurityUtils.getFingerprint(k).equals(md5);
try {
if (fingerprint.startsWith("SHA1:")) {
return new FingerprintVerifier("SHA-1", fingerprint.substring(5));
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
if (fingerprint.startsWith("SHA256:")) {
return new FingerprintVerifier("SHA-256", fingerprint.substring(7));
}
});
final String md5;
if (fingerprint.startsWith("MD5:")) {
md5 = fingerprint.substring(4); // remove the MD5: prefix
} else {
md5 = fingerprint;
}
if (!MD5_FINGERPRINT_PATTERN.matcher(md5).matches()) {
throw new SSHRuntimeException("Invalid MD5 fingerprint: " + fingerprint);
}
// Use the old default fingerprint verifier for md5 fingerprints
return (new HostKeyVerifier() {
@Override
public boolean verify(String h, int p, PublicKey k) {
return SecurityUtils.getFingerprint(k).equals(md5);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
});
} catch (SSHRuntimeException e) {
throw e;
} catch (IOException e) {
throw new SSHRuntimeException(e);
}
}
private final String digestAlgorithm;
@@ -90,8 +99,10 @@ public class FingerprintVerifier implements HostKeyVerifier {
* the used digest algorithm
* @param base64Fingerprint
* base64 encoded fingerprint data
*
* @throws IOException
*/
private FingerprintVerifier(String digestAlgorithm, String base64Fingerprint) {
private FingerprintVerifier(String digestAlgorithm, String base64Fingerprint) throws IOException {
this.digestAlgorithm = digestAlgorithm;
// if the length is not padded with "=" chars at the end so that it is divisible by 4 the SSHJ Base64 implementation does not work correctly
@@ -99,7 +110,7 @@ public class FingerprintVerifier implements HostKeyVerifier {
while (base64FingerprintBuilder.length() % 4 != 0) {
base64FingerprintBuilder.append("=");
}
fingerprintData = Base64.getDecoder().decode(base64FingerprintBuilder.toString());
fingerprintData = Base64.decode(base64FingerprintBuilder.toString());
}
@Override

View File

@@ -18,30 +18,15 @@ package net.schmizz.sshj.transport.verification;
import com.hierynomus.sshj.common.KeyAlgorithm;
import com.hierynomus.sshj.transport.verification.KnownHostMatchers;
import com.hierynomus.sshj.userauth.certificate.Certificate;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.common.*;
import org.slf4j.Logger;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.*;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
/**
@@ -289,7 +274,7 @@ public class OpenSSHKnownHosts
if (type != KeyType.UNKNOWN) {
final String sKey = split[i++];
try {
byte[] keyBytes = Base64.getDecoder().decode(sKey);
byte[] keyBytes = Base64.decode(sKey);
key = new Buffer.PlainBuffer(keyBytes).readPublicKey();
} catch (IOException ioe) {
log.warn("Error decoding Base64 key bytes", ioe);
@@ -468,7 +453,8 @@ public class OpenSSHKnownHosts
}
private String getKeyString(PublicKey pk) {
return Base64.getEncoder().encodeToString(pk.getEncoded());
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(pk);
return Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
}
protected String getHostPart() {

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.userauth;
public enum AuthResult {
SUCCESS,
FAILURE,
PARTIAL
}

View File

@@ -37,12 +37,12 @@ public interface UserAuth {
* @param nextService the service to set on successful authentication
* @param methods the {@link AuthMethod}'s to try
*
* @return whether authentication was successful
* @return whether authentication was successful, failed, or partially successful
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
boolean authenticate(String username, Service nextService, AuthMethod methods, int timeoutMs)
AuthResult authenticate(String username, Service nextService, AuthMethod methods, int timeoutMs)
throws UserAuthException, TransportException;
/**

View File

@@ -40,7 +40,7 @@ public class UserAuthImpl
extends AbstractService
implements UserAuth {
private final Promise<Boolean, UserAuthException> authenticated;
private final Promise<AuthResult, UserAuthException> authenticated;
// Externally available
private volatile String banner = "";
@@ -53,13 +53,13 @@ public class UserAuthImpl
public UserAuthImpl(Transport trans) {
super("ssh-userauth", trans);
authenticated = new Promise<Boolean, UserAuthException>("authenticated", UserAuthException.chainer, trans.getConfig().getLoggerFactory());
authenticated = new Promise<AuthResult, UserAuthException>("authenticated", UserAuthException.chainer, trans.getConfig().getLoggerFactory());
}
@Override
public boolean authenticate(String username, Service nextService, AuthMethod method, int timeoutMs)
public AuthResult authenticate(String username, Service nextService, AuthMethod method, int timeoutMs)
throws UserAuthException, TransportException {
final boolean outcome;
final AuthResult outcome;
authenticated.lock();
try {
@@ -73,8 +73,10 @@ public class UserAuthImpl
currentMethod.request();
outcome = authenticated.retrieve(timeoutMs, TimeUnit.MILLISECONDS);
if (outcome) {
if (outcome == AuthResult.SUCCESS) {
log.debug("`{}` auth successful", method.getName());
} else if (outcome == AuthResult.PARTIAL) {
log.debug("`{}` auth partially successful", method.getName());
} else {
log.debug("`{}` auth failed", method.getName());
}
@@ -124,7 +126,7 @@ public class UserAuthImpl
// Should fix https://github.com/hierynomus/sshj/issues/237
trans.setAuthenticated(); // So it can put delayed compression into force if applicable
trans.setService(nextService); // We aren't in charge anymore, next service is
authenticated.deliver(true);
authenticated.deliver(AuthResult.SUCCESS);
break;
case USERAUTH_FAILURE:
@@ -133,7 +135,7 @@ public class UserAuthImpl
if (allowedMethods.contains(currentMethod.getName()) && currentMethod.shouldRetry()) {
currentMethod.request();
} else {
authenticated.deliver(false);
authenticated.deliver(partialSuccess ? AuthResult.PARTIAL : AuthResult.FAILURE);
}
break;

View File

@@ -22,6 +22,7 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SecurityUtils;
@@ -42,8 +43,9 @@ import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
@@ -241,7 +243,7 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException("Invalid key file format: missing \"PuTTY-User-Key-File-?\" entry");
}
// Retrieve keys from payload
publicKey = Base64.getDecoder().decode(payload.get("Public-Lines"));
publicKey = Base64.decode(payload.get("Public-Lines"));
if (this.isEncrypted()) {
final char[] passphrase;
if (pwdf != null) {
@@ -250,7 +252,7 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
passphrase = "".toCharArray();
}
try {
privateKey = this.decrypt(Base64.getDecoder().decode(payload.get("Private-Lines")), passphrase);
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), passphrase);
Mac mac;
if (this.keyFileVersion <= 2) {
mac = this.prepareVerifyMacV2(passphrase);
@@ -262,7 +264,7 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
PasswordUtils.blankOut(passphrase);
}
} else {
privateKey = Base64.getDecoder().decode(payload.get("Private-Lines"));
privateKey = Base64.decode(payload.get("Private-Lines"));
}
}

View File

@@ -16,6 +16,7 @@
package com.hierynomus.sshj.common
import com.hierynomus.sshj.userauth.certificate.Certificate
import net.schmizz.sshj.common.Base64
import net.schmizz.sshj.common.Buffer
import net.schmizz.sshj.common.KeyType
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile
@@ -109,7 +110,7 @@ f26VSnEypH3G3cmPYfpVcXL63bCb0E4sNJwENM4tQGZa5YGz3CxMdgIVAJUv4z9+
def keyType = KeyType.fromString(parts[0])
when:
def pubKey = new Buffer.PlainBuffer(Base64.getDecoder().decode(parts[1])).readPublicKey()
def pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey()
then:
KeyType.fromKey(pubKey) == keyType

View File

@@ -15,18 +15,18 @@
*/
package com.hierynomus.sshj.connection.channel.direct
import com.hierynomus.sshj.test.SshServerExtension
import com.hierynomus.sshj.test.SshFixture
import net.schmizz.sshj.connection.channel.direct.Parameters
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.Rule
import spock.lang.Specification
import spock.util.concurrent.PollingConditions
class LocalPortForwarderSpec extends Specification {
@RegisterExtension
SshServerExtension tunnelFixture = new SshServerExtension()
@Rule
SshFixture tunnelFixture = new SshFixture()
@RegisterExtension
SshServerExtension realServer = new SshServerExtension()
@Rule
SshFixture realServer = new SshFixture()
def "should not hang when disconnect tunnel"() {
given:

View File

@@ -15,29 +15,25 @@
*/
package com.hierynomus.sshj.sftp
import com.hierynomus.sshj.test.SshServerExtension
import com.hierynomus.sshj.test.SshFixture
import com.hierynomus.sshj.test.util.FileUtil
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.sftp.FileMode
import net.schmizz.sshj.sftp.SFTPClient
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import spock.lang.TempDir
import spock.lang.Unroll
import java.nio.file.Files
import java.nio.file.Path
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
class SFTPClientSpec extends Specification {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension()
@Rule
public SshFixture fixture = new SshFixture()
@TempDir
public Path temp
@Rule
public TemporaryFolder temp = new TemporaryFolder()
@Unroll
def "should copy #sourceType->#targetType if #targetExists with #named name"() {
@@ -83,11 +79,11 @@ class SFTPClientSpec extends Specification {
def "should not throw exception on close before disconnect"() {
given:
File file = Files.createFile(temp.resolve("source.txt")).toFile()
File file = temp.newFile("source.txt")
FileUtil.writeToFile(file, "This is the source")
when:
doUpload(file, temp.resolve("dest.txt").toFile())
doUpload(file, temp.newFile("dest.txt"))
then:
noExceptionThrown()
@@ -151,10 +147,10 @@ class SFTPClientSpec extends Specification {
def "should not merge same name subdirs (GH #252)"() {
given:
File toto = Files.createDirectory(temp.resolve("toto")).toFile()
File toto = temp.newFolder("toto")
File tutu = mkdir(toto, "tutu")
File toto2 = mkdir(toto, "toto")
File dest = Files.createDirectory(temp.resolve("dest")).toFile()
File dest = temp.newFolder("dest")
FileUtil.writeToFile(new File(toto, "toto.txt"), "Toto file")
FileUtil.writeToFile(new File(tutu, "tototutu.txt"), "Toto/Tutu file")
FileUtil.writeToFile(new File(toto2, "totototo.txt"), "Toto/Toto file")

View File

@@ -15,19 +15,21 @@
*/
package com.hierynomus.sshj.transport.verification
import net.schmizz.sshj.common.Base64
import net.schmizz.sshj.common.Buffer
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts
import net.schmizz.sshj.util.KeyUtil
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import spock.lang.TempDir
import spock.lang.Unroll
import java.nio.file.Files
import java.security.PublicKey
class OpenSSHKnownHostsSpec extends Specification {
@TempDir def temp
@Rule
public TemporaryFolder temp = new TemporaryFolder();
def "should parse and verify hashed host entry"() {
given:
@@ -63,7 +65,7 @@ class OpenSSHKnownHostsSpec extends Specification {
host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCiYp2IDgzDFhl8T4TRLIhEljvEixz1YN0XWh4dYh0REGK9T4QKiyb28EztPMdcOtz1uyX5rUGYXX9hj99S4SiU=
host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTjA7hduYGmvV9smEEsIdGLdghSPD7kL8QarIIOkeXmBh+LTtT/T1K+Ot/rmXCZsP8hoUXxbvN+Tks440Ci0ck=
""")
def pk = new Buffer.PlainBuffer(Base64.getDecoder().decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTjA7hduYGmvV9smEEsIdGLdghSPD7kL8QarIIOkeXmBh+LTtT/T1K+Ot/rmXCZsP8hoUXxbvN+Tks440Ci0ck=")).readPublicKey()
def pk = new Buffer.PlainBuffer(Base64.decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTjA7hduYGmvV9smEEsIdGLdghSPD7kL8QarIIOkeXmBh+LTtT/T1K+Ot/rmXCZsP8hoUXxbvN+Tks440Ci0ck=")).readPublicKey()
when:
def knownhosts = new OpenSSHKnownHosts(f)
@@ -77,7 +79,7 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL
host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTIDgzDFhl8T4TRLIhEljvEixz1YN0XWh4dYh0REGK9T4QKiyb28EztPMdcOtz1uyX5rUGYXX9hj99S4SiU=
host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTjA7hduYGmvV9smEEsIdGLdghSPD7kL8QarIIOkeXmBh+LTtT/T1K+Ot/rmXCZsP8hoUXxbvN+Tks440Ci0ck=
""")
def pk = new Buffer.PlainBuffer(Base64.getDecoder().decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTjA7hduYGmvV9smEEsIdGLdghSPD7kL8QarIIOkeXmBh+LTtT/T1K+Ot/rmXCZsP8hoUXxbvN+Tks440Ci0ck=")).readPublicKey()
def pk = new Buffer.PlainBuffer(Base64.decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTjA7hduYGmvV9smEEsIdGLdghSPD7kL8QarIIOkeXmBh+LTtT/T1K+Ot/rmXCZsP8hoUXxbvN+Tks440Ci0ck=")).readPublicKey()
when:
def knownhosts = new OpenSSHKnownHosts(f)
@@ -148,7 +150,7 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL
def "should match any host name from multi-host line"() {
given:
def f = knownHosts("schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==")
def pk = new Buffer.PlainBuffer(Base64.getDecoder().decode("AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==")).readPublicKey()
def pk = new Buffer.PlainBuffer(Base64.decode("AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==")).readPublicKey()
when:
def knownHosts = new OpenSSHKnownHosts(f)
@@ -182,7 +184,7 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL
|\t\t\t\t\t
|\t@revoked host3\tssh-ed25519\t \t$key\t
""".stripMargin())
def pk = new Buffer.PlainBuffer(Base64.getDecoder().decode(key)).readPublicKey()
def pk = new Buffer.PlainBuffer(Base64.decode(key)).readPublicKey()
when:
def knownhosts = new OpenSSHKnownHosts(f)
@@ -210,11 +212,11 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL
def knownhosts = new OpenSSHKnownHosts(f)
then:
knownhosts.verify("good-host", 22, new Buffer.PlainBuffer(Base64.getDecoder().decode(key)).readPublicKey())
knownhosts.verify("good-host", 22, new Buffer.PlainBuffer(Base64.decode(key)).readPublicKey())
}
def knownHosts(String s) {
def f = Files.createFile(temp.resolve("known_hosts")).toFile()
def f = temp.newFile("known_hosts")
f.write(s)
return f
}

View File

@@ -15,18 +15,18 @@
*/
package com.hierynomus.sshj.userauth.keyprovider
import com.hierynomus.sshj.test.SshServerExtension
import com.hierynomus.sshj.test.SshFixture
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.userauth.keyprovider.KeyFormat
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.Rule
import spock.lang.Specification
import spock.lang.Unroll
class FileKeyProviderSpec extends Specification {
@RegisterExtension
SshServerExtension fixture = new SshServerExtension(false)
@Rule
SshFixture fixture = new SshFixture(false)
def setup() {
fixture.getServer().setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE)

View File

@@ -31,12 +31,20 @@
package net.schmizz.sshj.signature
import com.hierynomus.sshj.common.KeyAlgorithm
import spock.lang.Specification
import spock.lang.Unroll
import spock.lang.Unroll;
import java.security.KeyFactory
import java.security.PublicKey
import java.security.spec.DSAPublicKeySpec
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import spock.lang.Specification
class SignatureDSASpec extends Specification {

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.transport.verification
import net.schmizz.sshj.common.Base64
import net.schmizz.sshj.common.Buffer
import spock.lang.Specification
import spock.lang.Unroll
@@ -62,6 +63,6 @@ class FingerprintVerifierSpec extends Specification {
def getPublicKey() {
def lines = new File("src/test/resources/keytypes/test_ed25519.pub").readLines()
def keystring = lines[0].split(" ")[1]
return new Buffer.PlainBuffer(Base64.getDecoder().decode(keystring)).readPublicKey()
return new Buffer.PlainBuffer(Base64.decode(keystring)).readPublicKey()
}
}

View File

@@ -15,10 +15,10 @@
*/
package com.hierynomus.sshj.connection.channel;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.connection.channel.direct.Session;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
@@ -26,8 +26,8 @@ import static org.hamcrest.MatcherAssert.assertThat;
public class ChannelCloseEofTest {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@Test
public void shouldCorrectlyHandleSessionChannelEof() throws IOException, InterruptedException {

View File

@@ -15,13 +15,13 @@
*/
package com.hierynomus.sshj.connection.channel.direct;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.sftp.SFTPClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
@@ -30,17 +30,17 @@ import static org.hamcrest.MatcherAssert.assertThat;
public class CommandTest {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@TempDir
public File temp;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void shouldExecuteBackgroundCommand() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("jeroen", "jeroen");
File file = new File(temp, "testdir");
File file = new File(temp.getRoot(), "testdir");
assertThat("File should not exist", !file.exists());
// TODO figure out why this does not really execute in the background.
Session.Command exec = sshClient.startSession().exec("mkdir " + file.getPath() + " &");

View File

@@ -16,42 +16,51 @@
package com.hierynomus.sshj.connection.channel.forwarded;
import com.hierynomus.sshj.test.HttpServer;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import com.hierynomus.sshj.test.util.FileUtil;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder;
import net.schmizz.sshj.connection.channel.direct.Parameters;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class LocalPortForwarderTest {
private static final String LOCALHOST_URL = "http://127.0.0.1:8080";
private static final Logger log = LoggerFactory.getLogger(LocalPortForwarderTest.class);
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@RegisterExtension
@Rule
public HttpServer httpServer = new HttpServer();
@BeforeEach
@Before
public void setUp() throws IOException {
fixture.getServer().setForwardingFilter(new AcceptAllForwardingFilter());
File file = Files.createFile(httpServer.getDocRoot().toPath().resolve("index.html")).toFile();
File file = httpServer.getDocRoot().newFile("index.html");
FileUtil.writeToFile(file, "<html><head/><body><h1>Hi!</h1></body></html>");
}
@Test
public void shouldHaveWorkingHttpServer() throws IOException {
assertEquals(200, httpGet());
// Just to check that we have a working http server...
assertThat(httpGet("127.0.0.1", 8080), equalTo(200));
}
@Test
@@ -60,8 +69,7 @@ public class LocalPortForwarderTest {
httpGetAndAssertConnectionClosedByServer(8080);
}
@Test
@Timeout(10_000)
@Test(timeout = 10_000)
public void shouldCloseConnectionWhenRemoteServerClosesConnection() throws IOException {
SSHClient sshClient = getFixtureClient();
@@ -85,6 +93,7 @@ public class LocalPortForwarderTest {
}
public static void httpGetAndAssertConnectionClosedByServer(int port) throws IOException {
System.out.println("HTTP GET to port: " + port);
try (Socket socket = new Socket("localhost", port)) {
// Send a basic HTTP GET
// It returns 400 Bad Request because it's missing a bunch of info, but the HTTP response doesn't matter, we just want to test the connection closing.
@@ -98,24 +107,28 @@ public class LocalPortForwarderTest {
InputStream inputStream = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream);
int buf = -2;
while (buf != -1) {
while (true) {
buf = reader.read();
System.out.print((char)buf);
if (buf == -1) {
break;
}
}
// Attempt to read more. If the server has closed the connection this will return -1
int read = inputStream.read();
// Assert input stream was closed by server.
assertEquals(-1, read);
Assert.assertEquals(-1, read);
}
}
private int httpGet() throws IOException {
final URL url = new URL(LOCALHOST_URL);
final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("GET");
return urlConnection.getResponseCode();
private int httpGet(String server, int port) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
String urlString = "http://" + server + ":" + port;
log.info("Trying: GET " + urlString);
HttpResponse execute = client.execute(new HttpGet(urlString));
return execute.getStatusLine().getStatusCode();
}
private SSHClient getFixtureClient() throws IOException {

View File

@@ -16,48 +16,53 @@
package com.hierynomus.sshj.connection.channel.forwarded;
import com.hierynomus.sshj.test.HttpServer;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import com.hierynomus.sshj.test.util.FileUtil;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder;
import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.file.Files;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertEquals;
public class RemotePortForwarderTest {
private static final Logger log = LoggerFactory.getLogger(RemotePortForwarderTest.class);
private static final PortRange RANGE = new PortRange(9000, 9999);
private static final String LOCALHOST = "127.0.0.1";
private static final String LOCALHOST_URL_FORMAT = "http://127.0.0.1:%d";
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress(LOCALHOST, 8080);
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@RegisterExtension
@Rule
public HttpServer httpServer = new HttpServer();
@BeforeEach
@Before
public void setUp() throws IOException {
fixture.getServer().setForwardingFilter(new AcceptAllForwardingFilter());
File file = Files.createFile(httpServer.getDocRoot().toPath().resolve("index.html")).toFile();
File file = httpServer.getDocRoot().newFile("index.html");
FileUtil.writeToFile(file, "<html><head/><body><h1>Hi!</h1></body></html>");
}
@Test
public void shouldHaveWorkingHttpServer() throws IOException {
assertEquals(200, httpGet(8080));
// Just to check that we have a working http server...
assertEquals(200, httpGet( 8080));
}
@Test
@@ -122,12 +127,12 @@ public class RemotePortForwarderTest {
}
}
private int httpGet(final int port) throws IOException {
final URL url = new URL(String.format(LOCALHOST_URL_FORMAT, port));
final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("GET");
return urlConnection.getResponseCode();
private int httpGet(int port) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
String urlString = "http://" + LOCALHOST + ":" + port;
log.info("Trying: GET " + urlString);
HttpResponse execute = client.execute(new HttpGet(urlString));
return execute.getStatusLine().getStatusCode();
}
private SSHClient getFixtureClient() throws IOException {

View File

@@ -15,18 +15,21 @@
*/
package com.hierynomus.sshj.keepalive;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.keepalive.KeepAlive;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.UserAuthException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class KeepAliveThreadTerminationTest {
@@ -34,8 +37,8 @@ public class KeepAliveThreadTerminationTest {
private static final long STOP_SLEEP = 1500;
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@Test
public void shouldNotStartThreadOnSetKeepAliveInterval() {

View File

@@ -15,7 +15,7 @@
*/
package com.hierynomus.sshj.sftp;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.sftp.OpenMode;
@@ -23,9 +23,9 @@ import net.schmizz.sshj.sftp.RemoteFile;
import net.schmizz.sshj.sftp.SFTPEngine;
import net.schmizz.sshj.sftp.SFTPException;
import org.apache.sshd.common.util.io.IoUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.*;
import java.security.SecureRandom;
@@ -34,15 +34,15 @@ import java.util.Random;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class RemoteFileTest {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@TempDir
public File temp;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void shouldNotGoOutOfBoundsInReadAheadInputStream() throws IOException {
@@ -51,7 +51,7 @@ public class RemoteFileTest {
SFTPEngine sftp = new SFTPEngine(ssh).init();
RemoteFile rf;
File file = new File(temp, "SftpReadAheadTest.bin");
File file = temp.newFile("SftpReadAheadTest.bin");
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
byte[] data = new byte[8192];
new Random(53).nextBytes(data);
@@ -96,7 +96,7 @@ public class RemoteFileTest {
SFTPEngine sftp = new SFTPEngine(ssh).init();
RemoteFile rf;
File file = new File(temp, "SftpReadAheadLimitTest.bin");
File file = temp.newFile("SftpReadAheadLimitTest.bin");
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
byte[] data = new byte[8192];
new Random(53).nextBytes(data);
@@ -139,7 +139,7 @@ public class RemoteFileTest {
SFTPEngine sftp = new SFTPEngine(ssh).init();
RemoteFile rf;
File file = new File(temp, "SftpReadAheadLimitedTest.bin");
File file = temp.newFile("SftpReadAheadLimitedTest.bin");
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
byte[] data = new byte[8192];
new Random(53).nextBytes(data);
@@ -205,7 +205,7 @@ public class RemoteFileTest {
final byte[] expected = new byte[fileSize];
new SecureRandom(new byte[] { 31 }).nextBytes(expected);
File file = new File(temp, "shouldReadCorrectlyWhenWrappedInBufferedStream.bin");
File file = temp.newFile("shouldReadCorrectlyWhenWrappedInBufferedStream.bin");
try (OutputStream fStream = new FileOutputStream(file)) {
IoUtils.copy(new ByteArrayInputStream(expected), fStream);
}
@@ -221,7 +221,7 @@ public class RemoteFileTest {
actual = baos.toByteArray();
}
assertEquals(expected.length, actual.length, "The file should be fully read");
assertEquals("The file should be fully read", expected.length, actual.length);
assertThat("The file should be read correctly",
ByteArrayUtils.equals(expected, 0, actual, 0, expected.length));
}

View File

@@ -21,8 +21,9 @@ import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
import org.apache.sshd.server.SshServer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,8 +36,13 @@ public abstract class BaseAlgorithmTest {
private SingletonRandomFactory randomFactory = new SingletonRandomFactory(new JCERandom.Factory());
private DefaultConfig config = new DefaultConfig();
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension(false);
@Rule
public SshFixture fixture = new SshFixture(false);
@After
public void stopServer() {
fixture.stopServer();
}
@Test
public void shouldVerifyAlgorithm() throws IOException {

View File

@@ -15,25 +15,27 @@
*/
package com.hierynomus.sshj.test;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.io.File;
import java.nio.file.Files;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
/**
* Can be used to setup a test HTTP server
*/
public class HttpServer implements BeforeEachCallback, AfterEachCallback {
public class HttpServer extends ExternalResource {
private org.glassfish.grizzly.http.server.HttpServer httpServer;
private File docRoot ;
private TemporaryFolder docRoot = new TemporaryFolder();
@Override
public void afterEach(ExtensionContext context) throws Exception {
protected void before() throws Throwable {
docRoot.create();
httpServer = org.glassfish.grizzly.http.server.HttpServer.createSimpleServer(docRoot.getRoot().getAbsolutePath());
httpServer.start();
}
@Override
protected void after() {
try {
httpServer.shutdownNow();
} catch (Exception e) {}
@@ -43,18 +45,11 @@ public class HttpServer implements BeforeEachCallback, AfterEachCallback {
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
docRoot = Files.createTempDirectory("sshj").toFile();
httpServer = org.glassfish.grizzly.http.server.HttpServer.createSimpleServer(docRoot.getAbsolutePath());
httpServer.start();
}
public org.glassfish.grizzly.http.server.HttpServer getHttpServer() {
return httpServer;
}
public File getDocRoot() {
public TemporaryFolder getDocRoot() {
return docRoot;
}
}

View File

@@ -25,11 +25,8 @@ import org.apache.sshd.scp.server.ScpCommandFactory;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.shell.ProcessShellFactory;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.rules.ExternalResource;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
@@ -39,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* Can be used as a rule to ensure the server is teared down after each test.
*/
public class SshServerExtension implements BeforeEachCallback, AfterEachCallback, Closeable {
public class SshFixture extends ExternalResource {
public static final String hostkey = "hostkey.pem";
public static final String fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
public static final String listCommand = OsUtils.isWin32() ? "cmd.exe /C dir" : "ls";
@@ -49,28 +46,22 @@ public class SshServerExtension implements BeforeEachCallback, AfterEachCallback
private final AtomicBoolean started = new AtomicBoolean(false);
private boolean autoStart = true;
public SshServerExtension(boolean autoStart) {
public SshFixture(boolean autoStart) {
this.autoStart = autoStart;
}
public SshServerExtension() {
public SshFixture() {
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
close();
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
protected void before() throws Throwable {
if (autoStart) {
start();
}
}
@Override
public void close() throws IOException {
protected void after() {
stopClient();
stopServer();
}

View File

@@ -15,15 +15,17 @@
*/
package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.transport.cipher.ChachaPolyCiphers;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
import com.hierynomus.sshj.transport.cipher.ChachaPolyCiphers;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class ChachaPolyCipherTest {

View File

@@ -15,30 +15,30 @@
*/
package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.transport.DisconnectListener;
import net.schmizz.sshj.transport.TransportException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class DisconnectionTest {
private AtomicBoolean disconnected = null;
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
@BeforeEach
@Before
public void setupFlag() throws IOException {
disconnected = new AtomicBoolean(false);
// Initialize the client

View File

@@ -20,58 +20,48 @@ import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import java.security.Security;
import java.util.Arrays;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.Assert.assertArrayEquals;
/**
* Unit test to decrypt SSH traffic with OpenSSH and Apache Mina SSHD (master) using AES-GCM ciphers, for verifying
* cipher behaviour.
*/
@RunWith(Theories.class)
public class GcmCipherDecryptSshPacketTest {
public static Stream<Arguments> sets() {
return Stream.of(Arguments.of("mina-sshd", 3), Arguments.of("openssh", 4));
}
@DataPoints
public static final String sets[][] = new String[][]{{"mina-sshd", "3"}, {"openssh", "4"}};
@BeforeAll
@BeforeClass
public static void setupBeforeClass() {
Security.addProvider(new BouncyCastleProvider());
}
@ParameterizedTest
@MethodSource("sets")
public void testDecryptPacket(String ssh, int nr) throws Exception {
@Theory
public void testDecryptPacket(String[] args) throws Exception {
ClassLoader classLoader = getClass().getClassLoader();
byte[] iv = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/" + ssh + "/s2c.iv.bin"))
.toByteArray();
byte[] key = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/" + ssh + "/s2c.key.bin"))
.toByteArray();
byte[] iv = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/s2c.iv.bin")).toByteArray();
byte[] key = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/s2c.key.bin")).toByteArray();
Cipher cipher = GcmCiphers.AES128GCM().create();
cipher.init(Cipher.Mode.Decrypt, key, iv);
for (int i = 1; i <= nr; i++) {
byte[] data = IOUtils
.readFully(classLoader
.getResourceAsStream("ssh-packets/gcm/" + ssh + "/client.receive." + i + ".bin"))
.toByteArray();
for(int i=1; i<=Integer.parseInt(args[1]); i++) {
byte[] data = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/client.receive."+i+".bin")).toByteArray();
SSHPacket inputBuffer = new SSHPacket(data);
cipher.updateAAD(inputBuffer.array(), 0, 4);
int size = inputBuffer.readUInt32AsInt();
cipher.update(inputBuffer.array(), 4, size);
byte[] expected = IOUtils
.readFully(classLoader
.getResourceAsStream("ssh-packets/gcm/" + ssh + "/client.decrypted." + i + ".bin"))
.toByteArray();
assertArrayEquals(Arrays.copyOfRange(expected, 0, size + 4),
Arrays.copyOfRange(inputBuffer.array(), 0, size + 4));
byte[] expected = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/client.decrypted."+i+".bin")).toByteArray();
assertArrayEquals(Arrays.copyOfRange(expected, 0, size+4),
Arrays.copyOfRange(inputBuffer.array(), 0, size+4));
}
}
}

View File

@@ -18,21 +18,23 @@ package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import javax.crypto.AEADBadTagException;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.*;
@RunWith(Theories.class)
public class GcmCipherTest {
public static GcmCiphers.Factory[] cipherFactories() {
return new GcmCiphers.Factory[] { GcmCiphers.AES128GCM(), GcmCiphers.AES256GCM() }; };
public static final @DataPoints
GcmCiphers.Factory[] cipherFactories = { GcmCiphers.AES128GCM(), GcmCiphers.AES256GCM() };
@ParameterizedTest
@MethodSource("cipherFactories")
@Theory
public void testEncryptDecrypt(GcmCiphers.Factory factory) throws Exception {
Cipher enc = factory.create();
byte[] key = new byte[enc.getBlockSize()];

View File

@@ -15,76 +15,63 @@
*/
package com.hierynomus.sshj.transport.kex;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.BaseAlgorithmTest;
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.DHGexSHA1;
import net.schmizz.sshj.transport.kex.DHGexSHA256;
import net.schmizz.sshj.transport.kex.ECDHNistP;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.kex.DHGEXServer;
import org.apache.sshd.server.kex.DHGServer;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
public class KeyExchangeTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@RunWith(Parameterized.class)
public class KeyExchangeTest extends BaseAlgorithmTest {
@SuppressWarnings("deprecation")
@Parameterized.Parameters(name = "algorithm={0}")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ DHGEXServer.newFactory(BuiltinDHFactories.dhgex), new DHGexSHA1.Factory() },
{ DHGEXServer.newFactory(BuiltinDHFactories.dhgex256), new DHGexSHA256.Factory() },
{ DHGServer.newFactory(BuiltinDHFactories.ecdhp256), new ECDHNistP.Factory256() },
{ DHGServer.newFactory(BuiltinDHFactories.ecdhp384), new ECDHNistP.Factory384() },
{ DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg1), DHGroups.Group1SHA1() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg14), DHGroups.Group14SHA1() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg14_256), DHGroups.Group14SHA256() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg15_512), DHGroups.Group15SHA512() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg16_512), DHGroups.Group16SHA512() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg17_512), DHGroups.Group17SHA512() },
{ DHGServer.newFactory(BuiltinDHFactories.dhg18_512), DHGroups.Group18SHA512() },
return Arrays.asList(new Object[][]{
{DHGEXServer.newFactory(BuiltinDHFactories.dhgex), new DHGexSHA1.Factory()},
{DHGEXServer.newFactory(BuiltinDHFactories.dhgex256), new DHGexSHA256.Factory()},
{DHGServer.newFactory(BuiltinDHFactories.ecdhp256), new ECDHNistP.Factory256()},
{DHGServer.newFactory(BuiltinDHFactories.ecdhp384), new ECDHNistP.Factory384()},
{DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521()},
{DHGServer.newFactory(BuiltinDHFactories.dhg1), DHGroups.Group1SHA1()},
{DHGServer.newFactory(BuiltinDHFactories.dhg14), DHGroups.Group14SHA1()},
{DHGServer.newFactory(BuiltinDHFactories.dhg14_256), DHGroups.Group14SHA256()},
{DHGServer.newFactory(BuiltinDHFactories.dhg15_512), DHGroups.Group15SHA512()},
{DHGServer.newFactory(BuiltinDHFactories.dhg16_512), DHGroups.Group16SHA512()},
{DHGServer.newFactory(BuiltinDHFactories.dhg17_512), DHGroups.Group17SHA512()},
{DHGServer.newFactory(BuiltinDHFactories.dhg18_512), DHGroups.Group18SHA512()},
});
}
@TestFactory
public Stream<DynamicTest> keyExchangeTests() {
return getParameters().stream().map(params -> {
KeyExchangeFactory serverFactory = (KeyExchangeFactory) params[0];
Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory = (Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange>) params[1];
SingletonRandomFactory randomFactory = new SingletonRandomFactory(new JCERandom.Factory());
private final Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
private final KeyExchangeFactory serverFactory;
return DynamicTest.dynamicTest(serverFactory.getName() + " <-> " + clientFactory.getName(), () -> {
try (SshServerExtension fixture = new SshServerExtension(false)) {
for (int i = 0; i < 10; i++) {
logger.info("--> Attempt {}", i);
fixture.getServer().setKeyExchangeFactories(Collections.singletonList(serverFactory));
fixture.start();
Config config = new DefaultConfig();
config.setRandomFactory(randomFactory);
config.setKeyExchangeFactories(Collections.singletonList(clientFactory));
public KeyExchangeTest(KeyExchangeFactory serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
this.clientFactory = clientFactory;
this.serverFactory = serverFactory;
}
SSHClient sshClient = fixture.connectClient(fixture.setupClient(config));
assertThat("should be connected", sshClient.isConnected());
sshClient.disconnect();
fixture.stopClient();
};
};
});
});
@Override
protected Config getClientConfig(DefaultConfig config) {
config.setKeyExchangeFactories(Collections.singletonList(clientFactory));
return config;
}
@Override
protected void configureServer(SshServer server) {
server.setKeyExchangeFactories(Collections.singletonList(serverFactory));
}
}

View File

@@ -15,24 +15,24 @@
*/
package com.hierynomus.sshj.userauth;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
import net.schmizz.sshj.util.gss.BogusGSSManager;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Rule;
import org.junit.Test;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertTrue;
public class GssApiTest {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension();
@Rule
public SshFixture fixture = new SshFixture();
private static final String LOGIN_CONTEXT_NAME = "TestLoginContext";

View File

@@ -14,11 +14,13 @@
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* JUnit unit tests for BCrypt routines
@@ -26,6 +28,168 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* @version 0.2
*/
public class BCryptTest {
String[][] test_vectors = {
{ "",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
{ "",
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" },
{ "",
"$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
"$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
{ "",
"$2a$12$k42ZFHFWqBp3vWli.nIn8u",
"$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
{ "a",
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
{ "a",
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." },
{ "a",
"$2a$10$k87L/MF28Q673VKh8/cPi.",
"$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
{ "a",
"$2a$12$8NJH3LsPrANStV6XtBakCe",
"$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
{ "abc",
"$2a$06$If6bvum7DFjUnE9p2uDeDu",
"$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
{ "abc",
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" },
{ "abc",
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
{ "abc",
"$2a$12$EXRkfkdmXn2gzds2SSitu.",
"$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
{ "abcdefghijklmnopqrstuvwxyz",
"$2a$06$.rCVZVOThsIa97pEDOxvGu",
"$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
{ "abcdefghijklmnopqrstuvwxyz",
"$2a$08$aTsUwsyowQuzRrDqFflhge",
"$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." },
{ "abcdefghijklmnopqrstuvwxyz",
"$2a$10$fVH8e28OQRj9tqiDXs1e1u",
"$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
{ "abcdefghijklmnopqrstuvwxyz",
"$2a$12$D4G5f18o7aMMfwasBL7Gpu",
"$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
"$2a$06$fPIsBO8qRqkjj273rfaOI.",
"$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
"$2a$08$Eq2r4G/76Wv39MzSX262hu",
"$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" },
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
"$2a$12$WApznUOJfkEGSmYRfnkrPO",
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
};
/**
* Test method for 'BCrypt.hashpw(String, String)'
*/
@Test
public void testHashpw() {
System.out.print("BCrypt.hashpw(): ");
for (int i = 0; i < test_vectors.length; i++) {
String plain = test_vectors[i][0];
String salt = test_vectors[i][1];
String expected = test_vectors[i][2];
String hashed = BCrypt.hashpw(plain, salt);
assertEquals(hashed, expected);
System.out.print(".");
}
}
/**
* Test method for 'BCrypt.gensalt(int)'
*/
@Test
public void testGensaltInt() {
System.out.print("BCrypt.gensalt(log_rounds):");
for (int i = 4; i <= 12; i++) {
System.out.print(" " + i + ":");
for (int j = 0; j < test_vectors.length; j += 4) {
String plain = test_vectors[j][0];
String salt = BCrypt.gensalt(i);
String hashed1 = BCrypt.hashpw(plain, salt);
String hashed2 = BCrypt.hashpw(plain, hashed1);
assertEquals(hashed1, hashed2);
System.out.print(".");
}
}
}
/**
* Test method for 'BCrypt.gensalt()'
*/
@Test
public void testGensalt() {
System.out.print("BCrypt.gensalt(): ");
for (int i = 0; i < test_vectors.length; i += 4) {
String plain = test_vectors[i][0];
String salt = BCrypt.gensalt();
String hashed1 = BCrypt.hashpw(plain, salt);
String hashed2 = BCrypt.hashpw(plain, hashed1);
assertEquals(hashed1, hashed2);
System.out.print(".");
}
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting success
*/
@Test
public void testCheckpw_success() {
System.out.print("BCrypt.checkpw w/ good passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
String plain = test_vectors[i][0];
String expected = test_vectors[i][2];
assertTrue(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting failure
*/
@Test
public void testCheckpw_failure() {
System.out.print("BCrypt.checkpw w/ bad passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
int broken_index = (i + 4) % test_vectors.length;
String plain = test_vectors[i][0];
String expected = test_vectors[broken_index][2];
assertFalse(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
}
/**
* Test for correct hashing of non-US-ASCII passwords
*/
@Test
public void testInternationalChars() {
System.out.print("BCrypt.hashpw w/ international chars: ");
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
String pw2 = "????????";
String h1 = BCrypt.hashpw(pw1, BCrypt.gensalt());
assertFalse(BCrypt.checkpw(pw2, h1));
System.out.print(".");
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
assertFalse(BCrypt.checkpw(pw1, h2));
System.out.print(".");
}
private static class BCryptHashTV {
private final byte[] pass;

View File

@@ -15,16 +15,16 @@
*/
package com.hierynomus.sshj.userauth.method;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
import net.schmizz.sshj.userauth.method.ChallengeResponseProvider;
import net.schmizz.sshj.userauth.password.Resource;
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
@@ -34,10 +34,10 @@ import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
public class AuthKeyboardInteractiveTest {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension(false);
@Rule
public SshFixture fixture = new SshFixture(false);
@BeforeEach
@Before
public void setKeyboardInteractiveAuthenticator() throws IOException {
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthKeyboardInteractiveFactory()));
fixture.getServer().setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);

View File

@@ -15,7 +15,7 @@
*/
package com.hierynomus.sshj.userauth.method;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.UserAuthException;
import net.schmizz.sshj.userauth.password.PasswordFinder;
@@ -25,23 +25,23 @@ import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
import org.apache.sshd.server.session.ServerSession;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.util.Collections;
import java.util.Stack;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.Assert.assertThrows;
public class AuthPasswordTest {
@RegisterExtension
public SshServerExtension fixture = new SshServerExtension(false);
@Rule
public SshFixture fixture = new SshFixture(false);
@BeforeEach
@Before
public void setPasswordAuthenticator() throws IOException {
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthPasswordFactory()));
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {

View File

@@ -19,13 +19,13 @@ import net.schmizz.sshj.userauth.method.PasswordResponseProvider;
import net.schmizz.sshj.userauth.password.AccountResource;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.Resource;
import org.junit.jupiter.api.Test;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
import java.util.Collections;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
public class PasswordResponseProviderTest {
private static final char[] PASSWORD = "the_password".toCharArray();
private static final AccountResource ACCOUNT_RESOURCE = new AccountResource("user", "host");
@@ -64,13 +64,13 @@ public class PasswordResponseProviderTest {
@Test
public void shouldPassRetry() {
assertFalse(createDefaultResponseProvider(false).shouldRetry());
assertTrue(createDefaultResponseProvider(true).shouldRetry());
Assert.assertFalse(createDefaultResponseProvider(false).shouldRetry());
Assert.assertTrue(createDefaultResponseProvider(true).shouldRetry());
}
@Test
public void shouldHaveNoSubmethods() {
assertEquals(createDefaultResponseProvider(true).getSubmethods(), Collections.emptyList());
Assert.assertEquals(createDefaultResponseProvider(true).getSubmethods(), Collections.emptyList());
}
@Test
@@ -91,9 +91,10 @@ public class PasswordResponseProviderTest {
}
private static void checkPrompt(PasswordResponseProvider responseProvider, String prompt, char[] expected) {
assertArrayEquals(expected, responseProvider.getResponse(prompt, false), "Prompt '" + prompt + "'");
Assert.assertArrayEquals("Prompt '" + prompt + "'", expected, responseProvider.getResponse(prompt, false));
}
@NotNull
private static PasswordResponseProvider createDefaultResponseProvider(final boolean shouldRetry) {
PasswordFinder passwordFinder = new TestPasswordFinder(shouldRetry);
PasswordResponseProvider responseProvider = new PasswordResponseProvider(passwordFinder);
@@ -110,13 +111,13 @@ public class PasswordResponseProviderTest {
@Override
public char[] reqPassword(Resource<?> resource) {
assertEquals(resource, ACCOUNT_RESOURCE);
Assert.assertEquals(resource, ACCOUNT_RESOURCE);
return PASSWORD;
}
@Override
public boolean shouldRetry(Resource<?> resource) {
assertEquals(resource, ACCOUNT_RESOURCE);
Assert.assertEquals(resource, ACCOUNT_RESOURCE);
return shouldRetry;
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.SecurityUtils;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.Optional;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class DefaultSecurityProviderConfigTest {
private static Provider bouncyCastleProvider;
@BeforeAll
public static void removeProviders() {
bouncyCastleProvider = Security.getProvider(SecurityUtils.BOUNCY_CASTLE);
if (bouncyCastleProvider != null) {
Security.removeProvider(SecurityUtils.BOUNCY_CASTLE);
}
}
@AfterAll
public static void addProviders() {
if (bouncyCastleProvider != null) {
Security.addProvider(bouncyCastleProvider);
}
}
@Test
public void testBouncyCastleNotRegistered() {
new DefaultSecurityProviderConfig();
assertBouncyCastleNotRegistered();
}
private void assertBouncyCastleNotRegistered() {
final Optional<String> bouncyCastleFound = Arrays.stream(Security.getProviders())
.map(Provider::getName)
.filter(SecurityUtils.BOUNCY_CASTLE::contentEquals)
.findFirst();
assertFalse(bouncyCastleFound.isPresent());
}
}

View File

@@ -15,20 +15,21 @@
*/
package net.schmizz.sshj;
import com.hierynomus.sshj.test.SshServerExtension;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import org.junit.jupiter.api.Test;
import com.hierynomus.sshj.test.SshFixture;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.jupiter.api.Assertions.fail;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import static org.junit.Assert.fail;
public class LoadsOfConnects {
protected final Logger log = LoggerFactory.getLogger(getClass());
private final SshServerExtension fixture = new SshServerExtension();
private final SshFixture fixture = new SshFixture();
@Test
public void loadsOfConnects() {
@@ -39,7 +40,7 @@ public class LoadsOfConnects {
SSHClient client = fixture.setupConnectedDefaultClient();
client.authPassword("test", "test");
Session s = client.startSession();
Session.Command c = s.exec(SshServerExtension.listCommand);
Session.Command c = s.exec(SshFixture.listCommand);
IOUtils.readFully(c.getErrorStream());
IOUtils.readFully(c.getInputStream());
c.close();

View File

@@ -15,30 +15,30 @@
*/
package net.schmizz.sshj;
import com.hierynomus.sshj.test.SshServerExtension;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.userauth.UserAuthException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertTrue;
/* Kinda basic right now */
public class SmokeTest {
private final SshServerExtension fixture = new SshServerExtension();
private final SshFixture fixture = new SshFixture();
@BeforeEach
@Before
public void setUp()
throws IOException {
fixture.start();
fixture.setupConnectedDefaultClient();
}
@AfterEach
@After
public void tearDown()
throws IOException, InterruptedException {
fixture.stopClient();
@@ -58,4 +58,4 @@ public class SmokeTest {
assertTrue(fixture.getClient().isAuthenticated());
}
}
}

View File

@@ -17,11 +17,11 @@ package net.schmizz.sshj.common;
import net.schmizz.sshj.common.Buffer.BufferException;
import net.schmizz.sshj.common.Buffer.PlainBuffer;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.math.BigInteger;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.*;
public class BufferTest {
@@ -162,7 +162,7 @@ public class BufferTest {
for (long value : values) {
byte[] bytesBigInt = new PlainBuffer().putUInt64(BigInteger.valueOf(value)).getCompactData();
byte[] bytesLong = new PlainBuffer().putUInt64(value).getCompactData();
assertArrayEquals(bytesLong, bytesBigInt, "Value: " + value);
assertArrayEquals("Value: " + value, bytesLong, bytesBigInt);
}
}

View File

@@ -15,13 +15,13 @@
*/
package net.schmizz.sshj.common;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.io.*;
import java.util.Random;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;

View File

@@ -18,7 +18,7 @@ package net.schmizz.sshj.connection.channel;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.ExceptionChainer;
import net.schmizz.sshj.common.LoggerFactory;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.io.IOException;
import java.net.Socket;
@@ -86,4 +86,4 @@ public class SocketStreamCopyMonitorTest {
}
}, LoggerFactory.DEFAULT);
}
}
}

View File

@@ -15,12 +15,12 @@
*/
package net.schmizz.sshj.connection.channel.direct;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.*;
public class ParametersTest {

View File

@@ -17,12 +17,12 @@ package net.schmizz.sshj.keyprovider;
import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertEquals;
public class KeyProviderUtilTest {

View File

@@ -19,17 +19,27 @@ import com.hierynomus.sshj.common.KeyDecryptionFailedException;
import com.hierynomus.sshj.userauth.certificate.Certificate;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import net.schmizz.sshj.userauth.password.Resource;
import net.schmizz.sshj.util.KeyUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
@@ -44,7 +54,7 @@ import java.util.*;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.Assert.*;
public class OpenSSHKeyFileTest {
@@ -131,8 +141,8 @@ public class OpenSSHKeyFileTest {
}
};
@TempDir
public File temporaryFolder;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void blankingOut()
@@ -244,9 +254,8 @@ public class OpenSSHKeyFileTest {
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes128cbc.pem", "sshjtest", true);
}
@Test
@Test(expected = KeyDecryptionFailedException.class)
public void shouldFailOnIncorrectPassphraseAfterRetries() throws IOException {
assertThrows(KeyDecryptionFailedException.class, () -> {
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
keyFile.init(new File("src/test/resources/keytypes/ed25519_aes256cbc.pem"), new PasswordFinder() {
private int reqCounter = 0;
@@ -263,8 +272,6 @@ public class OpenSSHKeyFileTest {
}
});
keyFile.getPrivate();
});
}
@Test
@@ -288,20 +295,20 @@ public class OpenSSHKeyFileTest {
assertTrue(privateKey instanceof RSAPrivateCrtKey);
final RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) privateKey;
assertEquals(rsaPublicKey.getPublicExponent(), rsaPrivateCrtKey.getPublicExponent(), "Public Key Exponent not matched");
assertEquals(rsaPublicKey.getModulus(), rsaPrivateCrtKey.getModulus(), "Public Key Modulus not matched");
assertEquals("Public Key Exponent not matched", rsaPublicKey.getPublicExponent(), rsaPrivateCrtKey.getPublicExponent());
assertEquals("Public Key Modulus not matched", rsaPublicKey.getModulus(), rsaPrivateCrtKey.getModulus());
final BigInteger privateExponent = rsaPrivateCrtKey.getPrivateExponent();
final BigInteger expectedPrimeExponentP = privateExponent.mod(rsaPrivateCrtKey.getPrimeP().subtract(BigInteger.ONE));
assertEquals(expectedPrimeExponentP, rsaPrivateCrtKey.getPrimeExponentP(), "Prime Exponent P not matched");
assertEquals("Prime Exponent P not matched", expectedPrimeExponentP, rsaPrivateCrtKey.getPrimeExponentP());
final BigInteger expectedPrimeExponentQ = privateExponent.mod(rsaPrivateCrtKey.getPrimeQ().subtract(BigInteger.ONE));
assertEquals(expectedPrimeExponentQ, rsaPrivateCrtKey.getPrimeExponentQ(), "Prime Exponent Q not matched");
assertEquals("Prime Exponent Q not matched", expectedPrimeExponentQ, rsaPrivateCrtKey.getPrimeExponentQ());
final BigInteger expectedCoefficient = rsaPrivateCrtKey.getPrimeQ().modInverse(rsaPrivateCrtKey.getPrimeP());
assertEquals(expectedCoefficient, rsaPrivateCrtKey.getCrtCoefficient(), "Prime CRT Coefficient not matched");
assertEquals("Prime CRT Coefficient not matched", expectedCoefficient, rsaPrivateCrtKey.getCrtCoefficient());
}
@Test
@@ -404,9 +411,7 @@ public class OpenSSHKeyFileTest {
public void notTrimmedKeys() throws IOException {
File initialPrivateKey = new File("src/test/resources/id_rsa");
File initialPublicKey = new File("src/test/resources/id_rsa.pub");
File folder = new File(temporaryFolder, "keys");
assertTrue(folder.mkdir());
File corruptedPrivateKey = new File(folder, "id_rsa");
File corruptedPrivateKey = new File(temporaryFolder.newFolder(), "id_rsa");
File corruptedPublicKey = new File(corruptedPrivateKey.getParent(), "id_rsa.pub");
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(initialPrivateKey)));
@@ -444,7 +449,14 @@ public class OpenSSHKeyFileTest {
FileKeyProvider keyProvider = new OpenSSHKeyV1KeyFile();
keyProvider.init(new StringReader(""));
assertThrows(IOException.class, keyProvider::getPrivate, "This key is not in 'openssh-key-v1' format");
assertThrows("This key is not in 'openssh-key-v1' format", IOException.class, keyProvider::getPrivate);
}
@Before
public void checkBCRegistration() {
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new AssertionError("bouncy castle needed");
}
}
private String readFile(String pathname)

Some files were not shown because too many files have changed in this diff Show More