mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-08 16:18:05 +03:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7e402c557 | ||
|
|
8ef996b406 | ||
|
|
e9cb90901c | ||
|
|
69812e9a81 | ||
|
|
9a939d029b | ||
|
|
50efeb6519 | ||
|
|
aabb1be52e | ||
|
|
32329e547e | ||
|
|
8cf63a96a9 | ||
|
|
cab7731928 | ||
|
|
50073db6c1 | ||
|
|
90099bbf5e | ||
|
|
ce0a7d5193 | ||
|
|
ced27fc898 | ||
|
|
624747c527 | ||
|
|
d8697c2228 | ||
|
|
7c14098f7d | ||
|
|
d5805a6c64 | ||
|
|
8a66dc5336 | ||
|
|
a5c10ab50f |
2
.github/workflows/gradle.yml
vendored
2
.github/workflows/gradle.yml
vendored
@@ -24,6 +24,8 @@ jobs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew check
|
run: ./gradlew check
|
||||||
|
- name: Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
|
|
||||||
integration:
|
integration:
|
||||||
name: Integration test
|
name: Integration test
|
||||||
|
|||||||
21
README.adoc
21
README.adoc
@@ -1,7 +1,7 @@
|
|||||||
= sshj - SSHv2 library for Java
|
= sshj - SSHv2 library for Java
|
||||||
Jeroen van Erp
|
Jeroen van Erp
|
||||||
:sshj_groupid: com.hierynomus
|
:sshj_groupid: com.hierynomus
|
||||||
:sshj_version: 0.31.0
|
:sshj_version: 0.32.0
|
||||||
:source-highlighter: pygments
|
: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"]
|
image:https://github.com/hierynomus/sshj/actions/workflows/gradle.yml/badge.svg[link="https://github.com/hierynomus/sshj/actions/workflows/gradle.yml"]
|
||||||
@@ -104,8 +104,25 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
|
|||||||
Fork away!
|
Fork away!
|
||||||
|
|
||||||
== Release history
|
== Release history
|
||||||
|
SSHJ 0.33.0 (2022-04-22)::
|
||||||
|
* Upgraded dependencies BouncyCastle (1.70)
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/687[#687]: Correctly close connection when remote closes connection.
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/741[#741]: Add support for testcontainers in test setup to test more scenarios
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/733[#733]: Send correct key proposal if client knows CA key
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/746[#746]: Fix bug in reading Putty private key file with passphrase
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/742[#742]: Use Config.keyAlgorithms to determine rsa-sha2 support
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/754[#754]: Use SFTP protocol version to set FXP rename flags conditionally
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/752[#752]: Correctly start and terminate KeepAlive thread
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/753[#753]: Provide better thread names
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/724[#724]: Add parameter to limit read ahead length
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/763[#763]: Try all public key algorithms for a specific key type
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/756[#756]: Remove deprecated proxy connect methods
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/770[#770]: Add support for `ed25519` `aes-128-cbc` keys
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/773[#773]: Fix NPE when reading empty OpenSSHKeyV1KeyFile
|
||||||
|
* Merged https://github.com/hierynomus/sshj/pull/777[#777]: Don't request too many read-ahead packets
|
||||||
|
|
||||||
SSHJ 0.32.0 (2021-10-12)::
|
SSHJ 0.32.0 (2021-10-12)::
|
||||||
* Send EOF on channel close (Fixes https://github.com/hierynomus/sshj/issue/143[#143], https://github.com/hierynomus/sshj/issue/496[#496], https://github.com/hierynomus/sshj/issue/553[#553], https://github.com/hierynomus/sshj/issue/554[#554])
|
* Send EOF on channel close (Fixes https://github.com/hierynomus/sshj/issues/143[#143], https://github.com/hierynomus/sshj/issues/496[#496], https://github.com/hierynomus/sshj/issues/553[#553], https://github.com/hierynomus/sshj/issues/554[#554])
|
||||||
* Merged https://github.com/hierynomus/sshj/pull/726[#726]: Parse OpenSSH v1 keys with full CRT information present
|
* Merged https://github.com/hierynomus/sshj/pull/726[#726]: Parse OpenSSH v1 keys with full CRT information present
|
||||||
* Merged https://github.com/hierynomus/sshj/pull/721[#721]: Prefer known host key algorithm for host key verification
|
* Merged https://github.com/hierynomus/sshj/pull/721[#721]: Prefer known host key algorithm for host key verification
|
||||||
* Merged https://github.com/hierynomus/sshj/pull/716[#716], https://github.com/hierynomus/sshj/pull/729[#729] and https://github.com/hierynomus/sshj/pull/730[#730]: Add full support for PuTTY v3 key files.
|
* Merged https://github.com/hierynomus/sshj/pull/716[#716], https://github.com/hierynomus/sshj/pull/729[#729] and https://github.com/hierynomus/sshj/pull/730[#730]: Add full support for PuTTY v3 key files.
|
||||||
|
|||||||
55
build.gradle
55
build.gradle
@@ -1,7 +1,3 @@
|
|||||||
import java.text.SimpleDateFormat
|
|
||||||
import com.bmuschko.gradle.docker.tasks.container.*
|
|
||||||
import com.bmuschko.gradle.docker.tasks.image.*
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "groovy"
|
id "groovy"
|
||||||
@@ -39,8 +35,8 @@ project.version = scmVersion.version
|
|||||||
|
|
||||||
configurations.implementation.transitive = false
|
configurations.implementation.transitive = false
|
||||||
|
|
||||||
def bouncycastleVersion = "1.69"
|
def bouncycastleVersion = "1.70"
|
||||||
def sshdVersion = "2.1.0"
|
def sshdVersion = "2.8.0"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.slf4j:slf4j-api:1.7.32"
|
implementation "org.slf4j:slf4j-api:1.7.32"
|
||||||
@@ -51,16 +47,16 @@ dependencies {
|
|||||||
|
|
||||||
implementation "net.i2p.crypto:eddsa:0.3.0"
|
implementation "net.i2p.crypto:eddsa:0.3.0"
|
||||||
|
|
||||||
testImplementation "junit:junit:4.12"
|
testImplementation "junit:junit:4.13.2"
|
||||||
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.4'
|
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.4'
|
||||||
testImplementation "org.mockito:mockito-core:2.28.2"
|
testImplementation "org.mockito:mockito-core:4.2.0"
|
||||||
testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
|
testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
|
||||||
testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion"
|
testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion"
|
||||||
testImplementation "org.apache.sshd:sshd-scp:$sshdVersion"
|
testImplementation "org.apache.sshd:sshd-scp:$sshdVersion"
|
||||||
testRuntimeOnly "ch.qos.logback:logback-classic:1.2.6"
|
testImplementation "ch.qos.logback:logback-classic:1.2.9"
|
||||||
testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
|
testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
|
||||||
testImplementation 'org.apache.httpcomponents:httpclient:4.5.9'
|
testImplementation 'org.apache.httpcomponents:httpclient:4.5.13'
|
||||||
|
testImplementation 'org.testcontainers:testcontainers:1.16.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
license {
|
license {
|
||||||
@@ -276,48 +272,11 @@ jacocoTestReport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
task buildItestImage(type: DockerBuildImage) {
|
|
||||||
inputDir = file('src/itest/docker-image')
|
|
||||||
images.add('sshj/sshd-itest:latest')
|
|
||||||
}
|
|
||||||
|
|
||||||
task createItestContainer(type: DockerCreateContainer) {
|
|
||||||
dependsOn buildItestImage
|
|
||||||
targetImageId buildItestImage.getImageId()
|
|
||||||
hostConfig.portBindings = ['2222:22']
|
|
||||||
hostConfig.autoRemove = true
|
|
||||||
}
|
|
||||||
|
|
||||||
task startItestContainer(type: DockerStartContainer) {
|
|
||||||
dependsOn createItestContainer
|
|
||||||
targetContainerId createItestContainer.getContainerId()
|
|
||||||
}
|
|
||||||
|
|
||||||
task logItestContainer(type: DockerLogsContainer) {
|
|
||||||
dependsOn createItestContainer
|
|
||||||
targetContainerId createItestContainer.getContainerId()
|
|
||||||
showTimestamps = true
|
|
||||||
stdErr = true
|
|
||||||
stdOut = true
|
|
||||||
tailAll = true
|
|
||||||
}
|
|
||||||
|
|
||||||
task stopItestContainer(type: DockerStopContainer) {
|
|
||||||
targetContainerId createItestContainer.getContainerId()
|
|
||||||
}
|
|
||||||
|
|
||||||
task forkedUploadRelease(type: GradleBuild) {
|
task forkedUploadRelease(type: GradleBuild) {
|
||||||
buildFile = project.buildFile
|
buildFile = project.buildFile
|
||||||
tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
|
tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
|
||||||
}
|
}
|
||||||
|
|
||||||
project.tasks.integrationTest.dependsOn(startItestContainer)
|
|
||||||
project.tasks.integrationTest.finalizedBy(stopItestContainer)
|
|
||||||
|
|
||||||
// Being enabled, it pollutes logs on CI. Uncomment when debugging some test to get sshd logs.
|
|
||||||
// project.tasks.stopItestContainer.dependsOn(logItestContainer)
|
|
||||||
|
|
||||||
project.tasks.release.dependsOn([project.tasks.integrationTest, project.tasks.build])
|
project.tasks.release.dependsOn([project.tasks.integrationTest, project.tasks.build])
|
||||||
project.tasks.release.finalizedBy(project.tasks.forkedUploadRelease)
|
project.tasks.release.finalizedBy(project.tasks.forkedUploadRelease)
|
||||||
project.tasks.jacocoTestReport.dependsOn(project.tasks.test)
|
project.tasks.jacocoTestReport.dependsOn(project.tasks.test)
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
FROM sickp/alpine-sshd:7.5-r2
|
|
||||||
|
|
||||||
ADD authorized_keys /home/sshj/.ssh/authorized_keys
|
|
||||||
|
|
||||||
ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key
|
|
||||||
ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub
|
|
||||||
ADD test-container/ssh_host_ed25519_key /etc/ssh/ssh_host_ed25519_key
|
|
||||||
ADD test-container/ssh_host_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
ADD test-container/sshd_config /etc/ssh/sshd_config
|
|
||||||
COPY test-container/trusted_ca_keys /etc/ssh/trusted_ca_keys
|
|
||||||
COPY test-container/host_keys/* /etc/ssh/
|
|
||||||
|
|
||||||
RUN apk add --no-cache tini
|
|
||||||
RUN \
|
|
||||||
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
|
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2"]
|
|
||||||
@@ -3,6 +3,7 @@ ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0w
|
|||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj
|
||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local
|
||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq2LSHRavxAT7ja2f+5soOUJl/zKSI ajvanerp@Heimdall.xebialabs.com
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq2LSHRavxAT7ja2f+5soOUJl/zKSI ajvanerp@Heimdall.xebialabs.com
|
||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBi sshjtest@TranceLove
|
||||||
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase
|
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase
|
||||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local
|
||||||
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMvfRYSe44VQGwxexOMibcM3+fWeUP1jrBofOxFDRRrzRF8dK/vll2svqTPXMRnITnT1UoemEcB5OHtvH4hzfh/HFeDxJ5S7UncYxoClTSa8MeMFG2Zj9CoUZs1SHbwSGg== root@sshj
|
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMvfRYSe44VQGwxexOMibcM3+fWeUP1jrBofOxFDRRrzRF8dK/vll2svqTPXMRnITnT1UoemEcB5OHtvH4hzfh/HFeDxJ5S7UncYxoClTSa8MeMFG2Zj9CoUZs1SHbwSGg== root@sshj
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
# $OpenBSD: sshd_config,v 1.101 2017/03/14 07:19:07 djm Exp $
|
|
||||||
|
|
||||||
# This is the sshd server system-wide configuration file. See
|
|
||||||
# sshd_config(5) for more information.
|
|
||||||
|
|
||||||
# This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin
|
|
||||||
|
|
||||||
# The strategy used for options in the default sshd_config shipped with
|
|
||||||
# OpenSSH is to specify options with their default value where
|
|
||||||
# possible, but leave them commented. Uncommented options override the
|
|
||||||
# default value.
|
|
||||||
|
|
||||||
#Port 22
|
|
||||||
#AddressFamily any
|
|
||||||
#ListenAddress 0.0.0.0
|
|
||||||
#ListenAddress ::
|
|
||||||
|
|
||||||
#HostKey /etc/ssh/ssh_host_rsa_key
|
|
||||||
#HostKey /etc/ssh/ssh_host_dsa_key
|
|
||||||
#HostKey /etc/ssh/ssh_host_ecdsa_key
|
|
||||||
#HostKey /etc/ssh/ssh_host_ed25519_key
|
|
||||||
|
|
||||||
# Ciphers and keying
|
|
||||||
#RekeyLimit default none
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
#SyslogFacility AUTH
|
|
||||||
#LogLevel INFO
|
|
||||||
|
|
||||||
# Authentication:
|
|
||||||
|
|
||||||
#LoginGraceTime 2m
|
|
||||||
PermitRootLogin yes
|
|
||||||
#StrictModes yes
|
|
||||||
#MaxAuthTries 6
|
|
||||||
#MaxSessions 10
|
|
||||||
|
|
||||||
#PubkeyAuthentication yes
|
|
||||||
|
|
||||||
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
|
|
||||||
# but this is overridden so installations will only check .ssh/authorized_keys
|
|
||||||
AuthorizedKeysFile .ssh/authorized_keys
|
|
||||||
|
|
||||||
#AuthorizedPrincipalsFile none
|
|
||||||
|
|
||||||
#AuthorizedKeysCommand none
|
|
||||||
#AuthorizedKeysCommandUser nobody
|
|
||||||
|
|
||||||
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
|
|
||||||
#HostbasedAuthentication no
|
|
||||||
# Change to yes if you don't trust ~/.ssh/known_hosts for
|
|
||||||
# HostbasedAuthentication
|
|
||||||
#IgnoreUserKnownHosts no
|
|
||||||
# Don't read the user's ~/.rhosts and ~/.shosts files
|
|
||||||
#IgnoreRhosts yes
|
|
||||||
|
|
||||||
# To disable tunneled clear text passwords, change to no here!
|
|
||||||
#PasswordAuthentication yes
|
|
||||||
#PermitEmptyPasswords no
|
|
||||||
|
|
||||||
# Change to no to disable s/key passwords
|
|
||||||
#ChallengeResponseAuthentication yes
|
|
||||||
|
|
||||||
# Kerberos options
|
|
||||||
#KerberosAuthentication no
|
|
||||||
#KerberosOrLocalPasswd yes
|
|
||||||
#KerberosTicketCleanup yes
|
|
||||||
#KerberosGetAFSToken no
|
|
||||||
|
|
||||||
# GSSAPI options
|
|
||||||
#GSSAPIAuthentication no
|
|
||||||
#GSSAPICleanupCredentials yes
|
|
||||||
|
|
||||||
# Set this to 'yes' to enable PAM authentication, account processing,
|
|
||||||
# and session processing. If this is enabled, PAM authentication will
|
|
||||||
# be allowed through the ChallengeResponseAuthentication and
|
|
||||||
# PasswordAuthentication. Depending on your PAM configuration,
|
|
||||||
# PAM authentication via ChallengeResponseAuthentication may bypass
|
|
||||||
# the setting of "PermitRootLogin without-password".
|
|
||||||
# If you just want the PAM account and session checks to run without
|
|
||||||
# PAM authentication, then enable this but set PasswordAuthentication
|
|
||||||
# and ChallengeResponseAuthentication to 'no'.
|
|
||||||
#UsePAM no
|
|
||||||
|
|
||||||
#AllowAgentForwarding yes
|
|
||||||
#AllowTcpForwarding yes
|
|
||||||
#GatewayPorts no
|
|
||||||
#X11Forwarding no
|
|
||||||
#X11DisplayOffset 10
|
|
||||||
#X11UseLocalhost yes
|
|
||||||
#PermitTTY yes
|
|
||||||
#PrintMotd yes
|
|
||||||
#PrintLastLog yes
|
|
||||||
#TCPKeepAlive yes
|
|
||||||
#UseLogin no
|
|
||||||
#PermitUserEnvironment no
|
|
||||||
#Compression delayed
|
|
||||||
#ClientAliveInterval 0
|
|
||||||
#ClientAliveCountMax 3
|
|
||||||
#UseDNS no
|
|
||||||
#PidFile /run/sshd.pid
|
|
||||||
#MaxStartups 10:30:100
|
|
||||||
#PermitTunnel no
|
|
||||||
#ChrootDirectory none
|
|
||||||
#VersionAddendum none
|
|
||||||
|
|
||||||
# no default banner path
|
|
||||||
#Banner none
|
|
||||||
|
|
||||||
# override default of no subsystems
|
|
||||||
Subsystem sftp /usr/lib/ssh/sftp-server
|
|
||||||
|
|
||||||
# the following are HPN related configuration options
|
|
||||||
# tcp receive buffer polling. disable in non autotuning kernels
|
|
||||||
#TcpRcvBufPoll yes
|
|
||||||
|
|
||||||
# disable hpn performance boosts
|
|
||||||
#HPNDisabled no
|
|
||||||
|
|
||||||
# buffer size for hpn to non-hpn connections
|
|
||||||
#HPNBufferSize 2048
|
|
||||||
|
|
||||||
|
|
||||||
# Example of overriding settings on a per-user basis
|
|
||||||
#Match User anoncvs
|
|
||||||
# X11Forwarding no
|
|
||||||
# AllowTcpForwarding no
|
|
||||||
# PermitTTY no
|
|
||||||
# ForceCommand cvs server
|
|
||||||
|
|
||||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1
|
|
||||||
macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com
|
|
||||||
|
|
||||||
TrustedUserCAKeys /etc/ssh/trusted_ca_keys
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
HostKey /etc/ssh/ssh_host_rsa_key
|
|
||||||
HostKey /etc/ssh/ssh_host_dsa_key
|
|
||||||
HostKey /etc/ssh/ssh_host_ecdsa_key
|
|
||||||
HostKey /etc/ssh/ssh_host_ed25519_key
|
|
||||||
|
|
||||||
HostKey /etc/ssh/ssh_host_ecdsa_256_key
|
|
||||||
HostCertificate /etc/ssh/ssh_host_ecdsa_256_key-cert.pub
|
|
||||||
|
|
||||||
HostKey /etc/ssh/ssh_host_ecdsa_384_key
|
|
||||||
HostCertificate /etc/ssh/ssh_host_ecdsa_384_key-cert.pub
|
|
||||||
|
|
||||||
HostKey /etc/ssh/ssh_host_ecdsa_521_key
|
|
||||||
HostCertificate /etc/ssh/ssh_host_ecdsa_521_key-cert.pub
|
|
||||||
|
|
||||||
HostKey /etc/ssh/ssh_host_ed25519_384_key
|
|
||||||
HostCertificate /etc/ssh/ssh_host_ed25519_384_key-cert.pub
|
|
||||||
|
|
||||||
HostKey /etc/ssh/ssh_host_rsa_2048_key
|
|
||||||
HostCertificate /etc/ssh/ssh_host_rsa_2048_key-cert.pub
|
|
||||||
|
|
||||||
LogLevel DEBUG2
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C)2009 - SSHJ Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hierynomus.sshj
|
|
||||||
|
|
||||||
import net.schmizz.sshj.Config
|
|
||||||
import net.schmizz.sshj.DefaultConfig
|
|
||||||
import net.schmizz.sshj.SSHClient
|
|
||||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
|
|
||||||
import spock.lang.Specification
|
|
||||||
|
|
||||||
class IntegrationBaseSpec extends Specification {
|
|
||||||
protected static final int DOCKER_PORT = 2222
|
|
||||||
protected static final String USERNAME = "sshj"
|
|
||||||
protected static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa"
|
|
||||||
protected final static String SERVER_IP = System.getProperty("serverIP", "127.0.0.1")
|
|
||||||
|
|
||||||
protected static SSHClient getConnectedClient(Config config) {
|
|
||||||
SSHClient sshClient = new SSHClient(config)
|
|
||||||
sshClient.addHostKeyVerifier(new PromiscuousVerifier())
|
|
||||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
|
||||||
|
|
||||||
return sshClient
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static SSHClient getConnectedClient() throws IOException {
|
|
||||||
return getConnectedClient(new DefaultConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,9 +20,15 @@ import net.schmizz.sshj.DefaultConfig
|
|||||||
import net.schmizz.sshj.SSHClient
|
import net.schmizz.sshj.SSHClient
|
||||||
import net.schmizz.sshj.transport.TransportException
|
import net.schmizz.sshj.transport.TransportException
|
||||||
import net.schmizz.sshj.userauth.UserAuthException
|
import net.schmizz.sshj.userauth.UserAuthException
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
class IntegrationSpec extends IntegrationBaseSpec {
|
class IntegrationSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "should accept correct key for #signatureName"() {
|
def "should accept correct key for #signatureName"() {
|
||||||
@@ -33,7 +39,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
|||||||
sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint
|
sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint
|
||||||
|
|
||||||
when:
|
when:
|
||||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
sshClient.isConnected()
|
sshClient.isConnected()
|
||||||
@@ -50,7 +56,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
|||||||
sshClient.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3")
|
sshClient.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3")
|
||||||
|
|
||||||
when:
|
when:
|
||||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
thrown(TransportException.class)
|
thrown(TransportException.class)
|
||||||
@@ -59,11 +65,11 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
|||||||
@Unroll
|
@Unroll
|
||||||
def "should authenticate with key #key"() {
|
def "should authenticate with key #key"() {
|
||||||
given:
|
given:
|
||||||
SSHClient client = getConnectedClient()
|
SSHClient client = sshd.getConnectedClient()
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key")
|
def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key")
|
||||||
client.authPublickey(USERNAME, keyProvider)
|
client.authPublickey(IntegrationTestUtil.USERNAME, keyProvider)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.isAuthenticated()
|
client.isAuthenticated()
|
||||||
@@ -74,6 +80,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
|||||||
"id_ecdsa_opensshv1" | null
|
"id_ecdsa_opensshv1" | null
|
||||||
"id_ed25519_opensshv1" | null
|
"id_ed25519_opensshv1" | null
|
||||||
"id_ed25519_opensshv1_aes256cbc.pem" | "foobar"
|
"id_ed25519_opensshv1_aes256cbc.pem" | "foobar"
|
||||||
|
"id_ed25519_opensshv1_aes128cbc.pem" | "sshjtest"
|
||||||
"id_ed25519_opensshv1_protected" | "sshjtest"
|
"id_ed25519_opensshv1_protected" | "sshjtest"
|
||||||
"id_rsa" | null
|
"id_rsa" | null
|
||||||
"id_rsa_opensshv1" | null
|
"id_rsa_opensshv1" | null
|
||||||
@@ -83,7 +90,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
|||||||
|
|
||||||
def "should not authenticate with wrong key"() {
|
def "should not authenticate with wrong key"() {
|
||||||
given:
|
given:
|
||||||
SSHClient client = getConnectedClient()
|
SSHClient client = sshd.getConnectedClient()
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key")
|
client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key")
|
||||||
|
|||||||
@@ -13,14 +13,9 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.backport;
|
package com.hierynomus.sshj
|
||||||
|
|
||||||
public class JavaVersion {
|
|
||||||
public static boolean isJava7OrEarlier() {
|
|
||||||
String property = System.getProperty("java.specification.version");
|
|
||||||
float diff = Float.parseFloat(property) - 1.7f;
|
|
||||||
|
|
||||||
return diff < 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class IntegrationTestUtil {
|
||||||
|
static final String USERNAME = "sshj"
|
||||||
|
static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa"
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.testcontainers.containers.wait.strategy.WaitStrategy;
|
||||||
|
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wait strategy designed for {@link SshdContainer} to wait until the SSH server is ready, to avoid races when a test
|
||||||
|
* tries to connect to a server before the server has started.
|
||||||
|
*/
|
||||||
|
public class SshServerWaitStrategy implements WaitStrategy {
|
||||||
|
private Duration startupTimeout = Duration.ofMinutes(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {
|
||||||
|
long expectedEnd = System.nanoTime() + startupTimeout.toNanos();
|
||||||
|
while (waitStrategyTarget.isRunning()) {
|
||||||
|
long attemptStart = System.nanoTime();
|
||||||
|
IOException error = null;
|
||||||
|
byte[] buffer = new byte[7];
|
||||||
|
try (Socket socket = new Socket()) {
|
||||||
|
socket.setSoTimeout(500);
|
||||||
|
socket.connect(new InetSocketAddress(
|
||||||
|
waitStrategyTarget.getHost(), waitStrategyTarget.getFirstMappedPort()));
|
||||||
|
// Haven't seen any SSH server that sends the version in two or more packets.
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
socket.getInputStream().read(buffer);
|
||||||
|
if (!Arrays.equals(buffer, "SSH-2.0".getBytes(StandardCharsets.UTF_8))) {
|
||||||
|
error = new IOException("The version message doesn't look like an SSH server version");
|
||||||
|
}
|
||||||
|
} catch (IOException err) {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error == null) {
|
||||||
|
break;
|
||||||
|
} else if (System.nanoTime() >= expectedEnd) {
|
||||||
|
throw new RuntimeException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//noinspection BusyWait
|
||||||
|
Thread.sleep(Math.max(0L, 500L - (System.nanoTime() - attemptStart) / 1_000_000));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WaitStrategy withStartupTimeout(Duration startupTimeout) {
|
||||||
|
this.startupTimeout = startupTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
155
src/itest/groovy/com/hierynomus/sshj/SshdContainer.java
Normal file
155
src/itest/groovy/com/hierynomus/sshj/SshdContainer.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,21 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.sftp
|
package com.hierynomus.sshj.sftp
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.IntegrationTestUtil
|
||||||
|
import com.hierynomus.sshj.SshdContainer
|
||||||
import net.schmizz.sshj.SSHClient
|
import net.schmizz.sshj.SSHClient
|
||||||
import net.schmizz.sshj.sftp.OpenMode
|
import net.schmizz.sshj.sftp.OpenMode
|
||||||
import net.schmizz.sshj.sftp.RemoteFile
|
import net.schmizz.sshj.sftp.RemoteFile
|
||||||
import net.schmizz.sshj.sftp.SFTPClient
|
import net.schmizz.sshj.sftp.SFTPClient
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
|
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
|
||||||
|
|
||||||
class FileWriteSpec extends IntegrationBaseSpec {
|
class FileWriteSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
def "should append to file (GH issue #390)"() {
|
def "should append to file (GH issue #390)"() {
|
||||||
given:
|
given:
|
||||||
SSHClient client = getConnectedClient()
|
SSHClient client = sshd.getConnectedClient()
|
||||||
client.authPublickey("sshj", "src/test/resources/id_rsa")
|
client.authPublickey("sshj", "src/test/resources/id_rsa")
|
||||||
SFTPClient sftp = client.newSFTPClient()
|
SFTPClient sftp = client.newSFTPClient()
|
||||||
def file = "/home/sshj/test.txt"
|
def file = "/home/sshj/test.txt"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,29 +15,34 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.signature
|
package com.hierynomus.sshj.signature
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.SshdContainer
|
||||||
import net.schmizz.sshj.DefaultConfig
|
import net.schmizz.sshj.DefaultConfig
|
||||||
import net.schmizz.sshj.SSHClient
|
import net.schmizz.sshj.SSHClient
|
||||||
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts
|
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a brief test for verifying connection to a server using keys with certificates.
|
* 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}.
|
* Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}.
|
||||||
*/
|
*/
|
||||||
class KeyWithCertificateSpec extends IntegrationBaseSpec {
|
class PublicKeyAuthWithCertificateSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "authorising with a signed public key #keyName"() {
|
def "authorising with a signed public key #keyName"() {
|
||||||
given:
|
given:
|
||||||
def client = getConnectedClient()
|
SSHClient client = new SSHClient(new DefaultConfig())
|
||||||
|
client.addHostKeyVerifier(new PromiscuousVerifier())
|
||||||
|
client.connect("127.0.0.1", sshd.firstMappedPort)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, "src/itest/resources/keyfiles/certificates/$keyName")
|
client.authPublickey("sshj", "src/itest/resources/keyfiles/certificates/$keyName")
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
@@ -73,43 +78,4 @@ class KeyWithCertificateSpec extends IntegrationBaseSpec {
|
|||||||
"id_ed25519_384_rfc4716_signed_by_rsa",
|
"id_ed25519_384_rfc4716_signed_by_rsa",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "accepting a signed host public key with type #hostKeyAlgo"() {
|
|
||||||
given:
|
|
||||||
File knownHosts = Files.createTempFile("known_hosts", "").toFile()
|
|
||||||
knownHosts.deleteOnExit()
|
|
||||||
|
|
||||||
and:
|
|
||||||
File caPubKey = new File("src/itest/resources/keyfiles/certificates/CA_rsa.pem.pub")
|
|
||||||
String knownHostsFileContents = "" +
|
|
||||||
"@cert-authority $SERVER_IP ${caPubKey.text}" +
|
|
||||||
"\n@cert-authority [$SERVER_IP]:$DOCKER_PORT ${caPubKey.text}"
|
|
||||||
knownHosts.write(knownHostsFileContents)
|
|
||||||
|
|
||||||
and:
|
|
||||||
def config = new DefaultConfig()
|
|
||||||
config.keyAlgorithms = config.keyAlgorithms.stream()
|
|
||||||
.filter { it.name == hostKeyAlgo }
|
|
||||||
.collect(Collectors.toList())
|
|
||||||
SSHClient sshClient = new SSHClient(config)
|
|
||||||
sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(knownHosts))
|
|
||||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
|
||||||
|
|
||||||
when:
|
|
||||||
sshClient.authPassword("sshj", "ultrapassword")
|
|
||||||
|
|
||||||
then:
|
|
||||||
sshClient.authenticated
|
|
||||||
|
|
||||||
and:
|
|
||||||
knownHosts.getText() == knownHostsFileContents
|
|
||||||
|
|
||||||
where:
|
|
||||||
hostKeyAlgo << [
|
|
||||||
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
|
||||||
"ssh-ed25519-cert-v01@openssh.com",
|
|
||||||
"ssh-rsa-cert-v01@openssh.com",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -15,18 +15,25 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.signature
|
package com.hierynomus.sshj.signature
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.IntegrationTestUtil
|
||||||
import net.schmizz.sshj.DefaultConfig
|
import com.hierynomus.sshj.SshdContainer
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
class RsaSignatureClientKeySpec extends IntegrationBaseSpec {
|
class RsaSignatureClientKeySpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "should correctly connect using publickey auth with RSA key with signature"() {
|
def "should correctly connect using publickey auth with RSA key with signature"() {
|
||||||
given:
|
given:
|
||||||
def client = getConnectedClient(new DefaultConfig())
|
def client = sshd.getConnectedClient()
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, "src/itest/resources/keyfiles/id_rsa2")
|
client.authPublickey(IntegrationTestUtil.USERNAME, "src/itest/resources/keyfiles/id_rsa2")
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
|
|||||||
@@ -15,22 +15,29 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.signature
|
package com.hierynomus.sshj.signature
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.IntegrationTestUtil
|
||||||
|
import com.hierynomus.sshj.SshdContainer
|
||||||
import com.hierynomus.sshj.key.KeyAlgorithms
|
import com.hierynomus.sshj.key.KeyAlgorithms
|
||||||
import net.schmizz.sshj.DefaultConfig
|
import net.schmizz.sshj.DefaultConfig
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
class SignatureSpec extends IntegrationBaseSpec {
|
class SignatureSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "should correctly connect with #sig Signature"() {
|
def "should correctly connect with #sig Signature"() {
|
||||||
given:
|
given:
|
||||||
def cfg = new DefaultConfig()
|
def cfg = new DefaultConfig()
|
||||||
cfg.setKeyAlgorithms(Collections.singletonList(sigFactory))
|
cfg.setKeyAlgorithms(Collections.singletonList(sigFactory))
|
||||||
def client = getConnectedClient(cfg)
|
def client = sshd.getConnectedClient(cfg)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, KEYFILE)
|
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
|
|||||||
@@ -15,21 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.transport.cipher
|
package com.hierynomus.sshj.transport.cipher
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.IntegrationTestUtil
|
||||||
|
import com.hierynomus.sshj.SshdContainer
|
||||||
import net.schmizz.sshj.DefaultConfig
|
import net.schmizz.sshj.DefaultConfig
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
class CipherSpec extends IntegrationBaseSpec {
|
class CipherSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "should correctly connect with #cipher Cipher"() {
|
def "should correctly connect with #cipher Cipher"() {
|
||||||
given:
|
given:
|
||||||
def cfg = new DefaultConfig()
|
def cfg = new DefaultConfig()
|
||||||
cfg.setCipherFactories(cipherFactory)
|
cfg.setCipherFactories(cipherFactory)
|
||||||
def client = getConnectedClient(cfg)
|
def client = sshd.getConnectedClient(cfg)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, KEYFILE)
|
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
|
|||||||
@@ -15,29 +15,32 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.transport.kex
|
package com.hierynomus.sshj.transport.kex
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.IntegrationTestUtil
|
||||||
import com.hierynomus.sshj.transport.mac.Macs
|
import com.hierynomus.sshj.SshdContainer
|
||||||
import net.schmizz.sshj.DefaultConfig
|
import net.schmizz.sshj.DefaultConfig
|
||||||
import net.schmizz.sshj.transport.kex.Curve25519DH
|
|
||||||
import net.schmizz.sshj.transport.kex.Curve25519SHA256
|
import net.schmizz.sshj.transport.kex.Curve25519SHA256
|
||||||
import net.schmizz.sshj.transport.kex.DH
|
|
||||||
import net.schmizz.sshj.transport.kex.DHGexSHA1
|
import net.schmizz.sshj.transport.kex.DHGexSHA1
|
||||||
import net.schmizz.sshj.transport.kex.DHGexSHA256
|
import net.schmizz.sshj.transport.kex.DHGexSHA256
|
||||||
import net.schmizz.sshj.transport.kex.ECDH
|
|
||||||
import net.schmizz.sshj.transport.kex.ECDHNistP
|
import net.schmizz.sshj.transport.kex.ECDHNistP
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
class KexSpec extends IntegrationBaseSpec {
|
class KexSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "should correctly connect with #kex Key Exchange"() {
|
def "should correctly connect with #kex Key Exchange"() {
|
||||||
given:
|
given:
|
||||||
def cfg = new DefaultConfig()
|
def cfg = new DefaultConfig()
|
||||||
cfg.setKeyExchangeFactories(kexFactory)
|
cfg.setKeyExchangeFactories(kexFactory)
|
||||||
def client = getConnectedClient(cfg)
|
def client = sshd.getConnectedClient(cfg)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, KEYFILE)
|
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
|
|||||||
@@ -15,21 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.transport.mac
|
package com.hierynomus.sshj.transport.mac
|
||||||
|
|
||||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
import com.hierynomus.sshj.IntegrationTestUtil
|
||||||
|
import com.hierynomus.sshj.SshdContainer
|
||||||
import net.schmizz.sshj.DefaultConfig
|
import net.schmizz.sshj.DefaultConfig
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
import spock.lang.Unroll
|
||||||
|
|
||||||
class MacSpec extends IntegrationBaseSpec {
|
class MacSpec extends Specification {
|
||||||
|
@Shared
|
||||||
|
@ClassRule
|
||||||
|
SshdContainer sshd
|
||||||
|
|
||||||
@Unroll
|
@Unroll
|
||||||
def "should correctly connect with #mac MAC"() {
|
def "should correctly connect with #mac MAC"() {
|
||||||
given:
|
given:
|
||||||
def cfg = new DefaultConfig()
|
def cfg = new DefaultConfig()
|
||||||
cfg.setMACFactories(macFactory)
|
cfg.setMACFactories(macFactory)
|
||||||
def client = getConnectedClient(cfg)
|
def client = sshd.getConnectedClient(cfg)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, KEYFILE)
|
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
@@ -47,10 +54,10 @@ class MacSpec extends IntegrationBaseSpec {
|
|||||||
given:
|
given:
|
||||||
def cfg = new DefaultConfig()
|
def cfg = new DefaultConfig()
|
||||||
cfg.setMACFactories(macFactory)
|
cfg.setMACFactories(macFactory)
|
||||||
def client = getConnectedClient(cfg)
|
def client = sshd.getConnectedClient(cfg)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey(USERNAME, KEYFILE)
|
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
client.authenticated
|
client.authenticated
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfrL8SxDyrkNlsJdAmc7Z0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBiAAAAkLVqaDIfs+sPBNyy7ytdLnP/xH7Nt5FIXx3Upw6wKuMGdBzbFQLcvu60Le+SFP3uUfXE8TcHramXbH0n+UBMW6raCAKOkHUU1BtrKxPG1eKU/LBx3Bk5FxyKm7fo0XsCUmqSVK25EHOJfYq1QwIbWICkvQUNu+2Hg8/MQKoFJMentI+GqjdaG76f6Wf+aj9UwA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public interface RemoteAddressProvider {
|
||||||
|
/**
|
||||||
|
* Get Remote Socket Address associated with transport connection
|
||||||
|
*
|
||||||
|
* @return Remote Socket Address or null when not connected
|
||||||
|
*/
|
||||||
|
InetSocketAddress getRemoteSocketAddress();
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public class ThreadNameProvider {
|
||||||
|
private static final String DISCONNECTED = "DISCONNECTED";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Thread Name prefixed with sshj followed by class and remote address when connected
|
||||||
|
*
|
||||||
|
* @param thread Class of Thread being named
|
||||||
|
* @param remoteAddressProvider Remote Address Provider associated with Thread
|
||||||
|
*/
|
||||||
|
public static void setThreadName(final Thread thread, final RemoteAddressProvider remoteAddressProvider) {
|
||||||
|
final InetSocketAddress remoteSocketAddress = remoteAddressProvider.getRemoteSocketAddress();
|
||||||
|
final String address = remoteSocketAddress == null ? DISCONNECTED : remoteSocketAddress.toString();
|
||||||
|
final String threadName = String.format("sshj-%s-%s", thread.getClass().getSimpleName(), address);
|
||||||
|
thread.setName(threadName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,8 +27,6 @@ import java.util.List;
|
|||||||
|
|
||||||
public class KeyAlgorithms {
|
public class KeyAlgorithms {
|
||||||
|
|
||||||
public static List<String> SSH_RSA_SHA2_ALGORITHMS = Arrays.asList("rsa-sha2-512", "rsa-sha2-256");
|
|
||||||
|
|
||||||
public static Factory SSHRSA() { return new Factory("ssh-rsa", new SignatureRSA.FactorySSHRSA(), KeyType.RSA); }
|
public static Factory SSHRSA() { return new Factory("ssh-rsa", new SignatureRSA.FactorySSHRSA(), KeyType.RSA); }
|
||||||
public static Factory SSHRSACertV01() { return new Factory("ssh-rsa-cert-v01@openssh.com", new SignatureRSA.FactoryCERT(), KeyType.RSA_CERT); }
|
public static Factory SSHRSACertV01() { return new Factory("ssh-rsa-cert-v01@openssh.com", new SignatureRSA.FactoryCERT(), KeyType.RSA_CERT); }
|
||||||
public static Factory RSASHA256() { return new Factory("rsa-sha2-256", new SignatureRSA.FactoryRSASHA256(), KeyType.RSA); }
|
public static Factory RSASHA256() { return new Factory("rsa-sha2-256", new SignatureRSA.FactoryRSASHA256(), KeyType.RSA); }
|
||||||
@@ -61,6 +59,10 @@ public class KeyAlgorithms {
|
|||||||
return algorithmName;
|
return algorithmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyType getKeyType() {
|
||||||
|
return keyType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAlgorithm create() {
|
public KeyAlgorithm create() {
|
||||||
return new BaseKeyAlgorithm(algorithmName, signatureFactory, keyType);
|
return new BaseKeyAlgorithm(algorithmName, signatureFactory, keyType);
|
||||||
|
|||||||
@@ -177,11 +177,11 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
Arrays.fill(charBuffer.array(), '\u0000');
|
Arrays.fill(charBuffer.array(), '\u0000');
|
||||||
Arrays.fill(byteBuffer.array(), (byte) 0);
|
Arrays.fill(byteBuffer.array(), (byte) 0);
|
||||||
}
|
}
|
||||||
byte[] keyiv = new byte[48];
|
byte[] keyiv = new byte[cipher.getIVSize()+ cipher.getBlockSize()];
|
||||||
new BCrypt().pbkdf(passphrase, opts.readBytes(), opts.readUInt32AsInt(), keyiv);
|
new BCrypt().pbkdf(passphrase, opts.readBytes(), opts.readUInt32AsInt(), keyiv);
|
||||||
Arrays.fill(passphrase, (byte) 0);
|
Arrays.fill(passphrase, (byte) 0);
|
||||||
byte[] key = Arrays.copyOfRange(keyiv, 0, 32);
|
byte[] key = Arrays.copyOfRange(keyiv, 0, cipher.getBlockSize());
|
||||||
byte[] iv = Arrays.copyOfRange(keyiv, 32, 48);
|
byte[] iv = Arrays.copyOfRange(keyiv, cipher.getBlockSize(), cipher.getIVSize() + cipher.getBlockSize());
|
||||||
cipher.init(Cipher.Mode.Decrypt, key, iv);
|
cipher.init(Cipher.Mode.Decrypt, key, iv);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("No support for KDF '" + kdfName + "'.");
|
throw new IllegalStateException("No support for KDF '" + kdfName + "'.");
|
||||||
@@ -193,6 +193,8 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
return BlockCiphers.AES256CTR().create();
|
return BlockCiphers.AES256CTR().create();
|
||||||
} else if (cipherName.equals(BlockCiphers.AES256CBC().getName())) {
|
} else if (cipherName.equals(BlockCiphers.AES256CBC().getName())) {
|
||||||
return BlockCiphers.AES256CBC().create();
|
return BlockCiphers.AES256CBC().create();
|
||||||
|
} else if (cipherName.equals(BlockCiphers.AES128CBC().getName())) {
|
||||||
|
return BlockCiphers.AES128CBC().create();
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Cipher '" + cipherName + "' not currently implemented for openssh-key-v1 format");
|
throw new IllegalStateException("Cipher '" + cipherName + "' not currently implemented for openssh-key-v1 format");
|
||||||
}
|
}
|
||||||
@@ -216,6 +218,9 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
while (line != null && !line.startsWith(BEGIN)) {
|
while (line != null && !line.startsWith(BEGIN)) {
|
||||||
line = reader.readLine();
|
line = reader.readLine();
|
||||||
}
|
}
|
||||||
|
if (line == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
line = line.substring(BEGIN.length());
|
line = line.substring(BEGIN.length());
|
||||||
return line.startsWith(OPENSSH_PRIVATE_KEY);
|
return line.startsWith(OPENSSH_PRIVATE_KEY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ final class Heartbeater
|
|||||||
extends KeepAlive {
|
extends KeepAlive {
|
||||||
|
|
||||||
Heartbeater(ConnectionImpl conn) {
|
Heartbeater(ConnectionImpl conn) {
|
||||||
super(conn, "heartbeater");
|
super(conn, "sshj-Heartbeater");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import net.schmizz.sshj.connection.ConnectionImpl;
|
|||||||
import net.schmizz.sshj.transport.TransportException;
|
import net.schmizz.sshj.transport.TransportException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public abstract class KeepAlive extends Thread {
|
public abstract class KeepAlive extends Thread {
|
||||||
protected final Logger log;
|
protected final Logger log;
|
||||||
protected final ConnectionImpl conn;
|
protected final ConnectionImpl conn;
|
||||||
@@ -33,40 +35,32 @@ public abstract class KeepAlive extends Thread {
|
|||||||
setDaemon(true);
|
setDaemon(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return keepAliveInterval > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized int getKeepAliveInterval() {
|
public synchronized int getKeepAliveInterval() {
|
||||||
return keepAliveInterval;
|
return keepAliveInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setKeepAliveInterval(int keepAliveInterval) {
|
public synchronized void setKeepAliveInterval(int keepAliveInterval) {
|
||||||
this.keepAliveInterval = keepAliveInterval;
|
this.keepAliveInterval = keepAliveInterval;
|
||||||
if (keepAliveInterval > 0 && getState() == State.NEW) {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized protected int getPositiveInterval()
|
|
||||||
throws InterruptedException {
|
|
||||||
while (keepAliveInterval <= 0) {
|
|
||||||
wait();
|
|
||||||
}
|
|
||||||
return keepAliveInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
log.debug("Starting {}, sending keep-alive every {} seconds", getClass().getSimpleName(), keepAliveInterval);
|
log.debug("{} Started with interval [{} seconds]", getClass().getSimpleName(), keepAliveInterval);
|
||||||
try {
|
try {
|
||||||
while (!isInterrupted()) {
|
while (!isInterrupted()) {
|
||||||
final int hi = getPositiveInterval();
|
final int interval = getKeepAliveInterval();
|
||||||
if (conn.getTransport().isRunning()) {
|
if (conn.getTransport().isRunning()) {
|
||||||
log.debug("Sending keep-alive since {} seconds elapsed", hi);
|
log.debug("{} Sending after interval [{} seconds]", getClass().getSimpleName(), interval);
|
||||||
doKeepAlive();
|
doKeepAlive();
|
||||||
}
|
}
|
||||||
Thread.sleep(hi * 1000);
|
TimeUnit.SECONDS.sleep(interval);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// Interrupt signal may be catched when sleeping.
|
log.trace("{} Interrupted while sleeping", getClass().getSimpleName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// If we weren't interrupted, kill the transport, then this exception was unexpected.
|
// If we weren't interrupted, kill the transport, then this exception was unexpected.
|
||||||
// Else we're in shutdown-mode already, so don't forcibly kill the transport.
|
// Else we're in shutdown-mode already, so don't forcibly kill the transport.
|
||||||
@@ -74,9 +68,7 @@ public abstract class KeepAlive extends Thread {
|
|||||||
conn.getTransport().die(e);
|
conn.getTransport().die(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.debug("{} Stopped", getClass().getSimpleName());
|
||||||
log.debug("Stopping {}", getClass().getSimpleName());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void doKeepAlive() throws TransportException, ConnectionException;
|
protected abstract void doKeepAlive() throws TransportException, ConnectionException;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class KeepAliveRunner extends KeepAlive {
|
|||||||
new LinkedList<Promise<SSHPacket, ConnectionException>>();
|
new LinkedList<Promise<SSHPacket, ConnectionException>>();
|
||||||
|
|
||||||
KeepAliveRunner(ConnectionImpl conn) {
|
KeepAliveRunner(ConnectionImpl conn) {
|
||||||
super(conn, "keep-alive");
|
super(conn, "sshj-KeepAliveRunner");
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public int getMaxAliveCount() {
|
synchronized public int getMaxAliveCount() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import net.schmizz.sshj.transport.mac.MAC;
|
|||||||
import net.schmizz.sshj.transport.random.Random;
|
import net.schmizz.sshj.transport.random.Random;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -188,4 +189,30 @@ public class ConfigImpl
|
|||||||
public void setVerifyHostKeyCertificates(boolean value) {
|
public void setVerifyHostKeyCertificates(boolean value) {
|
||||||
verifyHostKeyCertificates = value;
|
verifyHostKeyCertificates = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modern servers neglect the key algorithm ssh-rsa. OpenSSH 8.8 even dropped its support by default in favour
|
||||||
|
* of rsa-sha2-*. However, there are legacy servers like Apache SSHD that don't support the newer replacements
|
||||||
|
* for ssh-rsa.
|
||||||
|
*
|
||||||
|
* If ssh-rsa factory is in {@link #getKeyAlgorithms()}, this methods makes ssh-rsa key algorithm more preferred
|
||||||
|
* than any of rsa-sha2-*. Otherwise, nothing happens.
|
||||||
|
*/
|
||||||
|
public void prioritizeSshRsaKeyAlgorithm() {
|
||||||
|
List<Factory.Named<KeyAlgorithm>> keyAlgorithms = getKeyAlgorithms();
|
||||||
|
for (int sshRsaIndex = 0; sshRsaIndex < keyAlgorithms.size(); ++ sshRsaIndex) {
|
||||||
|
if ("ssh-rsa".equals(keyAlgorithms.get(sshRsaIndex).getName())) {
|
||||||
|
for (int i = 0; i < sshRsaIndex; ++i) {
|
||||||
|
final String algo = keyAlgorithms.get(i).getName();
|
||||||
|
if ("rsa-sha2-256".equals(algo) || "rsa-sha2-512".equals(algo)) {
|
||||||
|
keyAlgorithms = new ArrayList<>(keyAlgorithms);
|
||||||
|
keyAlgorithms.add(i, keyAlgorithms.remove(sshRsaIndex));
|
||||||
|
setKeyAlgorithms(keyAlgorithms);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj;
|
package net.schmizz.sshj;
|
||||||
|
|
||||||
|
import net.schmizz.keepalive.KeepAlive;
|
||||||
|
import com.hierynomus.sshj.common.ThreadNameProvider;
|
||||||
import net.schmizz.sshj.common.*;
|
import net.schmizz.sshj.common.*;
|
||||||
import net.schmizz.sshj.connection.Connection;
|
import net.schmizz.sshj.connection.Connection;
|
||||||
import net.schmizz.sshj.connection.ConnectionException;
|
import net.schmizz.sshj.connection.ConnectionException;
|
||||||
@@ -55,6 +57,7 @@ import javax.security.auth.login.LoginContext;
|
|||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
@@ -424,6 +427,7 @@ public class SSHClient
|
|||||||
@Override
|
@Override
|
||||||
public void disconnect()
|
public void disconnect()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
conn.getKeepAlive().interrupt();
|
||||||
for (LocalPortForwarder forwarder : forwarders) {
|
for (LocalPortForwarder forwarder : forwarders) {
|
||||||
try {
|
try {
|
||||||
forwarder.close();
|
forwarder.close();
|
||||||
@@ -441,6 +445,16 @@ public class SSHClient
|
|||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Remote Socket Address from Transport
|
||||||
|
*
|
||||||
|
* @return Remote Socket Address or null when not connected
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getRemoteSocketAddress() {
|
||||||
|
return trans.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the character set used to communicate with the remote machine for certain strings (like paths).
|
* Returns the character set used to communicate with the remote machine for certain strings (like paths).
|
||||||
*
|
*
|
||||||
@@ -791,6 +805,11 @@ public class SSHClient
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
super.onConnect();
|
super.onConnect();
|
||||||
trans.init(getRemoteHostname(), getRemotePort(), getInputStream(), getOutputStream());
|
trans.init(getRemoteHostname(), getRemotePort(), getInputStream(), getOutputStream());
|
||||||
|
final KeepAlive keepAliveThread = conn.getKeepAlive();
|
||||||
|
if (keepAliveThread.isEnabled()) {
|
||||||
|
ThreadNameProvider.setThreadName(conn.getKeepAlive(), trans);
|
||||||
|
keepAliveThread.start();
|
||||||
|
}
|
||||||
doKex();
|
doKex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj;
|
package net.schmizz.sshj;
|
||||||
|
|
||||||
import com.hierynomus.sshj.backport.JavaVersion;
|
|
||||||
import com.hierynomus.sshj.backport.Jdk7HttpProxySocket;
|
|
||||||
import net.schmizz.sshj.connection.channel.Channel;
|
import net.schmizz.sshj.connection.channel.Channel;
|
||||||
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
|
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
|
||||||
|
|
||||||
@@ -26,7 +24,6 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
public abstract class SocketClient {
|
public abstract class SocketClient {
|
||||||
@@ -57,73 +54,6 @@ public abstract class SocketClient {
|
|||||||
return new InetSocketAddress(hostname, port);
|
return new InetSocketAddress(hostname, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a host via a proxy.
|
|
||||||
* @param hostname The host name to connect to.
|
|
||||||
* @param proxy The proxy to connect via.
|
|
||||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
|
||||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void connect(String hostname, Proxy proxy) throws IOException {
|
|
||||||
connect(hostname, defaultPort, proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a host via a proxy.
|
|
||||||
* @param hostname The host name to connect to.
|
|
||||||
* @param port The port to connect to.
|
|
||||||
* @param proxy The proxy to connect via.
|
|
||||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
|
||||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void connect(String hostname, int port, Proxy proxy) throws IOException {
|
|
||||||
this.hostname = hostname;
|
|
||||||
this.port = port;
|
|
||||||
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
|
|
||||||
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
|
|
||||||
socket = new Jdk7HttpProxySocket(proxy);
|
|
||||||
} else {
|
|
||||||
socket = new Socket(proxy);
|
|
||||||
}
|
|
||||||
socket.connect(makeInetSocketAddress(hostname, port), connectTimeout);
|
|
||||||
onConnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a host via a proxy.
|
|
||||||
* @param host The host address to connect to.
|
|
||||||
* @param proxy The proxy to connect via.
|
|
||||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
|
||||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void connect(InetAddress host, Proxy proxy) throws IOException {
|
|
||||||
connect(host, defaultPort, proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a host via a proxy.
|
|
||||||
* @param host The host address to connect to.
|
|
||||||
* @param port The port to connect to.
|
|
||||||
* @param proxy The proxy to connect via.
|
|
||||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
|
||||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
|
|
||||||
this.port = port;
|
|
||||||
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
|
|
||||||
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
|
|
||||||
socket = new Jdk7HttpProxySocket(proxy);
|
|
||||||
} else {
|
|
||||||
socket = new Socket(proxy);
|
|
||||||
}
|
|
||||||
socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
|
||||||
onConnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connect(String hostname) throws IOException {
|
public void connect(String hostname) throws IOException {
|
||||||
connect(hostname, defaultPort);
|
connect(hostname, defaultPort);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,8 +145,14 @@ public class StreamCopier {
|
|||||||
final double sizeKiB = count / 1024.0;
|
final double sizeKiB = count / 1024.0;
|
||||||
log.debug(String.format("%1$,.1f KiB transferred in %2$,.1f seconds (%3$,.2f KiB/s)", sizeKiB, timeSeconds, (sizeKiB / timeSeconds)));
|
log.debug(String.format("%1$,.1f KiB transferred in %2$,.1f seconds (%3$,.2f KiB/s)", sizeKiB, timeSeconds, (sizeKiB / timeSeconds)));
|
||||||
|
|
||||||
if (length != -1 && read == -1)
|
// Did we encounter EOF?
|
||||||
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");
|
if (read == -1) {
|
||||||
|
// If InputStream was closed we should also close OutputStream
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
if (length != -1)
|
||||||
|
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.connection.channel.direct;
|
package net.schmizz.sshj.connection.channel.direct;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.common.RemoteAddressProvider;
|
||||||
import net.schmizz.sshj.common.SSHException;
|
import net.schmizz.sshj.common.SSHException;
|
||||||
|
|
||||||
/** A factory interface for creating SSH {@link Session session channels}. */
|
/** A factory interface for creating SSH {@link Session session channels}. */
|
||||||
public interface SessionFactory {
|
public interface SessionFactory extends RemoteAddressProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a {@code session} channel. The returned {@link Session} instance allows {@link Session#exec(String)
|
* Opens a {@code session} channel. The returned {@link Session} instance allows {@link Session#exec(String)
|
||||||
@@ -27,7 +28,7 @@ public interface SessionFactory {
|
|||||||
*
|
*
|
||||||
* @return the opened {@code session} channel
|
* @return the opened {@code session} channel
|
||||||
*
|
*
|
||||||
* @throws SSHException
|
* @throws SSHException Thrown on session initialization failures
|
||||||
* @see Session
|
* @see Session
|
||||||
*/
|
*/
|
||||||
Session startSession()
|
Session startSession()
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ public class RemotePortForwarder
|
|||||||
// Listen on all IPv4
|
// Listen on all IPv4
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if ("0.0.0.0".equals(address) && "0:0:0:0:0:0:0:0".equals(channelForward.address)) {
|
||||||
|
// Handle IPv4 requests on IPv6 channel forward
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class PacketReader extends Thread {
|
|||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
log = engine.getLoggerFactory().getLogger(getClass());
|
log = engine.getLoggerFactory().getLogger(getClass());
|
||||||
this.in = engine.getSubsystem().getInputStream();
|
this.in = engine.getSubsystem().getInputStream();
|
||||||
setName("sftp reader");
|
setName("sshj-PacketReader");
|
||||||
setDaemon(true);
|
setDaemon(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -220,49 +222,95 @@ public class RemoteFile
|
|||||||
|
|
||||||
public class ReadAheadRemoteFileInputStream
|
public class ReadAheadRemoteFileInputStream
|
||||||
extends InputStream {
|
extends InputStream {
|
||||||
|
private class UnconfirmedRead {
|
||||||
|
private final long offset;
|
||||||
|
private final Promise<Response, SFTPException> promise;
|
||||||
|
private final int length;
|
||||||
|
|
||||||
|
private UnconfirmedRead(long offset, int length, Promise<Response, SFTPException> promise) {
|
||||||
|
this.offset = offset;
|
||||||
|
this.length = length;
|
||||||
|
this.promise = promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnconfirmedRead(long offset, int length) throws IOException {
|
||||||
|
this(offset, length, RemoteFile.this.asyncRead(offset, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Promise<Response, SFTPException> getPromise() {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final byte[] b = new byte[1];
|
private final byte[] b = new byte[1];
|
||||||
|
|
||||||
private final int maxUnconfirmedReads;
|
private final int maxUnconfirmedReads;
|
||||||
private final Queue<Promise<Response, SFTPException>> unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
|
private final long readAheadLimit;
|
||||||
private final Queue<Long> unconfirmedReadOffsets = new LinkedList<Long>();
|
private final Deque<UnconfirmedRead> unconfirmedReads = new ArrayDeque<>();
|
||||||
|
|
||||||
private long requestOffset;
|
private long currentOffset;
|
||||||
private long responseOffset;
|
private int maxReadLength = Integer.MAX_VALUE;
|
||||||
private boolean eof;
|
private boolean eof;
|
||||||
|
|
||||||
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads) {
|
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads) {
|
||||||
assert 0 <= maxUnconfirmedReads;
|
this(maxUnconfirmedReads, 0L, -1L);
|
||||||
|
|
||||||
this.maxUnconfirmedReads = maxUnconfirmedReads;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset) {
|
/**
|
||||||
|
*
|
||||||
|
* @param maxUnconfirmedReads Maximum number of unconfirmed requests to send
|
||||||
|
* @param fileOffset Initial offset in file to read from
|
||||||
|
* @param readAheadLimit Read ahead is disabled after this limit has been reached
|
||||||
|
*/
|
||||||
|
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset, long readAheadLimit) {
|
||||||
assert 0 <= maxUnconfirmedReads;
|
assert 0 <= maxUnconfirmedReads;
|
||||||
assert 0 <= fileOffset;
|
assert 0 <= fileOffset;
|
||||||
|
|
||||||
this.maxUnconfirmedReads = maxUnconfirmedReads;
|
this.maxUnconfirmedReads = maxUnconfirmedReads;
|
||||||
this.requestOffset = this.responseOffset = fileOffset;
|
this.currentOffset = fileOffset;
|
||||||
|
this.readAheadLimit = readAheadLimit > 0 ? fileOffset + readAheadLimit : Long.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ByteArrayInputStream pending = new ByteArrayInputStream(new byte[0]);
|
private ByteArrayInputStream pending = new ByteArrayInputStream(new byte[0]);
|
||||||
|
|
||||||
private boolean retrieveUnconfirmedRead(boolean blocking) throws IOException {
|
private boolean retrieveUnconfirmedRead(boolean blocking) throws IOException {
|
||||||
if (unconfirmedReads.size() <= 0) {
|
final UnconfirmedRead unconfirmedRead = unconfirmedReads.peek();
|
||||||
|
if (unconfirmedRead == null || !blocking && !unconfirmedRead.getPromise().isDelivered()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
unconfirmedReads.remove(unconfirmedRead);
|
||||||
|
|
||||||
if (!blocking && !unconfirmedReads.peek().isDelivered()) {
|
final Response res = unconfirmedRead.promise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
unconfirmedReadOffsets.remove();
|
|
||||||
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
|
||||||
switch (res.getType()) {
|
switch (res.getType()) {
|
||||||
case DATA:
|
case DATA:
|
||||||
int recvLen = res.readUInt32AsInt();
|
int recvLen = res.readUInt32AsInt();
|
||||||
responseOffset += recvLen;
|
if (unconfirmedRead.offset == currentOffset) {
|
||||||
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
|
currentOffset += recvLen;
|
||||||
|
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
|
||||||
|
|
||||||
|
if (recvLen < unconfirmedRead.length) {
|
||||||
|
// The server returned a packet smaller than the client had requested.
|
||||||
|
// It can be caused by at least one of the following:
|
||||||
|
// * The file has been read fully. Then, few futile read requests can be sent during
|
||||||
|
// the next read(), but the file will be downloaded correctly anyway.
|
||||||
|
// * The server shapes the request length. Then, the read window will be adjusted,
|
||||||
|
// and all further read-ahead requests won't be shaped.
|
||||||
|
// * The file on the server is not a regular file, it is something like fifo.
|
||||||
|
// Then, the window will shrink, and the client will start reading the file slower than it
|
||||||
|
// hypothetically can. It must be a rare case, and it is not worth implementing a sort of
|
||||||
|
// congestion control algorithm here.
|
||||||
|
maxReadLength = recvLen;
|
||||||
|
unconfirmedReads.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATUS:
|
case STATUS:
|
||||||
@@ -290,40 +338,31 @@ public class RemoteFile
|
|||||||
// we also need to go here for len <= 0, because pending may be at
|
// we also need to go here for len <= 0, because pending may be at
|
||||||
// EOF in which case it would return -1 instead of 0
|
// EOF in which case it would return -1 instead of 0
|
||||||
|
|
||||||
|
long requestOffset;
|
||||||
|
if (unconfirmedReads.isEmpty()) {
|
||||||
|
requestOffset = currentOffset;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final UnconfirmedRead lastRequest = unconfirmedReads.getLast();
|
||||||
|
requestOffset = lastRequest.offset + lastRequest.length;
|
||||||
|
}
|
||||||
while (unconfirmedReads.size() <= maxUnconfirmedReads) {
|
while (unconfirmedReads.size() <= maxUnconfirmedReads) {
|
||||||
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
|
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
|
||||||
int reqLen = Math.max(1024, len); // don't be shy!
|
int reqLen = Math.min(Math.max(1024, len), maxReadLength);
|
||||||
unconfirmedReads.add(RemoteFile.this.asyncRead(requestOffset, reqLen));
|
if (readAheadLimit > requestOffset) {
|
||||||
unconfirmedReadOffsets.add(requestOffset);
|
long remaining = readAheadLimit - requestOffset;
|
||||||
|
if (reqLen > remaining) {
|
||||||
|
reqLen = (int) remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unconfirmedReads.add(new UnconfirmedRead(requestOffset, reqLen));
|
||||||
requestOffset += reqLen;
|
requestOffset += reqLen;
|
||||||
|
if (requestOffset >= readAheadLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long nextOffset = unconfirmedReadOffsets.peek();
|
if (!retrieveUnconfirmedRead(true /*blocking*/)) {
|
||||||
if (responseOffset != nextOffset) {
|
|
||||||
|
|
||||||
// the server could not give us all the data we needed, so
|
|
||||||
// we try to fill the gap synchronously
|
|
||||||
|
|
||||||
assert responseOffset < nextOffset;
|
|
||||||
assert 0 < (nextOffset - responseOffset);
|
|
||||||
assert (nextOffset - responseOffset) <= Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
byte[] buf = new byte[(int) (nextOffset - responseOffset)];
|
|
||||||
int recvLen = RemoteFile.this.read(responseOffset, buf, 0, buf.length);
|
|
||||||
|
|
||||||
if (recvLen < 0) {
|
|
||||||
eof = true;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 == recvLen) {
|
|
||||||
// avoid infinite loops
|
|
||||||
throw new SFTPException("Unexpected response size (0), bailing out");
|
|
||||||
}
|
|
||||||
|
|
||||||
responseOffset += recvLen;
|
|
||||||
pending = new ByteArrayInputStream(buf, 0, recvLen);
|
|
||||||
} else if (!retrieveUnconfirmedRead(true /*blocking*/)) {
|
|
||||||
|
|
||||||
// this may happen if we change prefetch strategy
|
// this may happen if we change prefetch strategy
|
||||||
// currently, we should never get here...
|
// currently, we should never get here...
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.sftp;
|
package net.schmizz.sshj.sftp;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.common.ThreadNameProvider;
|
||||||
import net.schmizz.concurrent.Promise;
|
import net.schmizz.concurrent.Promise;
|
||||||
import net.schmizz.sshj.common.IOUtils;
|
import net.schmizz.sshj.common.IOUtils;
|
||||||
import net.schmizz.sshj.common.LoggerFactory;
|
import net.schmizz.sshj.common.LoggerFactory;
|
||||||
@@ -68,6 +69,7 @@ public class SFTPEngine
|
|||||||
sub = session.startSubsystem("sftp");
|
sub = session.startSubsystem("sftp");
|
||||||
out = sub.getOutputStream();
|
out = sub.getOutputStream();
|
||||||
reader = new PacketReader(this);
|
reader = new PacketReader(this);
|
||||||
|
ThreadNameProvider.setThreadName(reader, ssh);
|
||||||
pathHelper = new PathHelper(new PathHelper.Canonicalizer() {
|
pathHelper = new PathHelper(new PathHelper.Canonicalizer() {
|
||||||
@Override
|
@Override
|
||||||
public String canonicalize(String path)
|
public String canonicalize(String path)
|
||||||
@@ -235,14 +237,17 @@ public class SFTPEngine
|
|||||||
if (operativeVersion < 1)
|
if (operativeVersion < 1)
|
||||||
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
|
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
|
||||||
|
|
||||||
long renameFlagMask = 0L;
|
final Request request = newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset());
|
||||||
for (RenameFlags flag : flags) {
|
// SFTP Version 5 introduced rename flags according to Section 6.5 of the specification
|
||||||
renameFlagMask = renameFlagMask | flag.longValue();
|
if (operativeVersion >= 5) {
|
||||||
|
long renameFlagMask = 0L;
|
||||||
|
for (RenameFlags flag : flags) {
|
||||||
|
renameFlagMask = renameFlagMask | flag.longValue();
|
||||||
|
}
|
||||||
|
request.putUInt32(renameFlagMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest(
|
doRequest(request).ensureStatusPacketIsOK();
|
||||||
newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset()).putUInt32(renameFlagMask)
|
|
||||||
).ensureStatusPacketIsOK();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String canonicalize(String path)
|
public String canonicalize(String path)
|
||||||
|
|||||||
@@ -243,7 +243,6 @@ final class KeyExchanger
|
|||||||
negotiatedAlgs.getKeyExchangeAlgorithm());
|
negotiatedAlgs.getKeyExchangeAlgorithm());
|
||||||
transport.setHostKeyAlgorithm(Factory.Named.Util.create(transport.getConfig().getKeyAlgorithms(),
|
transport.setHostKeyAlgorithm(Factory.Named.Util.create(transport.getConfig().getKeyAlgorithms(),
|
||||||
negotiatedAlgs.getSignatureAlgorithm()));
|
negotiatedAlgs.getSignatureAlgorithm()));
|
||||||
transport.setRSASHA2Support(negotiatedAlgs.getRSASHA2Support());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
kex.init(transport,
|
kex.init(transport,
|
||||||
|
|||||||
@@ -26,10 +26,8 @@ public final class NegotiatedAlgorithms {
|
|||||||
private final String c2sComp;
|
private final String c2sComp;
|
||||||
private final String s2cComp;
|
private final String s2cComp;
|
||||||
|
|
||||||
private final boolean rsaSHA2Support;
|
|
||||||
|
|
||||||
NegotiatedAlgorithms(String kex, String sig, String c2sCipher, String s2cCipher, String c2sMAC, String s2cMAC,
|
NegotiatedAlgorithms(String kex, String sig, String c2sCipher, String s2cCipher, String c2sMAC, String s2cMAC,
|
||||||
String c2sComp, String s2cComp, boolean rsaSHA2Support) {
|
String c2sComp, String s2cComp) {
|
||||||
this.kex = kex;
|
this.kex = kex;
|
||||||
this.sig = sig;
|
this.sig = sig;
|
||||||
this.c2sCipher = c2sCipher;
|
this.c2sCipher = c2sCipher;
|
||||||
@@ -38,7 +36,6 @@ public final class NegotiatedAlgorithms {
|
|||||||
this.s2cMAC = s2cMAC;
|
this.s2cMAC = s2cMAC;
|
||||||
this.c2sComp = c2sComp;
|
this.c2sComp = c2sComp;
|
||||||
this.s2cComp = s2cComp;
|
this.s2cComp = s2cComp;
|
||||||
this.rsaSHA2Support = rsaSHA2Support;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKeyExchangeAlgorithm() {
|
public String getKeyExchangeAlgorithm() {
|
||||||
@@ -73,10 +70,6 @@ public final class NegotiatedAlgorithms {
|
|||||||
return s2cComp;
|
return s2cComp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getRSASHA2Support() {
|
|
||||||
return rsaSHA2Support;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return ("[ " +
|
return ("[ " +
|
||||||
@@ -88,7 +81,6 @@ public final class NegotiatedAlgorithms {
|
|||||||
"s2cMAC=" + s2cMAC + "; " +
|
"s2cMAC=" + s2cMAC + "; " +
|
||||||
"c2sComp=" + c2sComp + "; " +
|
"c2sComp=" + c2sComp + "; " +
|
||||||
"s2cComp=" + s2cComp + "; " +
|
"s2cComp=" + s2cComp + "; " +
|
||||||
"rsaSHA2Support=" + rsaSHA2Support +
|
|
||||||
" ]");
|
" ]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.transport;
|
package net.schmizz.sshj.transport;
|
||||||
|
|
||||||
import com.hierynomus.sshj.key.KeyAlgorithms;
|
|
||||||
import net.schmizz.sshj.Config;
|
import net.schmizz.sshj.Config;
|
||||||
import net.schmizz.sshj.common.Buffer;
|
import net.schmizz.sshj.common.Buffer;
|
||||||
import net.schmizz.sshj.common.Factory;
|
import net.schmizz.sshj.common.Factory;
|
||||||
@@ -140,8 +139,8 @@ class Proposal {
|
|||||||
firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
|
firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
|
||||||
other.getClient2ServerCompressionAlgorithms()),
|
other.getClient2ServerCompressionAlgorithms()),
|
||||||
firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
|
firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
|
||||||
other.getServer2ClientCompressionAlgorithms()),
|
other.getServer2ClientCompressionAlgorithms())
|
||||||
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
|
private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public final class Reader
|
|||||||
public Reader(TransportImpl trans) {
|
public Reader(TransportImpl trans) {
|
||||||
this.trans = trans;
|
this.trans = trans;
|
||||||
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
|
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
|
||||||
setName("reader");
|
setName("sshj-Reader");
|
||||||
setDaemon(true);
|
setDaemon(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.transport;
|
package net.schmizz.sshj.transport;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.common.RemoteAddressProvider;
|
||||||
import com.hierynomus.sshj.key.KeyAlgorithm;
|
import com.hierynomus.sshj.key.KeyAlgorithm;
|
||||||
import net.schmizz.sshj.Config;
|
import net.schmizz.sshj.Config;
|
||||||
import net.schmizz.sshj.Service;
|
import net.schmizz.sshj.Service;
|
||||||
@@ -27,11 +28,12 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/** Transport layer of the SSH protocol. */
|
/** Transport layer of the SSH protocol. */
|
||||||
public interface Transport
|
public interface Transport
|
||||||
extends SSHPacketHandler {
|
extends SSHPacketHandler, RemoteAddressProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the host information and the streams to be used by this transport. Identification information is exchanged
|
* Sets the host information and the streams to be used by this transport. Identification information is exchanged
|
||||||
@@ -208,7 +210,7 @@ public interface Transport
|
|||||||
/**
|
/**
|
||||||
* Specify a {@code listener} that will be notified upon disconnection.
|
* Specify a {@code listener} that will be notified upon disconnection.
|
||||||
*
|
*
|
||||||
* @param listener
|
* @param listener Disconnect Listener to be configured
|
||||||
*/
|
*/
|
||||||
void setDisconnectListener(DisconnectListener listener);
|
void setDisconnectListener(DisconnectListener listener);
|
||||||
|
|
||||||
@@ -223,5 +225,5 @@ public interface Transport
|
|||||||
void die(Exception e);
|
void die(Exception e);
|
||||||
|
|
||||||
KeyAlgorithm getHostKeyAlgorithm();
|
KeyAlgorithm getHostKeyAlgorithm();
|
||||||
KeyAlgorithm getClientKeyAlgorithm(KeyType keyType) throws TransportException;
|
List<KeyAlgorithm> getClientKeyAlgorithms(KeyType keyType) throws TransportException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.transport;
|
package net.schmizz.sshj.transport;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.common.ThreadNameProvider;
|
||||||
import com.hierynomus.sshj.key.KeyAlgorithm;
|
import com.hierynomus.sshj.key.KeyAlgorithm;
|
||||||
import com.hierynomus.sshj.key.KeyAlgorithms;
|
import com.hierynomus.sshj.key.KeyAlgorithms;
|
||||||
import com.hierynomus.sshj.transport.IdentificationStringParser;
|
import com.hierynomus.sshj.transport.IdentificationStringParser;
|
||||||
@@ -22,7 +23,6 @@ import net.schmizz.concurrent.ErrorDeliveryUtil;
|
|||||||
import net.schmizz.concurrent.Event;
|
import net.schmizz.concurrent.Event;
|
||||||
import net.schmizz.sshj.AbstractService;
|
import net.schmizz.sshj.AbstractService;
|
||||||
import net.schmizz.sshj.Config;
|
import net.schmizz.sshj.Config;
|
||||||
import net.schmizz.sshj.SSHClient;
|
|
||||||
import net.schmizz.sshj.Service;
|
import net.schmizz.sshj.Service;
|
||||||
import net.schmizz.sshj.common.*;
|
import net.schmizz.sshj.common.*;
|
||||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||||
@@ -32,6 +32,8 @@ import org.slf4j.Logger;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -86,8 +88,6 @@ public final class TransportImpl
|
|||||||
|
|
||||||
private KeyAlgorithm hostKeyAlgorithm;
|
private KeyAlgorithm hostKeyAlgorithm;
|
||||||
|
|
||||||
private boolean rsaSHA2Support;
|
|
||||||
|
|
||||||
private final Event<TransportException> serviceAccept;
|
private final Event<TransportException> serviceAccept;
|
||||||
|
|
||||||
private final Event<TransportException> close;
|
private final Event<TransportException> close;
|
||||||
@@ -130,8 +130,8 @@ public final class TransportImpl
|
|||||||
public TransportImpl(Config config) {
|
public TransportImpl(Config config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.loggerFactory = config.getLoggerFactory();
|
this.loggerFactory = config.getLoggerFactory();
|
||||||
this.serviceAccept = new Event<TransportException>("service accept", TransportException.chainer, loggerFactory);
|
this.serviceAccept = new Event<>("service accept", TransportException.chainer, loggerFactory);
|
||||||
this.close = new Event<TransportException>("transport close", TransportException.chainer, loggerFactory);
|
this.close = new Event<>("transport close", TransportException.chainer, loggerFactory);
|
||||||
this.nullService = new NullService(this);
|
this.nullService = new NullService(this);
|
||||||
this.service = nullService;
|
this.service = nullService;
|
||||||
this.log = loggerFactory.getLogger(getClass());
|
this.log = loggerFactory.getLogger(getClass());
|
||||||
@@ -165,9 +165,20 @@ public final class TransportImpl
|
|||||||
throw new TransportException(e);
|
throw new TransportException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThreadNameProvider.setThreadName(reader, this);
|
||||||
reader.start();
|
reader.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Remote Socket Address using Connection Information
|
||||||
|
*
|
||||||
|
* @return Remote Socket Address or null when not connected
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getRemoteSocketAddress() {
|
||||||
|
return connInfo == null ? null : new InetSocketAddress(getRemoteHost(), getRemotePort());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TransportImpl implements its own default DisconnectListener.
|
* TransportImpl implements its own default DisconnectListener.
|
||||||
*/
|
*/
|
||||||
@@ -211,7 +222,7 @@ public final class TransportImpl
|
|||||||
*
|
*
|
||||||
* @param buffer The buffer to read from.
|
* @param buffer The buffer to read from.
|
||||||
* @return empty string if full ident string has not yet been received
|
* @return empty string if full ident string has not yet been received
|
||||||
* @throws IOException
|
* @throws IOException Thrown when protocol version is not supported
|
||||||
*/
|
*/
|
||||||
private String readIdentification(Buffer.PlainBuffer buffer)
|
private String readIdentification(Buffer.PlainBuffer buffer)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -544,7 +555,7 @@ public final class TransportImpl
|
|||||||
* Got an SSH_MSG_UNIMPLEMENTED, so lets see where we're at and act accordingly.
|
* Got an SSH_MSG_UNIMPLEMENTED, so lets see where we're at and act accordingly.
|
||||||
*
|
*
|
||||||
* @param packet The 'unimplemented' packet received
|
* @param packet The 'unimplemented' packet received
|
||||||
* @throws TransportException
|
* @throws TransportException Thrown when key exchange is ongoing
|
||||||
*/
|
*/
|
||||||
private void gotUnimplemented(SSHPacket packet)
|
private void gotUnimplemented(SSHPacket packet)
|
||||||
throws SSHException {
|
throws SSHException {
|
||||||
@@ -626,21 +637,19 @@ public final class TransportImpl
|
|||||||
return this.hostKeyAlgorithm;
|
return this.hostKeyAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRSASHA2Support(boolean rsaSHA2Support) {
|
|
||||||
this.rsaSHA2Support = rsaSHA2Support;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAlgorithm getClientKeyAlgorithm(KeyType keyType) throws TransportException {
|
public List<KeyAlgorithm> getClientKeyAlgorithms(KeyType keyType) throws TransportException {
|
||||||
if (keyType != KeyType.RSA || !rsaSHA2Support) {
|
|
||||||
return Factory.Named.Util.create(getConfig().getKeyAlgorithms(), keyType.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Factory.Named<KeyAlgorithm>> factories = getConfig().getKeyAlgorithms();
|
List<Factory.Named<KeyAlgorithm>> factories = getConfig().getKeyAlgorithms();
|
||||||
|
List<KeyAlgorithm> available = new ArrayList<>();
|
||||||
if (factories != null)
|
if (factories != null)
|
||||||
for (Factory.Named<KeyAlgorithm> f : factories)
|
for (Factory.Named<KeyAlgorithm> f : factories)
|
||||||
if (f.getName().equals("ssh-rsa") || KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS.contains(f.getName()))
|
if (
|
||||||
return f.create();
|
f instanceof KeyAlgorithms.Factory && ((KeyAlgorithms.Factory) f).getKeyType().equals(keyType)
|
||||||
throw new TransportException("Cannot find an available KeyAlgorithm for type " + keyType);
|
|| !(f instanceof KeyAlgorithms.Factory) && f.getName().equals(keyType.toString())
|
||||||
|
)
|
||||||
|
available.add(f.create());
|
||||||
|
if (available.isEmpty())
|
||||||
|
throw new TransportException("Cannot find an available KeyAlgorithm for type " + keyType);
|
||||||
|
return available;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,19 @@ public class OpenSSHKnownHosts
|
|||||||
for (KnownHostEntry e : entries) {
|
for (KnownHostEntry e : entries) {
|
||||||
try {
|
try {
|
||||||
if (e.appliesTo(adjustedHostname)) {
|
if (e.appliesTo(adjustedHostname)) {
|
||||||
knownHostAlgorithms.add(e.getType().toString());
|
final KeyType type = e.getType();
|
||||||
|
if (e instanceof HostEntry && ((HostEntry) e).marker == Marker.CA_CERT) {
|
||||||
|
// Only the CA key type is known, but the type of the host key is not.
|
||||||
|
// Adding all supported types for keys with certificates.
|
||||||
|
for (final KeyType candidate : KeyType.values()) {
|
||||||
|
if (candidate.getParent() != null) {
|
||||||
|
knownHostAlgorithms.add(candidate.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
knownHostAlgorithms.add(type.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,17 +27,36 @@ import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
public abstract class KeyedAuthMethod
|
public abstract class KeyedAuthMethod
|
||||||
extends AbstractAuthMethod {
|
extends AbstractAuthMethod {
|
||||||
|
|
||||||
protected final KeyProvider kProv;
|
protected final KeyProvider kProv;
|
||||||
|
private Queue<KeyAlgorithm> available;
|
||||||
|
|
||||||
public KeyedAuthMethod(String name, KeyProvider kProv) {
|
public KeyedAuthMethod(String name, KeyProvider kProv) {
|
||||||
super(name);
|
super(name);
|
||||||
this.kProv = kProv;
|
this.kProv = kProv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KeyAlgorithm getPublicKeyAlgorithm(KeyType keyType) throws TransportException {
|
||||||
|
if (available == null) {
|
||||||
|
available = new LinkedList<>(params.getTransport().getClientKeyAlgorithms(keyType));
|
||||||
|
}
|
||||||
|
return available.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRetry() {
|
||||||
|
if (available != null) {
|
||||||
|
available.poll();
|
||||||
|
return !available.isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected SSHPacket putPubKey(SSHPacket reqBuf)
|
protected SSHPacket putPubKey(SSHPacket reqBuf)
|
||||||
throws UserAuthException {
|
throws UserAuthException {
|
||||||
PublicKey key;
|
PublicKey key;
|
||||||
@@ -50,13 +69,16 @@ public abstract class KeyedAuthMethod
|
|||||||
// public key as 2 strings: [ key type | key blob ]
|
// public key as 2 strings: [ key type | key blob ]
|
||||||
KeyType keyType = KeyType.fromKey(key);
|
KeyType keyType = KeyType.fromKey(key);
|
||||||
try {
|
try {
|
||||||
KeyAlgorithm ka = params.getTransport().getClientKeyAlgorithm(keyType);
|
KeyAlgorithm ka = getPublicKeyAlgorithm(keyType);
|
||||||
reqBuf.putString(ka.getKeyAlgorithm())
|
if (ka != null) {
|
||||||
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
|
reqBuf.putString(ka.getKeyAlgorithm())
|
||||||
return reqBuf;
|
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
|
||||||
|
return reqBuf;
|
||||||
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType);
|
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType, ioe);
|
||||||
}
|
}
|
||||||
|
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SSHPacket putSig(SSHPacket reqBuf)
|
protected SSHPacket putSig(SSHPacket reqBuf)
|
||||||
@@ -71,7 +93,7 @@ public abstract class KeyedAuthMethod
|
|||||||
final KeyType kt = KeyType.fromKey(key);
|
final KeyType kt = KeyType.fromKey(key);
|
||||||
Signature signature;
|
Signature signature;
|
||||||
try {
|
try {
|
||||||
signature = params.getTransport().getClientKeyAlgorithm(kt).newSignature();
|
signature = getPublicKeyAlgorithm(kt).newSignature();
|
||||||
} catch (TransportException e) {
|
} catch (TransportException e) {
|
||||||
throw new UserAuthException("No KeyAlgorithm configured for key " + kt);
|
throw new UserAuthException("No KeyAlgorithm configured for key " + kt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.userauth.password;
|
package net.schmizz.sshj.userauth.password;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -64,6 +65,9 @@ public class PasswordUtils {
|
|||||||
*/
|
*/
|
||||||
public static byte[] toByteArray(char[] password) {
|
public static byte[] toByteArray(char[] password) {
|
||||||
CharBuffer charBuffer = CharBuffer.wrap(password);
|
CharBuffer charBuffer = CharBuffer.wrap(password);
|
||||||
return StandardCharsets.UTF_8.encode(charBuffer).array();
|
final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
|
||||||
|
byte[] bytes = new byte[byteBuffer.remaining()];
|
||||||
|
byteBuffer.get(bytes, 0, bytes.length);
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package com.hierynomus.sshj.userauth.keyprovider
|
package com.hierynomus.sshj.userauth.keyprovider
|
||||||
|
|
||||||
import com.hierynomus.sshj.test.SshFixture
|
import com.hierynomus.sshj.test.SshFixture
|
||||||
|
import net.schmizz.sshj.DefaultConfig
|
||||||
import net.schmizz.sshj.SSHClient
|
import net.schmizz.sshj.SSHClient
|
||||||
import net.schmizz.sshj.userauth.keyprovider.KeyFormat
|
import net.schmizz.sshj.userauth.keyprovider.KeyFormat
|
||||||
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator
|
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator
|
||||||
@@ -39,7 +40,14 @@ class FileKeyProviderSpec extends Specification {
|
|||||||
@Unroll
|
@Unroll
|
||||||
def "should have #format FileKeyProvider enabled by default"() {
|
def "should have #format FileKeyProvider enabled by default"() {
|
||||||
given:
|
given:
|
||||||
SSHClient client = fixture.setupConnectedDefaultClient()
|
// `fixture` is backed by Apache SSHD server. Looks like it doesn't support rsa-sha2-512 public key signature.
|
||||||
|
// Changing the default config to prioritize the former default implementation of RSA signature.
|
||||||
|
def config = new DefaultConfig()
|
||||||
|
config.prioritizeSshRsaKeyAlgorithm()
|
||||||
|
|
||||||
|
and:
|
||||||
|
SSHClient client = fixture.setupClient(config)
|
||||||
|
fixture.connectClient(client)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
client.authPublickey("jeroen", keyfile)
|
client.authPublickey("jeroen", keyfile)
|
||||||
@@ -48,7 +56,7 @@ class FileKeyProviderSpec extends Specification {
|
|||||||
client.isAuthenticated()
|
client.isAuthenticated()
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
client.disconnect()
|
client?.disconnect()
|
||||||
|
|
||||||
where:
|
where:
|
||||||
format | keyfile
|
format | keyfile
|
||||||
|
|||||||
87
src/test/groovy/net/schmizz/sshj/ConfigImplSpec.groovy
Normal file
87
src/test/groovy/net/schmizz/sshj/ConfigImplSpec.groovy
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.hierynomus.sshj.key.KeyAlgorithms
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class ConfigImplSpec extends Specification {
|
||||||
|
static def ECDSA = KeyAlgorithms.ECDSASHANistp521()
|
||||||
|
static def ED25519 = KeyAlgorithms.EdDSA25519()
|
||||||
|
static def RSA_SHA_256 = KeyAlgorithms.RSASHA256()
|
||||||
|
static def RSA_SHA_512 = KeyAlgorithms.RSASHA512()
|
||||||
|
static def SSH_RSA = KeyAlgorithms.SSHRSA()
|
||||||
|
|
||||||
|
def "prioritizeSshRsaKeyAlgorithm does nothing if there is no ssh-rsa"() {
|
||||||
|
given:
|
||||||
|
def config = new DefaultConfig()
|
||||||
|
config.keyAlgorithms = [RSA_SHA_512, ED25519]
|
||||||
|
|
||||||
|
when:
|
||||||
|
config.prioritizeSshRsaKeyAlgorithm()
|
||||||
|
|
||||||
|
then:
|
||||||
|
config.keyAlgorithms == [RSA_SHA_512, ED25519]
|
||||||
|
}
|
||||||
|
|
||||||
|
def "prioritizeSshRsaKeyAlgorithm does nothing if there is no rsa-sha2-any"() {
|
||||||
|
given:
|
||||||
|
def config = new DefaultConfig()
|
||||||
|
config.keyAlgorithms = [ED25519, SSH_RSA, ECDSA]
|
||||||
|
|
||||||
|
when:
|
||||||
|
config.prioritizeSshRsaKeyAlgorithm()
|
||||||
|
|
||||||
|
then:
|
||||||
|
config.keyAlgorithms == [ED25519, SSH_RSA, ECDSA]
|
||||||
|
}
|
||||||
|
|
||||||
|
def "prioritizeSshRsaKeyAlgorithm does nothing if ssh-rsa already has higher priority"() {
|
||||||
|
given:
|
||||||
|
def config = new DefaultConfig()
|
||||||
|
config.keyAlgorithms = [ED25519, SSH_RSA, RSA_SHA_512, ECDSA]
|
||||||
|
|
||||||
|
when:
|
||||||
|
config.prioritizeSshRsaKeyAlgorithm()
|
||||||
|
|
||||||
|
then:
|
||||||
|
config.keyAlgorithms == [ED25519, SSH_RSA, RSA_SHA_512, ECDSA]
|
||||||
|
}
|
||||||
|
|
||||||
|
def "prioritizeSshRsaKeyAlgorithm prioritizes ssh-rsa if there is one rsa-sha2-any is prioritized"() {
|
||||||
|
given:
|
||||||
|
def config = new DefaultConfig()
|
||||||
|
config.keyAlgorithms = [ED25519, RSA_SHA_512, ECDSA, SSH_RSA]
|
||||||
|
|
||||||
|
when:
|
||||||
|
config.prioritizeSshRsaKeyAlgorithm()
|
||||||
|
|
||||||
|
then:
|
||||||
|
config.keyAlgorithms == [ED25519, SSH_RSA, RSA_SHA_512, ECDSA]
|
||||||
|
}
|
||||||
|
|
||||||
|
def "prioritizeSshRsaKeyAlgorithm prioritizes ssh-rsa if there are two rsa-sha2-any is prioritized"() {
|
||||||
|
given:
|
||||||
|
def config = new DefaultConfig()
|
||||||
|
config.keyAlgorithms = [ED25519, RSA_SHA_512, ECDSA, RSA_SHA_256, SSH_RSA]
|
||||||
|
|
||||||
|
when:
|
||||||
|
config.prioritizeSshRsaKeyAlgorithm()
|
||||||
|
|
||||||
|
then:
|
||||||
|
config.keyAlgorithms == [ED25519, SSH_RSA, RSA_SHA_512, ECDSA, RSA_SHA_256]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.connection.channel.forwarded;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.test.HttpServer;
|
||||||
|
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.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.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class LocalPortForwarderTest {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(LocalPortForwarderTest.class);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public SshFixture fixture = new SshFixture();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public HttpServer httpServer = new HttpServer();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
fixture.getServer().setForwardingFilter(new AcceptAllForwardingFilter());
|
||||||
|
File file = httpServer.getDocRoot().newFile("index.html");
|
||||||
|
FileUtil.writeToFile(file, "<html><head/><body><h1>Hi!</h1></body></html>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHaveWorkingHttpServer() throws IOException {
|
||||||
|
// Just to check that we have a working http server...
|
||||||
|
assertThat(httpGet("127.0.0.1", 8080), equalTo(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHaveHttpServerThatClosesConnectionAfterResponse() throws IOException {
|
||||||
|
// Just to check that the test server does close connections before we try through the forwarder...
|
||||||
|
httpGetAndAssertConnectionClosedByServer(8080);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 10_000)
|
||||||
|
public void shouldCloseConnectionWhenRemoteServerClosesConnection() throws IOException {
|
||||||
|
SSHClient sshClient = getFixtureClient();
|
||||||
|
|
||||||
|
ServerSocket serverSocket = new ServerSocket();
|
||||||
|
serverSocket.setReuseAddress(true);
|
||||||
|
serverSocket.bind(new InetSocketAddress("0.0.0.0", 12345));
|
||||||
|
LocalPortForwarder localPortForwarder = sshClient.newLocalPortForwarder(new Parameters("0.0.0.0", 12345, "localhost", 8080), serverSocket);
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
localPortForwarder.listen();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, "local port listener").start();
|
||||||
|
|
||||||
|
// Test once to prove that the local HTTP connection is closed when the remote HTTP connection is closed.
|
||||||
|
httpGetAndAssertConnectionClosedByServer(12345);
|
||||||
|
|
||||||
|
// Test again to prove that the tunnel is still open, even after HTTP connection was closed.
|
||||||
|
httpGetAndAssertConnectionClosedByServer(12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
OutputStream outputStream = socket.getOutputStream();
|
||||||
|
PrintWriter writer = new PrintWriter(outputStream);
|
||||||
|
writer.println("GET / HTTP/1.1");
|
||||||
|
writer.println("");
|
||||||
|
writer.flush();
|
||||||
|
|
||||||
|
// Read the HTTP response
|
||||||
|
InputStream inputStream = socket.getInputStream();
|
||||||
|
InputStreamReader reader = new InputStreamReader(inputStream);
|
||||||
|
int buf = -2;
|
||||||
|
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.
|
||||||
|
Assert.assertEquals(-1, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||||
|
sshClient.authPassword("jeroen", "jeroen");
|
||||||
|
return sshClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,14 +37,14 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
public class RemotePortForwarderTest {
|
public class RemotePortForwarderTest {
|
||||||
private static final Logger log = LoggerFactory.getLogger(RemotePortForwarderTest.class);
|
private static final Logger log = LoggerFactory.getLogger(RemotePortForwarderTest.class);
|
||||||
|
|
||||||
private static final PortRange RANGE = new PortRange(9000, 9999);
|
private static final PortRange RANGE = new PortRange(9000, 9999);
|
||||||
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress("127.0.0.1", 8080);
|
private static final String LOCALHOST = "127.0.0.1";
|
||||||
|
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress(LOCALHOST, 8080);
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public SshFixture fixture = new SshFixture();
|
public SshFixture fixture = new SshFixture();
|
||||||
@@ -62,61 +62,63 @@ public class RemotePortForwarderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldHaveWorkingHttpServer() throws IOException {
|
public void shouldHaveWorkingHttpServer() throws IOException {
|
||||||
// Just to check that we have a working http server...
|
// Just to check that we have a working http server...
|
||||||
assertThat(httpGet("127.0.0.1", 8080), equalTo(200));
|
assertEquals(200, httpGet( 8080));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDynamicallyForwardPortForLocalhost() throws IOException {
|
public void shouldDynamicallyForwardPortForLocalhost() throws IOException {
|
||||||
SSHClient sshClient = getFixtureClient();
|
SSHClient sshClient = getFixtureClient();
|
||||||
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", new SinglePort(0));
|
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", new SinglePort(0));
|
||||||
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
|
assertHttpGetSuccess(bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDynamicallyForwardPortForAllIPv4() throws IOException {
|
public void shouldDynamicallyForwardPortForAllIPv4() throws IOException {
|
||||||
SSHClient sshClient = getFixtureClient();
|
SSHClient sshClient = getFixtureClient();
|
||||||
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", new SinglePort(0));
|
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", new SinglePort(0));
|
||||||
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
|
assertHttpGetSuccess(bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDynamicallyForwardPortForAllProtocols() throws IOException {
|
public void shouldDynamicallyForwardPortForAllProtocols() throws IOException {
|
||||||
SSHClient sshClient = getFixtureClient();
|
SSHClient sshClient = getFixtureClient();
|
||||||
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", new SinglePort(0));
|
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", new SinglePort(0));
|
||||||
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
|
assertHttpGetSuccess(bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldForwardPortForLocalhost() throws IOException {
|
public void shouldForwardPortForLocalhost() throws IOException {
|
||||||
SSHClient sshClient = getFixtureClient();
|
SSHClient sshClient = getFixtureClient();
|
||||||
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", RANGE);
|
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", RANGE);
|
||||||
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
|
assertHttpGetSuccess(bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldForwardPortForAllIPv4() throws IOException {
|
public void shouldForwardPortForAllIPv4() throws IOException {
|
||||||
SSHClient sshClient = getFixtureClient();
|
SSHClient sshClient = getFixtureClient();
|
||||||
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", RANGE);
|
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", RANGE);
|
||||||
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
|
assertHttpGetSuccess(bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldForwardPortForAllProtocols() throws IOException {
|
public void shouldForwardPortForAllProtocols() throws IOException {
|
||||||
SSHClient sshClient = getFixtureClient();
|
SSHClient sshClient = getFixtureClient();
|
||||||
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", RANGE);
|
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", RANGE);
|
||||||
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
|
assertHttpGetSuccess(bind);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHttpGetSuccess(final RemotePortForwarder.Forward bind) throws IOException {
|
||||||
|
assertEquals(200, httpGet(bind.getPort()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemotePortForwarder.Forward forwardPort(SSHClient sshClient, String address, PortRange portRange) throws IOException {
|
private RemotePortForwarder.Forward forwardPort(SSHClient sshClient, String address, PortRange portRange) throws IOException {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
RemotePortForwarder.Forward forward = sshClient.getRemotePortForwarder().bind(
|
return sshClient.getRemotePortForwarder().bind(
|
||||||
// where the server should listen
|
// where the server should listen
|
||||||
new RemotePortForwarder.Forward(address, portRange.nextPort()),
|
new RemotePortForwarder.Forward(address, portRange.nextPort()),
|
||||||
// what we do with incoming connections that are forwarded to us
|
// what we do with incoming connections that are forwarded to us
|
||||||
new SocketForwardingConnectListener(HTTP_SERVER_SOCKET_ADDR));
|
new SocketForwardingConnectListener(HTTP_SERVER_SOCKET_ADDR));
|
||||||
|
|
||||||
return forward;
|
|
||||||
} catch (ConnectionException ce) {
|
} catch (ConnectionException ce) {
|
||||||
if (!portRange.hasNext()) {
|
if (!portRange.hasNext()) {
|
||||||
throw ce;
|
throw ce;
|
||||||
@@ -125,9 +127,9 @@ public class RemotePortForwarderTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int httpGet(String server, int port) throws IOException {
|
private int httpGet(int port) throws IOException {
|
||||||
HttpClient client = HttpClientBuilder.create().build();
|
HttpClient client = HttpClientBuilder.create().build();
|
||||||
String urlString = "http://" + server + ":" + port;
|
String urlString = "http://" + LOCALHOST + ":" + port;
|
||||||
log.info("Trying: GET " + urlString);
|
log.info("Trying: GET " + urlString);
|
||||||
HttpResponse execute = client.execute(new HttpGet(urlString));
|
HttpResponse execute = client.execute(new HttpGet(urlString));
|
||||||
return execute.getStatusLine().getStatusCode();
|
return execute.getStatusLine().getStatusCode();
|
||||||
@@ -140,7 +142,7 @@ public class RemotePortForwarderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class PortRange {
|
private static class PortRange {
|
||||||
private int upper;
|
private final int upper;
|
||||||
private int current;
|
private int current;
|
||||||
|
|
||||||
public PortRange(int lower, int upper) {
|
public PortRange(int lower, int upper) {
|
||||||
|
|||||||
@@ -15,54 +15,66 @@
|
|||||||
*/
|
*/
|
||||||
package com.hierynomus.sshj.keepalive;
|
package com.hierynomus.sshj.keepalive;
|
||||||
|
|
||||||
import com.hierynomus.sshj.test.KnownFailingTests;
|
|
||||||
import com.hierynomus.sshj.test.SlowTests;
|
|
||||||
import com.hierynomus.sshj.test.SshFixture;
|
import com.hierynomus.sshj.test.SshFixture;
|
||||||
|
import net.schmizz.keepalive.KeepAlive;
|
||||||
import net.schmizz.keepalive.KeepAliveProvider;
|
import net.schmizz.keepalive.KeepAliveProvider;
|
||||||
import net.schmizz.sshj.DefaultConfig;
|
import net.schmizz.sshj.DefaultConfig;
|
||||||
import net.schmizz.sshj.SSHClient;
|
import net.schmizz.sshj.SSHClient;
|
||||||
import net.schmizz.sshj.userauth.UserAuthException;
|
import net.schmizz.sshj.userauth.UserAuthException;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.experimental.categories.Category;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.management.ManagementFactory;
|
|
||||||
import java.lang.management.ThreadInfo;
|
|
||||||
import java.lang.management.ThreadMXBean;
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
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 {
|
public class KeepAliveThreadTerminationTest {
|
||||||
|
|
||||||
|
private static final int KEEP_ALIVE_SECONDS = 1;
|
||||||
|
|
||||||
|
private static final long STOP_SLEEP = 1500;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public SshFixture fixture = new SshFixture();
|
public SshFixture fixture = new SshFixture();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Category({SlowTests.class, KnownFailingTests.class})
|
public void shouldNotStartThreadOnSetKeepAliveInterval() {
|
||||||
public void shouldCorrectlyTerminateThreadOnDisconnect() throws IOException, InterruptedException {
|
final SSHClient sshClient = setupClient();
|
||||||
DefaultConfig defaultConfig = new DefaultConfig();
|
|
||||||
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
SSHClient sshClient = fixture.setupClient(defaultConfig);
|
|
||||||
fixture.connectClient(sshClient);
|
|
||||||
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(1);
|
|
||||||
try {
|
|
||||||
sshClient.authPassword("bad", "credentials");
|
|
||||||
fail("Should not auth.");
|
|
||||||
} catch (UserAuthException e) {
|
|
||||||
// OK
|
|
||||||
}
|
|
||||||
fixture.stopClient();
|
|
||||||
Thread.sleep(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
final KeepAlive keepAlive = sshClient.getConnection().getKeepAlive();
|
||||||
for (long l : threadMXBean.getAllThreadIds()) {
|
assertTrue(keepAlive.isDaemon());
|
||||||
ThreadInfo threadInfo = threadMXBean.getThreadInfo(l);
|
assertFalse(keepAlive.isAlive());
|
||||||
if (threadInfo.getThreadName().equals("keep-alive") && threadInfo.getThreadState() != Thread.State.TERMINATED) {
|
assertEquals(Thread.State.NEW, keepAlive.getState());
|
||||||
fail("Found alive keep-alive thread in state " + threadInfo.getThreadState());
|
}
|
||||||
}
|
|
||||||
}
|
@Test
|
||||||
|
public void shouldStartThreadOnConnectAndInterruptOnDisconnect() throws IOException, InterruptedException {
|
||||||
|
final SSHClient sshClient = setupClient();
|
||||||
|
|
||||||
|
final KeepAlive keepAlive = sshClient.getConnection().getKeepAlive();
|
||||||
|
assertTrue(keepAlive.isDaemon());
|
||||||
|
assertEquals(Thread.State.NEW, keepAlive.getState());
|
||||||
|
|
||||||
|
fixture.connectClient(sshClient);
|
||||||
|
assertEquals(Thread.State.TIMED_WAITING, keepAlive.getState());
|
||||||
|
|
||||||
|
assertThrows(UserAuthException.class, () -> sshClient.authPassword("bad", "credentials"));
|
||||||
|
|
||||||
|
fixture.stopClient();
|
||||||
|
Thread.sleep(STOP_SLEEP);
|
||||||
|
|
||||||
|
assertFalse(keepAlive.isAlive());
|
||||||
|
assertEquals(Thread.State.TERMINATED, keepAlive.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSHClient setupClient() {
|
||||||
|
final DefaultConfig defaultConfig = new DefaultConfig();
|
||||||
|
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
|
||||||
|
final SSHClient sshClient = fixture.setupClient(defaultConfig);
|
||||||
|
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(KEEP_ALIVE_SECONDS);
|
||||||
|
return sshClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,21 +17,25 @@ package com.hierynomus.sshj.sftp;
|
|||||||
|
|
||||||
import com.hierynomus.sshj.test.SshFixture;
|
import com.hierynomus.sshj.test.SshFixture;
|
||||||
import net.schmizz.sshj.SSHClient;
|
import net.schmizz.sshj.SSHClient;
|
||||||
|
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||||
import net.schmizz.sshj.sftp.OpenMode;
|
import net.schmizz.sshj.sftp.OpenMode;
|
||||||
import net.schmizz.sshj.sftp.RemoteFile;
|
import net.schmizz.sshj.sftp.RemoteFile;
|
||||||
import net.schmizz.sshj.sftp.SFTPEngine;
|
import net.schmizz.sshj.sftp.SFTPEngine;
|
||||||
|
import net.schmizz.sshj.sftp.SFTPException;
|
||||||
|
import org.apache.sshd.common.util.io.IoUtils;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
import java.security.SecureRandom;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class RemoteFileTest {
|
public class RemoteFileTest {
|
||||||
@Rule
|
@Rule
|
||||||
@@ -84,4 +88,141 @@ public class RemoteFileTest {
|
|||||||
|
|
||||||
assertThat("The written and received data should match", data, equalTo(test2));
|
assertThat("The written and received data should match", data, equalTo(test2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotReadAheadAfterLimitInputStream() throws IOException {
|
||||||
|
SSHClient ssh = fixture.setupConnectedDefaultClient();
|
||||||
|
ssh.authPassword("test", "test");
|
||||||
|
SFTPEngine sftp = new SFTPEngine(ssh).init();
|
||||||
|
|
||||||
|
RemoteFile rf;
|
||||||
|
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);
|
||||||
|
data[3072] = 1;
|
||||||
|
rf.write(0, data, 0, data.length);
|
||||||
|
rf.close();
|
||||||
|
|
||||||
|
assertThat("The file should exist", file.exists());
|
||||||
|
|
||||||
|
rf = sftp.open(file.getPath());
|
||||||
|
InputStream rs = rf.new ReadAheadRemoteFileInputStream(16 /*maxUnconfirmedReads*/,0, 3072);
|
||||||
|
|
||||||
|
byte[] test = new byte[4097];
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
while (n < 2048) {
|
||||||
|
n += rs.read(test, n, 2048 - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
rf.close();
|
||||||
|
|
||||||
|
while (n < 3072) {
|
||||||
|
n += rs.read(test, n, 3072 - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("buffer overrun", test[3072] == 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
rs.read(test, n, test.length - n);
|
||||||
|
fail("Content must not be buffered");
|
||||||
|
} catch (SFTPException e){
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void limitedReadAheadInputStream() throws IOException {
|
||||||
|
SSHClient ssh = fixture.setupConnectedDefaultClient();
|
||||||
|
ssh.authPassword("test", "test");
|
||||||
|
SFTPEngine sftp = new SFTPEngine(ssh).init();
|
||||||
|
|
||||||
|
RemoteFile rf;
|
||||||
|
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);
|
||||||
|
data[3072] = 1;
|
||||||
|
rf.write(0, data, 0, data.length);
|
||||||
|
rf.close();
|
||||||
|
|
||||||
|
assertThat("The file should exist", file.exists());
|
||||||
|
|
||||||
|
rf = sftp.open(file.getPath());
|
||||||
|
InputStream rs = rf.new ReadAheadRemoteFileInputStream(16 /*maxUnconfirmedReads*/,0, 3072);
|
||||||
|
|
||||||
|
byte[] test = new byte[4097];
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
while (n < 2048) {
|
||||||
|
n += rs.read(test, n, 2048 - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (n < 3072) {
|
||||||
|
n += rs.read(test, n, 3072 - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("buffer overrun", test[3072] == 0);
|
||||||
|
|
||||||
|
n += rs.read(test, n, test.length - n); // --> ArrayIndexOutOfBoundsException
|
||||||
|
|
||||||
|
byte[] test2 = new byte[data.length];
|
||||||
|
System.arraycopy(test, 0, test2, 0, test.length);
|
||||||
|
|
||||||
|
while (n < data.length) {
|
||||||
|
n += rs.read(test2, n, data.length - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("The written and received data should match", data, equalTo(test2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadCorrectlyWhenWrappedInBufferedStream_FullSizeBuffer() throws IOException {
|
||||||
|
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadCorrectlyWhenWrappedInBufferedStream_HalfSizeBuffer() throws IOException {
|
||||||
|
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 512 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadCorrectlyWhenWrappedInBufferedStream_QuarterSizeBuffer() throws IOException {
|
||||||
|
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 256 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReadCorrectlyWhenWrappedInBufferedStream_SmallSizeBuffer() throws IOException {
|
||||||
|
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTestShouldReadCorrectlyWhenWrappedInBufferedStream(int fileSize, int bufferSize) throws IOException {
|
||||||
|
SSHClient ssh = fixture.setupConnectedDefaultClient();
|
||||||
|
ssh.authPassword("test", "test");
|
||||||
|
SFTPEngine sftp = new SFTPEngine(ssh).init();
|
||||||
|
|
||||||
|
final byte[] expected = new byte[fileSize];
|
||||||
|
new SecureRandom(new byte[] { 31 }).nextBytes(expected);
|
||||||
|
|
||||||
|
File file = temp.newFile("shouldReadCorrectlyWhenWrappedInBufferedStream.bin");
|
||||||
|
try (OutputStream fStream = new FileOutputStream(file)) {
|
||||||
|
IoUtils.copy(new ByteArrayInputStream(expected), fStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteFile rf = sftp.open(file.getPath());
|
||||||
|
final byte[] actual;
|
||||||
|
try (InputStream inputStream = new BufferedInputStream(
|
||||||
|
rf.new ReadAheadRemoteFileInputStream(10),
|
||||||
|
bufferSize)
|
||||||
|
) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
IoUtils.copy(inputStream, baos, expected.length);
|
||||||
|
actual = baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,17 @@ import net.schmizz.sshj.Config;
|
|||||||
import net.schmizz.sshj.DefaultConfig;
|
import net.schmizz.sshj.DefaultConfig;
|
||||||
import net.schmizz.sshj.SSHClient;
|
import net.schmizz.sshj.SSHClient;
|
||||||
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
|
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
|
||||||
import org.apache.sshd.common.NamedFactory;
|
|
||||||
import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
|
import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
|
||||||
|
import org.apache.sshd.scp.server.ScpCommandFactory;
|
||||||
import org.apache.sshd.server.SshServer;
|
import org.apache.sshd.server.SshServer;
|
||||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
|
||||||
import org.apache.sshd.server.command.Command;
|
|
||||||
import org.apache.sshd.server.command.CommandFactory;
|
|
||||||
import org.apache.sshd.server.scp.ScpCommandFactory;
|
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
|
||||||
import org.apache.sshd.server.shell.ProcessShellFactory;
|
import org.apache.sshd.server.shell.ProcessShellFactory;
|
||||||
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
|
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||||
import org.junit.rules.ExternalResource;
|
import org.junit.rules.ExternalResource;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,9 +39,9 @@ public class SshFixture extends ExternalResource {
|
|||||||
public static final String hostkey = "hostkey.pem";
|
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 fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
|
||||||
|
|
||||||
private SshServer server = defaultSshServer();
|
private final SshServer server = defaultSshServer();
|
||||||
private SSHClient client = null;
|
private SSHClient client = null;
|
||||||
private AtomicBoolean started = new AtomicBoolean(false);
|
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||||
private boolean autoStart = true;
|
private boolean autoStart = true;
|
||||||
|
|
||||||
public SshFixture(boolean autoStart) {
|
public SshFixture(boolean autoStart) {
|
||||||
@@ -108,38 +104,23 @@ public class SshFixture extends ExternalResource {
|
|||||||
sshServer.setPort(randomPort());
|
sshServer.setPort(randomPort());
|
||||||
ClassLoadableResourceKeyPairProvider fileKeyPairProvider = new ClassLoadableResourceKeyPairProvider(hostkey);
|
ClassLoadableResourceKeyPairProvider fileKeyPairProvider = new ClassLoadableResourceKeyPairProvider(hostkey);
|
||||||
sshServer.setKeyPairProvider(fileKeyPairProvider);
|
sshServer.setKeyPairProvider(fileKeyPairProvider);
|
||||||
sshServer.setPasswordAuthenticator(new PasswordAuthenticator() {
|
sshServer.setPasswordAuthenticator((username, password, session) -> username.equals(password));
|
||||||
@Override
|
|
||||||
public boolean authenticate(String username, String password, ServerSession session) {
|
|
||||||
return username.equals(password);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sshServer.setGSSAuthenticator(new BogusGSSAuthenticator());
|
sshServer.setGSSAuthenticator(new BogusGSSAuthenticator());
|
||||||
sshServer.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
|
sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||||
ScpCommandFactory commandFactory = new ScpCommandFactory();
|
ScpCommandFactory commandFactory = new ScpCommandFactory();
|
||||||
commandFactory.setDelegateCommandFactory(new CommandFactory() {
|
commandFactory.setDelegateCommandFactory((session, command) -> new ProcessShellFactory(command, command.split(" ")).createShell(session));
|
||||||
@Override
|
|
||||||
public Command createCommand(String command) {
|
|
||||||
return new ProcessShellFactory(command.split(" ")).create();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sshServer.setCommandFactory(commandFactory);
|
sshServer.setCommandFactory(commandFactory);
|
||||||
sshServer.setShellFactory(new ProcessShellFactory("ls"));
|
sshServer.setShellFactory(new ProcessShellFactory("ls", "ls"));
|
||||||
return sshServer;
|
return sshServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int randomPort() {
|
private int randomPort() {
|
||||||
try {
|
try {
|
||||||
ServerSocket s = null;
|
try (final ServerSocket s = new ServerSocket(0)) {
|
||||||
try {
|
|
||||||
s = new ServerSocket(0);
|
|
||||||
return s.getLocalPort();
|
return s.getLocalPort();
|
||||||
} finally {
|
|
||||||
if (s != null)
|
|
||||||
s.close();
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ import net.schmizz.sshj.common.Factory;
|
|||||||
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
||||||
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
||||||
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
||||||
import org.apache.sshd.common.NamedFactory;
|
|
||||||
import org.apache.sshd.common.kex.BuiltinDHFactories;
|
import org.apache.sshd.common.kex.BuiltinDHFactories;
|
||||||
import org.apache.sshd.common.kex.KeyExchange;
|
import org.apache.sshd.common.kex.KeyExchangeFactory;
|
||||||
import org.apache.sshd.server.SshServer;
|
import org.apache.sshd.server.SshServer;
|
||||||
import org.apache.sshd.server.kex.DHGEXServer;
|
import org.apache.sshd.server.kex.DHGEXServer;
|
||||||
import org.apache.sshd.server.kex.DHGServer;
|
import org.apache.sshd.server.kex.DHGServer;
|
||||||
@@ -38,6 +37,7 @@ import java.util.Collections;
|
|||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class KeyExchangeTest extends BaseAlgorithmTest {
|
public class KeyExchangeTest extends BaseAlgorithmTest {
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Parameterized.Parameters(name = "algorithm={0}")
|
@Parameterized.Parameters(name = "algorithm={0}")
|
||||||
public static Collection<Object[]> getParameters() {
|
public static Collection<Object[]> getParameters() {
|
||||||
return Arrays.asList(new Object[][]{
|
return Arrays.asList(new Object[][]{
|
||||||
@@ -56,10 +56,10 @@ public class KeyExchangeTest extends BaseAlgorithmTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
|
private final Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
|
||||||
private NamedFactory<KeyExchange> serverFactory;
|
private final KeyExchangeFactory serverFactory;
|
||||||
|
|
||||||
public KeyExchangeTest(NamedFactory<KeyExchange> serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
|
public KeyExchangeTest(KeyExchangeFactory serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
|
||||||
this.clientFactory = clientFactory;
|
this.clientFactory = clientFactory;
|
||||||
this.serverFactory = serverFactory;
|
this.serverFactory = serverFactory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,8 @@ import net.schmizz.sshj.SSHClient;
|
|||||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||||
import net.schmizz.sshj.userauth.method.ChallengeResponseProvider;
|
import net.schmizz.sshj.userauth.method.ChallengeResponseProvider;
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
|
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
|
||||||
import org.apache.sshd.common.NamedFactory;
|
import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
|
||||||
import org.apache.sshd.server.auth.UserAuth;
|
|
||||||
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractive;
|
|
||||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -43,28 +39,8 @@ public class AuthKeyboardInteractiveTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setKeyboardInteractiveAuthenticator() throws IOException {
|
public void setKeyboardInteractiveAuthenticator() throws IOException {
|
||||||
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
|
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthKeyboardInteractiveFactory()));
|
||||||
@Override
|
fixture.getServer().setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
|
||||||
public String getName() {
|
|
||||||
return UserAuthKeyboardInteractiveFactory.NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserAuth get() {
|
|
||||||
return new UserAuthKeyboardInteractive();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserAuth create() {
|
|
||||||
return get();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
|
|
||||||
@Override
|
|
||||||
public boolean authenticate(String username, String password, ServerSession session) {
|
|
||||||
return password.equals(username);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fixture.getServer().start();
|
fixture.getServer().start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +51,7 @@ public class AuthKeyboardInteractiveTest {
|
|||||||
sshClient.auth(userAndPassword, new AuthKeyboardInteractive(new ChallengeResponseProvider() {
|
sshClient.auth(userAndPassword, new AuthKeyboardInteractive(new ChallengeResponseProvider() {
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSubmethods() {
|
public List<String> getSubmethods() {
|
||||||
return new ArrayList<String>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,56 +21,29 @@ import net.schmizz.sshj.userauth.UserAuthException;
|
|||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
import net.schmizz.sshj.userauth.password.PasswordUpdateProvider;
|
import net.schmizz.sshj.userauth.password.PasswordUpdateProvider;
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
import org.apache.sshd.common.NamedFactory;
|
|
||||||
import org.apache.sshd.common.util.buffer.Buffer;
|
|
||||||
import org.apache.sshd.server.auth.UserAuth;
|
|
||||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
||||||
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
|
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
|
||||||
import org.apache.sshd.server.auth.password.UserAuthPassword;
|
|
||||||
import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
|
import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
public class AuthPasswordTest {
|
public class AuthPasswordTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public SshFixture fixture = new SshFixture(false);
|
public SshFixture fixture = new SshFixture(false);
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setPasswordAuthenticator() throws IOException {
|
public void setPasswordAuthenticator() throws IOException {
|
||||||
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
|
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthPasswordFactory()));
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return UserAuthPasswordFactory.NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserAuth get() {
|
|
||||||
return new UserAuthPassword() {
|
|
||||||
@Override
|
|
||||||
protected Boolean handleClientPasswordChangeRequest(Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword) throws Exception {
|
|
||||||
return session.getPasswordAuthenticator().authenticate(username, newPassword, session);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserAuth create() {
|
|
||||||
return get();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
|
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||||
@Override
|
@Override
|
||||||
public boolean authenticate(String username, String password, ServerSession session) {
|
public boolean authenticate(String username, String password, ServerSession session) {
|
||||||
@@ -80,6 +53,12 @@ public class AuthPasswordTest {
|
|||||||
return password.equals(username);
|
return password.equals(username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleClientPasswordChangeRequest(
|
||||||
|
ServerSession session, String username, String oldPassword, String newPassword) {
|
||||||
|
return username.equals(newPassword);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
fixture.getServer().start();
|
fixture.getServer().start();
|
||||||
}
|
}
|
||||||
@@ -87,8 +66,7 @@ public class AuthPasswordTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldNotHandlePasswordChangeIfNoPasswordUpdateProviderSet() throws IOException {
|
public void shouldNotHandlePasswordChangeIfNoPasswordUpdateProviderSet() throws IOException {
|
||||||
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||||
expectedException.expect(UserAuthException.class);
|
assertThrows(UserAuthException.class, () -> sshClient.authPassword("jeroen", "changeme"));
|
||||||
sshClient.authPassword("jeroen", "changeme");
|
|
||||||
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
|
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +90,7 @@ public class AuthPasswordTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldHandlePasswordChangeWithWrongPassword() throws IOException {
|
public void shouldHandlePasswordChangeWithWrongPassword() throws IOException {
|
||||||
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||||
expectedException.expect(UserAuthException.class);
|
assertThrows(UserAuthException.class, () -> sshClient.authPassword("jeroen", new PasswordFinder() {
|
||||||
sshClient.authPassword("jeroen", new PasswordFinder() {
|
|
||||||
@Override
|
@Override
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
public char[] reqPassword(Resource<?> resource) {
|
||||||
return "changeme".toCharArray();
|
return "changeme".toCharArray();
|
||||||
@@ -123,7 +100,7 @@ public class AuthPasswordTest {
|
|||||||
public boolean shouldRetry(Resource<?> resource) {
|
public boolean shouldRetry(Resource<?> resource) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, new StaticPasswordUpdateProvider("bad"));
|
}, new StaticPasswordUpdateProvider("bad")));
|
||||||
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
|
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +130,7 @@ public class AuthPasswordTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class StaticPasswordUpdateProvider implements PasswordUpdateProvider {
|
private static class StaticPasswordUpdateProvider implements PasswordUpdateProvider {
|
||||||
private Stack<String> newPasswords = new Stack<String>();
|
private final Stack<String> newPasswords = new Stack<>();
|
||||||
|
|
||||||
public StaticPasswordUpdateProvider(String... newPasswords) {
|
public StaticPasswordUpdateProvider(String... newPasswords) {
|
||||||
for (int i = newPasswords.length - 1; i >= 0; i--) {
|
for (int i = newPasswords.length - 1; i >= 0; i--) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
@@ -247,6 +248,12 @@ public class OpenSSHKeyFileTest {
|
|||||||
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256cbc.pem", "foobar", true);
|
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256cbc.pem", "foobar", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldLoadProtectedED25519PrivateKeyAes128CBC() throws IOException {
|
||||||
|
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes128cbc.pem", "sshjtest", false);
|
||||||
|
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes128cbc.pem", "sshjtest", true);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = KeyDecryptionFailedException.class)
|
@Test(expected = KeyDecryptionFailedException.class)
|
||||||
public void shouldFailOnIncorrectPassphraseAfterRetries() throws IOException {
|
public void shouldFailOnIncorrectPassphraseAfterRetries() throws IOException {
|
||||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||||
@@ -437,6 +444,14 @@ public class OpenSSHKeyFileTest {
|
|||||||
corruptedKeyFile.getPublic());
|
corruptedKeyFile.getPublic());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyPrivateKey() {
|
||||||
|
FileKeyProvider keyProvider = new OpenSSHKeyV1KeyFile();
|
||||||
|
keyProvider.init(new StringReader(""));
|
||||||
|
|
||||||
|
assertThrows("This key is not in 'openssh-key-v1' format", IOException.class, keyProvider::getPrivate);
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void checkBCRegistration() {
|
public void checkBCRegistration() {
|
||||||
if (!SecurityUtils.isBouncyCastleRegistered()) {
|
if (!SecurityUtils.isBouncyCastleRegistered()) {
|
||||||
|
|||||||
@@ -209,6 +209,25 @@ public class PuTTYKeyFileTest {
|
|||||||
"oYhmT2+0DKBuBVCAM4qRdA==\n" +
|
"oYhmT2+0DKBuBVCAM4qRdA==\n" +
|
||||||
"Private-MAC: 40ccc8b9a7291ec64e5be0c99badbc8a012bf220\n";
|
"Private-MAC: 40ccc8b9a7291ec64e5be0c99badbc8a012bf220\n";
|
||||||
|
|
||||||
|
final static String ppk1024_umlaut_passphrase = "PuTTY-User-Key-File-2: ssh-rsa\n" +
|
||||||
|
"Encryption: aes256-cbc\n" +
|
||||||
|
"Comment: user@host\n" +
|
||||||
|
"Public-Lines: 4\n" +
|
||||||
|
"AAAAB3NzaC1yc2EAAAADAQABAAAAgQDsQv60HaW0301hX/xV3AUcutbDDAJp7KWc\n" +
|
||||||
|
"6swL+H6jhwe3N7FK/SA4492bK5oHwU3ea3X6moLuapTMawMQbRy1kfQm99wcYc7C\n" +
|
||||||
|
"6PJO3uouzjDatc/aByDejbo5OL9kK4Vy7qm6tw1hC0JIM+TCvItKu+t6Myl7xzv4\n" +
|
||||||
|
"KbSHiMzulQ==\n" +
|
||||||
|
"Private-Lines: 8\n" +
|
||||||
|
"hPS6HYs4t8WChglZzo5G/B0ohnw2DQS19HMPllyVr9XfDyT2Xk8ZSTye84r5CtMP\n" +
|
||||||
|
"xF4Qc0nkoStyw9p9Tm762FhkM0iGghLWeCdTyqXVlAA9l3sr0BMJ9AoMvjQBqqns\n" +
|
||||||
|
"gjfPvmtNPFn8sfApHVOv1qSLSGOMZFm/q6KtGuR+IyTnMuZ71b/cQYYHbsAQxt09\n" +
|
||||||
|
"96I7jDhup/4uoi/tcPYhe998wRFSSldkAtcmYGUnDWCiivlP+gZsXvOI2zs2gCxx\n" +
|
||||||
|
"ECEwZNTR/j3G0muRUMf91iZSMBije+41j345F+ZHJ43gYXW6lxjFtI5jr9LRGWF1\n" +
|
||||||
|
"hTeY6IlLt4EBBGNrO8Rn0oGVuQdFQAZaredlt1V5FsgcSaMgg3rlScoz0IHHD66Q\n" +
|
||||||
|
"Hglp/IYN6Sx6OEGjh3oLGImag+Mz9/9WWGXPLhZ4MUpFAWqcTD4qPK0jYxTCM6QC\n" +
|
||||||
|
"TybFqMeCSEKiHSOiOGf2oQ==\n" +
|
||||||
|
"Private-MAC: 6aec23b6267edcb87b05ddef52a80894e3a246c4";
|
||||||
|
|
||||||
final static String ppkdsa_passphrase = "PuTTY-User-Key-File-2: ssh-dss\n" +
|
final static String ppkdsa_passphrase = "PuTTY-User-Key-File-2: ssh-dss\n" +
|
||||||
"Encryption: aes256-cbc\n" +
|
"Encryption: aes256-cbc\n" +
|
||||||
"Comment: dsa-key-20140507\n" +
|
"Comment: dsa-key-20140507\n" +
|
||||||
@@ -502,6 +521,15 @@ public class PuTTYKeyFileTest {
|
|||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCorrectPassphraseUmlautRsa() throws Exception {
|
||||||
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
|
key.init(new StringReader(ppk1024_umlaut_passphrase), new UnitTestPasswordFinder("äöü"));
|
||||||
|
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
||||||
|
assertNotNull(key.getPrivate());
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testWrongPassphraseRsa() throws Exception {
|
public void testWrongPassphraseRsa() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
|
|||||||
3
src/test/resources/keytypes/ed25519_aes128cbc.pem
Normal file
3
src/test/resources/keytypes/ed25519_aes128cbc.pem
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfrL8SxDyrkNlsJdAmc7Z0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBiAAAAkLVqaDIfs+sPBNyy7ytdLnP/xH7Nt5FIXx3Upw6wKuMGdBzbFQLcvu60Le+SFP3uUfXE8TcHramXbH0n+UBMW6raCAKOkHUU1BtrKxPG1eKU/LBx3Bk5FxyKm7fo0XsCUmqSVK25EHOJfYq1QwIbWICkvQUNu+2Hg8/MQKoFJMentI+GqjdaG76f6Wf+aj9UwA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
1
src/test/resources/keytypes/ed25519_aes128cbc.pem.pub
Normal file
1
src/test/resources/keytypes/ed25519_aes128cbc.pem.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBi sshjtest@TranceLove
|
||||||
Reference in New Issue
Block a user