Compare commits

..

1 Commits

Author SHA1 Message Date
Jeroen van Erp
b3b0af3265 Add Automatic-Module-Name to MANIFEST.MF 2021-08-27 15:35:06 +02:00
82 changed files with 1163 additions and 2483 deletions

View File

@@ -24,8 +24,19 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew check
- name: Codecov
uses: codecov/codecov-action@v2
# java10:
# name: Build with Java 10
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - name: Set up JDK 10
# uses: actions/setup-java@v1
# with:
# java-version: 10
# - name: Grant execute permission for gradlew
# run: chmod +x gradlew
# - name: Build with Gradle
# run: ./gradlew check -xanimalsnifferMain -xanimalsnifferTest
integration:
name: Integration test

View File

@@ -1,49 +0,0 @@
name: SSHJ Release
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
java12:
name: Build with Java 12
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 12
uses: actions/setup-java@v1
with:
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew check
release:
name: Release
needs: [java12]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v1
with:
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Release
run: ./gradlew release -Prelease.disableChecks -Prelease.pushTagsOnly -Prelease.customUsername=${{ github.actor }} -Prelease.customPassword=${{ github.token }}
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNINGKEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNINGKEYID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNINGPASSWORD }}
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }}

View File

@@ -2,6 +2,5 @@
"java.checkstyle.configuration": "${workspaceFolder}/gradle/config/checkstyle/checkstyle.xml",
"files.watcherExclude": {
"**/target": true
},
"java.configuration.updateBuildConfiguration": "automatic"
}
}

View File

@@ -1,11 +1,12 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.32.0
:sshj_version: 0.31.0
:source-highlighter: pygments
image:https://github.com/hierynomus/sshj/actions/workflows/gradle.yml/badge.svg[link="https://github.com/hierynomus/sshj/actions/workflows/gradle.yml"]
image:https://app.codacy.com/project/badge/Grade/2c8a5a67c6a54ed89c9a699fd6b27305["Codacy Grade", link="https://app.codacy.com/gh/hierynomus/sshj"]
image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"]
image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"]
image:https://api.codacy.com/project/badge/Grade/14a0a316bb9149739b5ea26dbfa8da8a["Codacy code quality", link="https://www.codacy.com/app/jeroen_2/sshj?utm_source=github.com&utm_medium=referral&utm_content=hierynomus/sshj&utm_campaign=Badge_Grade"]
image:https://codecov.io/gh/hierynomus/sshj/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/hierynomus/sshj"]
image:http://www.javadoc.io/badge/com.hierynomus/sshj.svg?color=blue["JavaDocs", link="http://www.javadoc.io/doc/com.hierynomus/sshj"]
image:https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj/badge.svg["Maven Central",link="https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj"]
@@ -104,36 +105,6 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
Fork away!
== 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)::
* 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/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/708[#708] and https://github.com/hierynomus/sshj/pull/713[#71]: Add support for PKCS#8 private keys
* Merged https://github.com/hierynomus/sshj/pull/703[#703]: Support host certificate keys
* Upgraded dependencies BouncyCastle (1.69), SLF4j (1.7.32), Logback (1.2.6), asn-one (0.6.0)
* Merged https://github.com/hierynomus/sshj/pull/702[#702]: Support Public key authentication using certificates
* Merged https://github.com/hierynomus/sshj/pull/691[#691]: Fix for writing negative unsigned integers to Buffer
* Merged https://github.com/hierynomus/sshj/pull/682[#682]: Support for chacha20-poly1305@openssh.com cipher
* Merged https://github.com/hierynomus/sshj/pull/680[#680]: Configurable preserve mtimes for SCP transfers
SSHJ 0.31.0 (2021-02-08)::
* Bump dependencies (asn-one 0.5.0, BouncyCastle 1.68, slf4j-api 1.7.30)
* Merged https://github.com/hierynomus/sshj/pull/660[#660]: Support ED25519 and ECDSA keys in PuTTY format

View File

@@ -1,23 +1,23 @@
import java.text.SimpleDateFormat
import com.bmuschko.gradle.docker.tasks.container.*
import com.bmuschko.gradle.docker.tasks.image.*
plugins {
id "java"
id "groovy"
id "jacoco"
id "com.github.blindpirate.osgi" version '0.0.6'
id "com.github.blindpirate.osgi" version '0.0.3'
id "maven-publish"
id "signing"
id 'pl.allegro.tech.build.axion-release' version '1.13.3'
id "com.bmuschko.docker-remote-api" version "7.1.0"
id "com.github.hierynomus.license" version "0.16.1"
id 'ru.vyarus.github-info' version '1.2.0'
id "io.github.gradle-nexus.publish-plugin" version "1.0.0"
}
repositories {
mavenCentral()
id 'pl.allegro.tech.build.axion-release' version '1.11.0'
id "com.bmuschko.docker-remote-api" version "6.4.0"
id "com.github.hierynomus.license" version "0.12.1"
id "com.jfrog.bintray" version "1.8.5"
id 'ru.vyarus.java-lib' version '1.0.5'
// id 'ru.vyarus.pom' version '1.0.3'
id 'ru.vyarus.github-info' version '1.1.0'
}
group = "com.hierynomus"
defaultTasks ["build"]
ext.moduleName = "${project.group}.${project.name}"
scmVersion {
@@ -33,30 +33,36 @@ scmVersion {
project.version = scmVersion.version
configurations.implementation.transitive = false
defaultTasks "build"
def bouncycastleVersion = "1.70"
def sshdVersion = "2.8.0"
repositories {
mavenCentral()
}
configurations.compile.transitive = false
def bouncycastleVersion = "1.69"
def sshdVersion = "2.1.0"
dependencies {
implementation "org.slf4j:slf4j-api:1.7.32"
implementation "org.slf4j:slf4j-api:1.7.30"
implementation "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
implementation "com.jcraft:jzlib:1.1.3"
implementation "com.hierynomus:asn-one:0.6.0"
implementation "com.hierynomus:asn-one:0.5.0"
implementation "net.i2p.crypto:eddsa:0.3.0"
testImplementation "junit:junit:4.13.2"
testImplementation "junit:junit:4.12"
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.4'
testImplementation "org.mockito:mockito-core:4.2.0"
testImplementation "org.mockito:mockito-core:2.28.2"
testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion"
testImplementation "org.apache.sshd:sshd-scp:$sshdVersion"
testImplementation "ch.qos.logback:logback-classic:1.2.9"
testRuntimeOnly "ch.qos.logback:logback-classic:1.2.3"
testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.13'
testImplementation 'org.testcontainers:testcontainers:1.16.2'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.9'
}
license {
@@ -65,7 +71,7 @@ license {
mapping {
java = 'SLASHSTAR_STYLE'
}
excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java', '**/com/hierynomus/sshj/userauth/keyprovider/bcrypt/*.java'])
excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java', '**/org/mindrot/jbcrypt/*.java'])
}
if (!JavaVersion.current().isJava9Compatible()) {
@@ -79,6 +85,7 @@ if (JavaVersion.current().isJava8Compatible()) {
}
}
compileJava {
options.compilerArgs.addAll(['--release', '7'])
}
@@ -116,12 +123,6 @@ jar {
}
}
java {
withJavadocJar()
withSourcesJar()
}
sourcesJar {
manifest {
attributes(
@@ -187,82 +188,71 @@ github {
license 'Apache'
}
publishing {
publications {
maven(MavenPublication) {
from(components.java)
}
}
}
project.signing {
required { project.gradle.taskGraph.hasTask("release") }
sign publishing.publications.maven
if (project.hasProperty("signingKeyId") || project.hasProperty("signingKey")) {
def signingKeyId = project.findProperty("signingKeyId")
def signingKey = project.findProperty("signingKey")
def signingPassword = project.findProperty("signingPassword")
if (signingKeyId) {
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
} else if (signingKey) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
}
}
project.plugins.withType(MavenPublishPlugin).all {
PublishingExtension publishing = project.extensions.getByType(PublishingExtension)
publishing.publications.withType(MavenPublication).all { mavenPublication ->
mavenPublication.pom {
name = "${project.name}"
description = 'SSHv2 library for Java'
inceptionYear = '2009'
url = "https://github.com/hierynomus/${project.name}"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0"
}
}
developers {
developer {
id = "hierynomus"
name = "Jeroen van Erp"
email = "jeroen@hierynomus.com"
}
developer {
id = "shikhar"
name = "Shikhar Bhushan"
email = "shikhar@schmizz.net"
url = "http://schmizz.net"
roles = ["Previous Lead developer"]
}
developer {
id = "iterate"
name = "David Kocher"
email = "dkocher@iterate.ch"
organization = "iterate GmbH"
organizationUrl = "https://iterate.ch"
roles = ["Developer"]
}
}
scm {
url = "https://github.com/hierynomus/${project.name}"
connection = "scm:git@github.com:hierynomus/${project.name}.git"
developerConnection = "scm:git@github.com:hierynomus/${project.name}.git"
pom {
description "SSHv2 library for Java"
url "https://github.com/hierynomus/sshj"
inceptionYear "2009"
developers {
developer {
id "hierynomus"
name "Jeroen van Erp"
email "jeroen@javadude.nl"
roles {
role "Lead developer"
}
}
}
}
developer {
id "shikhar"
name "Shikhar Bhushan"
email "shikhar@schmizz.net"
url "http://schmizz.net"
roles {
role "Previous lead developer"
}
}
developer {
id "iterate"
name "David Kocher"
email "dkocher@iterate.ch"
organization "iterage GmbH"
organizationUrl "https://iterate.ch"
roles {
role "Developer"
}
}
}
}
nexusPublishing {
repositories {
sonatype() //sonatypeUsername and sonatypePassword properties are used automatically
}
connectTimeout = Duration.ofMinutes(3)
clientTimeout = Duration.ofMinutes(3)
if (project.hasProperty("bintrayUsername") && project.hasProperty("bintrayApiKey")) {
bintray {
user = project.property("bintrayUsername")
key = project.property("bintrayApiKey")
publish = true
publications = ["maven"]
pkg {
repo = "maven"
name = "${project.name}"
licenses = ["Apache-2.0"]
vcsUrl = "https://github.com/hierynomus/sshj.git"
labels = ["ssh", "sftp", "secure-shell", "network", "file-transfer"]
githubRepo = "hierynomus/sshj"
version {
name = "${project.version}"
vcsTag = "v${project.version}"
released = new SimpleDateFormat('yyyy-MM-dd\'T\'HH:mm:ss.SSSZZ').format(new Date())
gpg {
sign = true
passphrase = project.property("signing.password")
}
mavenCentralSync {
sync = true
user = project.property("sonatypeUsername")
password = project.property("sonatypePassword")
close = 1
}
}
}
}
}
jacocoTestReport {
@@ -272,11 +262,48 @@ 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) {
buildFile = project.buildFile
tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
tasks = ["bintrayUpload"]
}
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.finalizedBy(project.tasks.forkedUploadRelease)
project.tasks.jacocoTestReport.dependsOn(project.tasks.test)

View File

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

View File

@@ -0,0 +1,24 @@
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"]

View File

@@ -3,7 +3,6 @@ ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0w
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local
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 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

View File

@@ -0,0 +1,158 @@
# $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

View File

@@ -0,0 +1,42 @@
/*
* 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())
}
}

View File

@@ -20,15 +20,9 @@ import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.transport.TransportException
import net.schmizz.sshj.userauth.UserAuthException
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class IntegrationSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class IntegrationSpec extends IntegrationBaseSpec {
@Unroll
def "should accept correct key for #signatureName"() {
@@ -39,7 +33,7 @@ class IntegrationSpec extends Specification {
sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint
when:
sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort)
sshClient.connect(SERVER_IP, DOCKER_PORT)
then:
sshClient.isConnected()
@@ -56,7 +50,7 @@ class IntegrationSpec extends Specification {
sshClient.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3")
when:
sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort)
sshClient.connect(SERVER_IP, DOCKER_PORT)
then:
thrown(TransportException.class)
@@ -65,11 +59,11 @@ class IntegrationSpec extends Specification {
@Unroll
def "should authenticate with key #key"() {
given:
SSHClient client = sshd.getConnectedClient()
SSHClient client = getConnectedClient()
when:
def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key")
client.authPublickey(IntegrationTestUtil.USERNAME, keyProvider)
client.authPublickey(USERNAME, keyProvider)
then:
client.isAuthenticated()
@@ -80,7 +74,6 @@ class IntegrationSpec extends Specification {
"id_ecdsa_opensshv1" | null
"id_ed25519_opensshv1" | null
"id_ed25519_opensshv1_aes256cbc.pem" | "foobar"
"id_ed25519_opensshv1_aes128cbc.pem" | "sshjtest"
"id_ed25519_opensshv1_protected" | "sshjtest"
"id_rsa" | null
"id_rsa_opensshv1" | null
@@ -90,7 +83,7 @@ class IntegrationSpec extends Specification {
def "should not authenticate with wrong key"() {
given:
SSHClient client = sshd.getConnectedClient()
SSHClient client = getConnectedClient()
when:
client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key")

View File

@@ -1,21 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj
class IntegrationTestUtil {
static final String USERNAME = "sshj"
static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa"
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj
import com.hierynomus.sshj.key.KeyAlgorithms
import net.schmizz.sshj.Config
import net.schmizz.sshj.DefaultConfig
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder
import spock.lang.Specification
import spock.lang.Unroll
import java.nio.file.Paths
/**
* Checks that SSHJ is able to work with OpenSSH 8.8, which removed ssh-rsa signature from the default setup.
*/
class RsaShaKeySignatureTest extends Specification {
private static final Map<String, KeyAlgorithms.Factory> SSH_HOST_KEYS_AND_FACTORIES = [
'ssh_host_ecdsa_256_key': KeyAlgorithms.ECDSASHANistp256(),
'ssh_host_ecdsa_384_key': KeyAlgorithms.ECDSASHANistp384(),
'ssh_host_ecdsa_521_key': KeyAlgorithms.ECDSASHANistp521(),
'ssh_host_ed25519_384_key': KeyAlgorithms.EdDSA25519(),
'ssh_host_rsa_2048_key': KeyAlgorithms.RSASHA512(),
]
private static void dockerfileBuilder(DockerfileBuilder it, String hostKey, String pubkeyAcceptedAlgorithms) {
it.from("archlinux:base")
it.run('yes | pacman -Sy core/openssh' +
' && (' +
' V=$(echo $(/usr/sbin/sshd -h 2>&1) | grep -o \'OpenSSH_[0-9][0-9]*[.][0-9][0-9]*p[0-9]\');' +
' if [[ "$V" < OpenSSH_8.8p1 ]]; then' +
' echo $V is too old 1>&2;' +
' exit 1;' +
' fi' +
')' +
' && set -o pipefail ' +
' && useradd --create-home sshj' +
' && echo \"sshj:ultrapassword\" | chpasswd')
it.add("authorized_keys", "/home/sshj/.ssh/")
it.add(hostKey, '/etc/ssh/')
it.run('chmod go-rwx /etc/ssh/ssh_host_*' +
' && chown -R sshj /home/sshj/.ssh' +
' && chmod -R go-rwx /home/sshj/.ssh')
it.expose(22)
def cmd = [
'/usr/sbin/sshd',
'-D',
'-e',
'-f', '/dev/null',
'-o', 'LogLevel=DEBUG2',
'-o', "HostKey=/etc/ssh/$hostKey",
]
if (pubkeyAcceptedAlgorithms != null) {
cmd += ['-o', "PubkeyAcceptedAlgorithms=$pubkeyAcceptedAlgorithms"]
}
it.cmd(cmd as String[])
}
private static SshdContainer makeSshdContainer(String hostKey, String pubkeyAcceptedAlgorithms) {
return new SshdContainer(new SshdContainer.DebugLoggingImageFromDockerfile()
.withFileFromPath("authorized_keys", Paths.get("src/itest/docker-image/authorized_keys"))
.withFileFromPath(hostKey, Paths.get("src/itest/docker-image/test-container/host_keys/$hostKey"))
.withDockerfileFromBuilder {
dockerfileBuilder(it, hostKey, pubkeyAcceptedAlgorithms)
})
}
@Unroll
def "connect to a server with host key #hostKey that does not support ssh-rsa"() {
given:
SshdContainer sshd = makeSshdContainer(hostKey, "rsa-sha2-512,rsa-sha2-256,ssh-ed25519")
sshd.start()
and:
Config config = new DefaultConfig()
config.keyAlgorithms = [
KeyAlgorithms.RSASHA512(),
KeyAlgorithms.RSASHA256(),
SSH_HOST_KEYS_AND_FACTORIES[hostKey],
]
when:
def sshClient = sshd.getConnectedClient(config)
sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1")
then:
sshClient.isAuthenticated()
cleanup:
sshClient?.disconnect()
sshd.stop()
where:
hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet()
}
@Unroll
def "connect to a default server with host key #hostKey using a default config"() {
given:
SshdContainer sshd = makeSshdContainer(hostKey, null)
sshd.start()
when:
def sshClient = sshd.getConnectedClient()
sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1")
then:
sshClient.isAuthenticated()
cleanup:
sshClient?.disconnect()
sshd.stop()
where:
hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet()
}
@Unroll
def "connect to a server with host key #hostkey that supports only ssh-rsa"() {
given:
SshdContainer sshd = makeSshdContainer(hostKey, "ssh-rsa,ssh-ed25519")
sshd.start()
and:
Config config = new DefaultConfig()
config.keyAlgorithms = [
KeyAlgorithms.SSHRSA(),
SSH_HOST_KEYS_AND_FACTORIES[hostKey],
]
when:
def sshClient = sshd.getConnectedClient(config)
sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1")
then:
sshClient.isAuthenticated()
cleanup:
sshClient.disconnect()
sshd.stop()
where:
hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet()
}
}

View File

@@ -1,77 +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 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;
}
}

View File

@@ -1,155 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
import org.testcontainers.utility.DockerLoggerFactory;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.Future;
/**
* A JUnit4 rule for launching a generic SSH server container.
*/
public class SshdContainer extends GenericContainer<SshdContainer> {
/**
* A workaround for strange logger names of testcontainers. They contain no dots, but contain slashes,
* square brackets, and even emoji. It's uneasy to set the logging level via the XML file of logback, the
* result would be less readable than the code below.
*/
public static class DebugLoggingImageFromDockerfile extends ImageFromDockerfile {
public DebugLoggingImageFromDockerfile() {
super();
Logger logger = (Logger) LoggerFactory.getILoggerFactory()
.getLogger(DockerLoggerFactory.getLogger(getDockerImageName()).getName());
logger.setLevel(Level.DEBUG);
}
}
public static class Builder {
public static final String DEFAULT_SSHD_CONFIG = "" +
"PermitRootLogin yes\n" +
"AuthorizedKeysFile .ssh/authorized_keys\n" +
"Subsystem sftp /usr/lib/ssh/sftp-server\n" +
"KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1\n" +
"macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com\n" +
"TrustedUserCAKeys /etc/ssh/trusted_ca_keys\n" +
"Ciphers 3des-cbc,blowfish-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com\n" +
"HostKey /etc/ssh/ssh_host_rsa_key\n" +
"HostKey /etc/ssh/ssh_host_dsa_key\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_key\n" +
"HostKey /etc/ssh/ssh_host_ed25519_key\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_256_key\n" +
"HostCertificate /etc/ssh/ssh_host_ecdsa_256_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_384_key\n" +
"HostCertificate /etc/ssh/ssh_host_ecdsa_384_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_521_key\n" +
"HostCertificate /etc/ssh/ssh_host_ecdsa_521_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_ed25519_384_key\n" +
"HostCertificate /etc/ssh/ssh_host_ed25519_384_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_rsa_2048_key\n" +
"HostCertificate /etc/ssh/ssh_host_rsa_2048_key-cert.pub\n" +
"LogLevel DEBUG2\n";
public static void defaultDockerfileBuilder(@NotNull DockerfileBuilder builder) {
builder.from("sickp/alpine-sshd:7.5-r2");
builder.add("authorized_keys", "/home/sshj/.ssh/authorized_keys");
builder.add("test-container/ssh_host_ecdsa_key", "/etc/ssh/ssh_host_ecdsa_key");
builder.add("test-container/ssh_host_ecdsa_key.pub", "/etc/ssh/ssh_host_ecdsa_key.pub");
builder.add("test-container/ssh_host_ed25519_key", "/etc/ssh/ssh_host_ed25519_key");
builder.add("test-container/ssh_host_ed25519_key.pub", "/etc/ssh/ssh_host_ed25519_key.pub");
builder.copy("test-container/trusted_ca_keys", "/etc/ssh/trusted_ca_keys");
builder.copy("test-container/host_keys/*", "/etc/ssh/");
builder.run("apk add --no-cache tini"
+ " && echo \"root:smile\" | chpasswd"
+ " && adduser -D -s /bin/ash sshj"
+ " && passwd -u sshj"
+ " && echo \"sshj:ultrapassword\" | chpasswd"
+ " && chmod 600 /home/sshj/.ssh/authorized_keys"
+ " && chmod 600 /etc/ssh/ssh_host_*_key"
+ " && chmod 644 /etc/ssh/*.pub"
+ " && chown -R sshj:sshj /home/sshj");
builder.entryPoint("/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2");
builder.add("sshd_config", "/etc/ssh/sshd_config");
}
private @NotNull String sshdConfig = DEFAULT_SSHD_CONFIG;
public @NotNull Builder withSshdConfig(@NotNull String sshdConfig) {
this.sshdConfig = sshdConfig;
return this;
}
public @NotNull SshdContainer build() {
return new SshdContainer(buildInner());
}
private @NotNull Future<String> buildInner() {
return new DebugLoggingImageFromDockerfile()
.withDockerfileFromBuilder(Builder::defaultDockerfileBuilder)
.withFileFromPath(".", Paths.get("src/itest/docker-image"))
.withFileFromString("sshd_config", sshdConfig);
}
}
@SuppressWarnings("unused") // Used dynamically by Spock
public SshdContainer() {
this(new SshdContainer.Builder().buildInner());
}
public SshdContainer(@NotNull Future<String> future) {
super(future);
withExposedPorts(22);
setWaitStrategy(new SshServerWaitStrategy());
withLogConsumer(outputFrame -> {
switch (outputFrame.getType()) {
case STDOUT:
logger().info("sshd stdout: {}", outputFrame.getUtf8String().stripTrailing());
break;
case STDERR:
logger().info("sshd stderr: {}", outputFrame.getUtf8String().stripTrailing());
break;
}
});
}
public SSHClient getConnectedClient(Config config) throws IOException {
SSHClient sshClient = new SSHClient(config);
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
sshClient.connect("127.0.0.1", getFirstMappedPort());
return sshClient;
}
public SSHClient getConnectedClient() throws IOException {
return getConnectedClient(new DefaultConfig());
}
}

View File

@@ -15,28 +15,21 @@
*/
package com.hierynomus.sshj.sftp
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.sftp.OpenMode
import net.schmizz.sshj.sftp.RemoteFile
import net.schmizz.sshj.sftp.SFTPClient
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import java.nio.charset.StandardCharsets
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
class FileWriteSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class FileWriteSpec extends IntegrationBaseSpec {
def "should append to file (GH issue #390)"() {
given:
SSHClient client = sshd.getConnectedClient()
SSHClient client = getConnectedClient()
client.authPublickey("sshj", "src/test/resources/id_rsa")
SFTPClient sftp = client.newSFTPClient()
def file = "/home/sshj/test.txt"

View File

@@ -1,83 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts
import spock.lang.Specification
import spock.lang.Unroll
import java.nio.file.Files
/**
* This is a brief test for verifying connection to a server using keys with certificates.
*
* Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}.
*/
class HostKeyWithCertificateSpec extends Specification {
@Unroll
def "accepting a signed host public key #hostKey"() {
given:
SshdContainer sshd = new SshdContainer.Builder()
.withSshdConfig("""
PasswordAuthentication yes
HostKey /etc/ssh/$hostKey
HostCertificate /etc/ssh/${hostKey}-cert.pub
""".stripMargin())
.build()
sshd.start()
and:
File knownHosts = Files.createTempFile("known_hosts", "").toFile()
knownHosts.deleteOnExit()
and:
File caPubKey = new File("src/itest/resources/keyfiles/certificates/CA_rsa.pem.pub")
def address = "127.0.0.1"
String knownHostsFileContents = "" +
"@cert-authority ${ address} ${caPubKey.text}" +
"\n@cert-authority [${address}]:${sshd.firstMappedPort} ${caPubKey.text}"
knownHosts.write(knownHostsFileContents)
and:
SSHClient sshClient = new SSHClient(new DefaultConfig())
sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(knownHosts))
sshClient.connect(address, sshd.firstMappedPort)
when:
sshClient.authPassword("sshj", "ultrapassword")
then:
sshClient.authenticated
and:
knownHosts.getText() == knownHostsFileContents
cleanup:
sshd.stop()
where:
hostKey << [
"ssh_host_ecdsa_256_key",
"ssh_host_ecdsa_384_key",
"ssh_host_ecdsa_521_key",
"ssh_host_ed25519_384_key",
"ssh_host_rsa_2048_key",
]
}
}

View File

@@ -15,34 +15,29 @@
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts
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.
*
* Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}.
*/
class PublicKeyAuthWithCertificateSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class KeyWithCertificateSpec extends IntegrationBaseSpec {
@Unroll
def "authorising with a signed public key #keyName"() {
given:
SSHClient client = new SSHClient(new DefaultConfig())
client.addHostKeyVerifier(new PromiscuousVerifier())
client.connect("127.0.0.1", sshd.firstMappedPort)
def client = getConnectedClient()
when:
client.authPublickey("sshj", "src/itest/resources/keyfiles/certificates/$keyName")
client.authPublickey(USERNAME, "src/itest/resources/keyfiles/certificates/$keyName")
then:
client.authenticated
@@ -78,4 +73,43 @@ class PublicKeyAuthWithCertificateSpec extends Specification {
"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",
]
}
}

View File

@@ -15,25 +15,18 @@
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.DefaultConfig
import spock.lang.Unroll
class RsaSignatureClientKeySpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class RsaSignatureClientKeySpec extends IntegrationBaseSpec {
@Unroll
def "should correctly connect using publickey auth with RSA key with signature"() {
given:
def client = sshd.getConnectedClient()
def client = getConnectedClient(new DefaultConfig())
when:
client.authPublickey(IntegrationTestUtil.USERNAME, "src/itest/resources/keyfiles/id_rsa2")
client.authPublickey(USERNAME, "src/itest/resources/keyfiles/id_rsa2")
then:
client.authenticated

View File

@@ -15,29 +15,22 @@
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.key.KeyAlgorithms
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class SignatureSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class SignatureSpec extends IntegrationBaseSpec {
@Unroll
def "should correctly connect with #sig Signature"() {
given:
def cfg = new DefaultConfig()
cfg.setKeyAlgorithms(Collections.singletonList(sigFactory))
def client = sshd.getConnectedClient(cfg)
def client = getConnectedClient(cfg)
when:
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
client.authPublickey(USERNAME, KEYFILE)
then:
client.authenticated

View File

@@ -15,28 +15,21 @@
*/
package com.hierynomus.sshj.transport.cipher
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class CipherSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class CipherSpec extends IntegrationBaseSpec {
@Unroll
def "should correctly connect with #cipher Cipher"() {
given:
def cfg = new DefaultConfig()
cfg.setCipherFactories(cipherFactory)
def client = sshd.getConnectedClient(cfg)
def client = getConnectedClient(cfg)
when:
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
client.authPublickey(USERNAME, KEYFILE)
then:
client.authenticated

View File

@@ -15,32 +15,29 @@
*/
package com.hierynomus.sshj.transport.kex
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.transport.mac.Macs
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.transport.kex.Curve25519DH
import net.schmizz.sshj.transport.kex.Curve25519SHA256
import net.schmizz.sshj.transport.kex.DH
import net.schmizz.sshj.transport.kex.DHGexSHA1
import net.schmizz.sshj.transport.kex.DHGexSHA256
import net.schmizz.sshj.transport.kex.ECDH
import net.schmizz.sshj.transport.kex.ECDHNistP
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class KexSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class KexSpec extends IntegrationBaseSpec {
@Unroll
def "should correctly connect with #kex Key Exchange"() {
given:
def cfg = new DefaultConfig()
cfg.setKeyExchangeFactories(kexFactory)
def client = sshd.getConnectedClient(cfg)
def client = getConnectedClient(cfg)
when:
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
client.authPublickey(USERNAME, KEYFILE)
then:
client.authenticated

View File

@@ -15,28 +15,21 @@
*/
package com.hierynomus.sshj.transport.mac
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class MacSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
class MacSpec extends IntegrationBaseSpec {
@Unroll
def "should correctly connect with #mac MAC"() {
given:
def cfg = new DefaultConfig()
cfg.setMACFactories(macFactory)
def client = sshd.getConnectedClient(cfg)
def client = getConnectedClient(cfg)
when:
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
client.authPublickey(USERNAME, KEYFILE)
then:
client.authenticated
@@ -54,10 +47,10 @@ class MacSpec extends Specification {
given:
def cfg = new DefaultConfig()
cfg.setMACFactories(macFactory)
def client = sshd.getConnectedClient(cfg)
def client = getConnectedClient(cfg)
when:
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
client.authPublickey(USERNAME, KEYFILE)
then:
client.authenticated

View File

@@ -1,3 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfrL8SxDyrkNlsJdAmc7Z0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBiAAAAkLVqaDIfs+sPBNyy7ytdLnP/xH7Nt5FIXx3Upw6wKuMGdBzbFQLcvu60Le+SFP3uUfXE8TcHramXbH0n+UBMW6raCAKOkHUU1BtrKxPG1eKU/LBx3Bk5FxyKm7fo0XsCUmqSVK25EHOJfYq1QwIbWICkvQUNu+2Hg8/MQKoFJMentI+GqjdaG76f6Wf+aj9UwA==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -13,20 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
package com.hierynomus.sshj.backport;
public enum RenameFlags {
OVERWRITE(1),
ATOMIC(2),
NATIVE(4);
public class JavaVersion {
public static boolean isJava7OrEarlier() {
String property = System.getProperty("java.specification.version");
float diff = Float.parseFloat(property) - 1.7f;
private final long flag;
RenameFlags(long flag) {
this.flag = flag;
return diff < 0.01;
}
public long longValue() {
return flag;
}
}

View File

@@ -1,27 +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.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();
}

View File

@@ -1,35 +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.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);
}
}

View File

@@ -27,6 +27,8 @@ import java.util.List;
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 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); }
@@ -59,10 +61,6 @@ public class KeyAlgorithms {
return algorithmName;
}
public KeyType getKeyType() {
return keyType;
}
@Override
public KeyAlgorithm create() {
return new BaseKeyAlgorithm(algorithmName, signatureFactory, keyType);

View File

@@ -30,7 +30,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import com.hierynomus.sshj.userauth.keyprovider.bcrypt.BCrypt;
import org.mindrot.jbcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.util.Arrays;
/**
@@ -177,11 +177,11 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
Arrays.fill(charBuffer.array(), '\u0000');
Arrays.fill(byteBuffer.array(), (byte) 0);
}
byte[] keyiv = new byte[cipher.getIVSize()+ cipher.getBlockSize()];
byte[] keyiv = new byte[48];
new BCrypt().pbkdf(passphrase, opts.readBytes(), opts.readUInt32AsInt(), keyiv);
Arrays.fill(passphrase, (byte) 0);
byte[] key = Arrays.copyOfRange(keyiv, 0, cipher.getBlockSize());
byte[] iv = Arrays.copyOfRange(keyiv, cipher.getBlockSize(), cipher.getIVSize() + cipher.getBlockSize());
byte[] key = Arrays.copyOfRange(keyiv, 0, 32);
byte[] iv = Arrays.copyOfRange(keyiv, 32, 48);
cipher.init(Cipher.Mode.Decrypt, key, iv);
} else {
throw new IllegalStateException("No support for KDF '" + kdfName + "'.");
@@ -193,8 +193,6 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
return BlockCiphers.AES256CTR().create();
} else if (cipherName.equals(BlockCiphers.AES256CBC().getName())) {
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");
}
@@ -218,9 +216,6 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
while (line != null && !line.startsWith(BEGIN)) {
line = reader.readLine();
}
if (line == null) {
return false;
}
line = line.substring(BEGIN.length());
return line.startsWith(OPENSSH_PRIVATE_KEY);
}
@@ -250,9 +245,13 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
break;
case RSA:
final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
final PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(rsaPrivateCrtKeySpec);
kp = new KeyPair(publicKey, privateKey);
BigInteger n = keyBuffer.readMPInt(); // Modulus
keyBuffer.readMPInt(); // Public Exponent
BigInteger d = keyBuffer.readMPInt(); // Private Exponent
keyBuffer.readMPInt(); // iqmp (q^-1 mod p)
keyBuffer.readMPInt(); // p (Prime 1)
keyBuffer.readMPInt(); // q (Prime 2)
kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(new RSAPrivateKeySpec(n, d)));
break;
case ECDSA256:
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
@@ -285,35 +284,6 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
}
/**
* Read RSA Private CRT Key Spec according to OpenSSH sshkey_private_deserialize in sshkey.c
*
* @param buffer Buffer
* @return RSA Private CRT Key Specification
* @throws Buffer.BufferException Thrown on failure to read from buffer
*/
private RSAPrivateCrtKeySpec readRsaPrivateKeySpec(final PlainBuffer buffer) throws Buffer.BufferException {
final BigInteger modulus = buffer.readMPInt();
final BigInteger publicExponent = buffer.readMPInt();
final BigInteger privateExponent = buffer.readMPInt();
final BigInteger crtCoefficient = buffer.readMPInt(); // iqmp (q^-1 mod p)
final BigInteger primeP = buffer.readMPInt();
final BigInteger primeQ = buffer.readMPInt();
// Calculate Prime Exponent P and Prime Exponent Q according to RFC 8017 Section 3.2
final BigInteger primeExponentP = privateExponent.remainder(primeP.subtract(BigInteger.ONE));
final BigInteger primeExponentQ = privateExponent.remainder(primeQ.subtract(BigInteger.ONE));
return new RSAPrivateCrtKeySpec(
modulus,
publicExponent,
privateExponent,
primeP,
primeQ,
primeExponentP,
primeExponentQ,
crtCoefficient
);
}
}

View File

@@ -24,7 +24,7 @@ final class Heartbeater
extends KeepAlive {
Heartbeater(ConnectionImpl conn) {
super(conn, "sshj-Heartbeater");
super(conn, "heartbeater");
}
@Override

View File

@@ -20,8 +20,6 @@ import net.schmizz.sshj.connection.ConnectionImpl;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import java.util.concurrent.TimeUnit;
public abstract class KeepAlive extends Thread {
protected final Logger log;
protected final ConnectionImpl conn;
@@ -35,32 +33,40 @@ public abstract class KeepAlive extends Thread {
setDaemon(true);
}
public boolean isEnabled() {
return keepAliveInterval > 0;
}
public synchronized int getKeepAliveInterval() {
return keepAliveInterval;
}
public synchronized void setKeepAliveInterval(int 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
public void run() {
log.debug("{} Started with interval [{} seconds]", getClass().getSimpleName(), keepAliveInterval);
log.debug("Starting {}, sending keep-alive every {} seconds", getClass().getSimpleName(), keepAliveInterval);
try {
while (!isInterrupted()) {
final int interval = getKeepAliveInterval();
final int hi = getPositiveInterval();
if (conn.getTransport().isRunning()) {
log.debug("{} Sending after interval [{} seconds]", getClass().getSimpleName(), interval);
log.debug("Sending keep-alive since {} seconds elapsed", hi);
doKeepAlive();
}
TimeUnit.SECONDS.sleep(interval);
Thread.sleep(hi * 1000);
}
} catch (InterruptedException e) {
log.trace("{} Interrupted while sleeping", getClass().getSimpleName());
// Interrupt signal may be catched when sleeping.
} catch (Exception e) {
// 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.
@@ -68,7 +74,9 @@ public abstract class KeepAlive extends Thread {
conn.getTransport().die(e);
}
}
log.debug("{} Stopped", getClass().getSimpleName());
log.debug("Stopping {}", getClass().getSimpleName());
}
protected abstract void doKeepAlive() throws TransportException, ConnectionException;

View File

@@ -37,7 +37,7 @@ public class KeepAliveRunner extends KeepAlive {
new LinkedList<Promise<SSHPacket, ConnectionException>>();
KeepAliveRunner(ConnectionImpl conn) {
super(conn, "sshj-KeepAliveRunner");
super(conn, "keep-alive");
}
synchronized public int getMaxAliveCount() {

View File

@@ -26,7 +26,6 @@ import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.random.Random;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -189,30 +188,4 @@ public class ConfigImpl
public void setVerifyHostKeyCertificates(boolean 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;
}
}
}
}

View File

@@ -15,8 +15,6 @@
*/
package net.schmizz.sshj;
import net.schmizz.keepalive.KeepAlive;
import com.hierynomus.sshj.common.ThreadNameProvider;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
@@ -57,7 +55,6 @@ import javax.security.auth.login.LoginContext;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.security.KeyPair;
@@ -143,7 +140,7 @@ public class SSHClient
super(DEFAULT_PORT);
loggerFactory = config.getLoggerFactory();
log = loggerFactory.getLogger(getClass());
this.trans = new TransportImpl(config);
this.trans = new TransportImpl(config, this);
this.auth = new UserAuthImpl(trans);
this.conn = new ConnectionImpl(trans, config.getKeepAliveProvider());
}
@@ -427,7 +424,6 @@ public class SSHClient
@Override
public void disconnect()
throws IOException {
conn.getKeepAlive().interrupt();
for (LocalPortForwarder forwarder : forwarders) {
try {
forwarder.close();
@@ -445,16 +441,6 @@ public class SSHClient
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).
*
@@ -805,11 +791,6 @@ public class SSHClient
throws IOException {
super.onConnect();
trans.init(getRemoteHostname(), getRemotePort(), getInputStream(), getOutputStream());
final KeepAlive keepAliveThread = conn.getKeepAlive();
if (keepAliveThread.isEnabled()) {
ThreadNameProvider.setThreadName(conn.getKeepAlive(), trans);
keepAliveThread.start();
}
doKex();
}

View File

@@ -15,6 +15,8 @@
*/
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.direct.DirectConnection;
@@ -24,6 +26,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
public abstract class SocketClient {
@@ -54,6 +57,73 @@ public abstract class SocketClient {
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 {
connect(hostname, defaultPort);
}

View File

@@ -145,14 +145,8 @@ public class StreamCopier {
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)));
// Did we encounter EOF?
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");
}
if (length != -1 && read == -1)
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");
return count;
}

View File

@@ -22,7 +22,6 @@ import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* {@link OutputStream} for channels. Buffers data upto the remote window's maximum packet size. Data can also be
@@ -37,7 +36,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
private final DataBuffer buffer = new DataBuffer();
private final byte[] b = new byte[1];
private AtomicBoolean closed;
private boolean closed;
private SSHException error;
private final class DataBuffer {
@@ -123,7 +122,6 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
this.chan = chan;
this.trans = trans;
this.win = win;
this.closed = new AtomicBoolean(false);
}
@Override
@@ -153,21 +151,24 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
private void checkClose() throws SSHException {
// Check whether either the Stream is closed, or the underlying channel is closed
if (closed.get() || !chan.isOpen()) {
if (error != null) {
if (closed || !chan.isOpen()) {
if (error != null)
throw error;
} else {
else
throw new ConnectionException("Stream closed");
}
}
}
@Override
public synchronized void close() throws IOException {
// Not closed yet, and underlying channel is open to flush the data to.
if (!closed.getAndSet(true) && chan.isOpen()) {
buffer.flush(false);
trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
if (!closed && chan.isOpen()) {
try {
buffer.flush(false);
// trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
} finally {
closed = true;
}
}
}

View File

@@ -15,11 +15,10 @@
*/
package net.schmizz.sshj.connection.channel.direct;
import com.hierynomus.sshj.common.RemoteAddressProvider;
import net.schmizz.sshj.common.SSHException;
/** A factory interface for creating SSH {@link Session session channels}. */
public interface SessionFactory extends RemoteAddressProvider {
public interface SessionFactory {
/**
* Opens a {@code session} channel. The returned {@link Session} instance allows {@link Session#exec(String)
@@ -28,7 +27,7 @@ public interface SessionFactory extends RemoteAddressProvider {
*
* @return the opened {@code session} channel
*
* @throws SSHException Thrown on session initialization failures
* @throws SSHException
* @see Session
*/
Session startSession()

View File

@@ -142,10 +142,6 @@ public class RemotePortForwarder
// Listen on all IPv4
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;
}

View File

@@ -41,7 +41,7 @@ public class PacketReader extends Thread {
this.engine = engine;
log = engine.getLoggerFactory().getLogger(getClass());
this.in = engine.getSubsystem().getInputStream();
setName("sshj-PacketReader");
setName("sftp reader");
setDaemon(true);
}

View File

@@ -23,8 +23,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
@@ -222,95 +220,49 @@ public class RemoteFile
public class ReadAheadRemoteFileInputStream
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 int maxUnconfirmedReads;
private final long readAheadLimit;
private final Deque<UnconfirmedRead> unconfirmedReads = new ArrayDeque<>();
private final Queue<Promise<Response, SFTPException>> unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
private final Queue<Long> unconfirmedReadOffsets = new LinkedList<Long>();
private long currentOffset;
private int maxReadLength = Integer.MAX_VALUE;
private long requestOffset;
private long responseOffset;
private boolean eof;
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads) {
this(maxUnconfirmedReads, 0L, -1L);
assert 0 <= maxUnconfirmedReads;
this.maxUnconfirmedReads = maxUnconfirmedReads;
}
/**
*
* @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) {
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset) {
assert 0 <= maxUnconfirmedReads;
assert 0 <= fileOffset;
this.maxUnconfirmedReads = maxUnconfirmedReads;
this.currentOffset = fileOffset;
this.readAheadLimit = readAheadLimit > 0 ? fileOffset + readAheadLimit : Long.MAX_VALUE;
this.requestOffset = this.responseOffset = fileOffset;
}
private ByteArrayInputStream pending = new ByteArrayInputStream(new byte[0]);
private boolean retrieveUnconfirmedRead(boolean blocking) throws IOException {
final UnconfirmedRead unconfirmedRead = unconfirmedReads.peek();
if (unconfirmedRead == null || !blocking && !unconfirmedRead.getPromise().isDelivered()) {
if (unconfirmedReads.size() <= 0) {
return false;
}
unconfirmedReads.remove(unconfirmedRead);
final Response res = unconfirmedRead.promise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
if (!blocking && !unconfirmedReads.peek().isDelivered()) {
return false;
}
unconfirmedReadOffsets.remove();
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
switch (res.getType()) {
case DATA:
int recvLen = res.readUInt32AsInt();
if (unconfirmedRead.offset == currentOffset) {
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();
}
}
responseOffset += recvLen;
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
break;
case STATUS:
@@ -338,31 +290,40 @@ public class RemoteFile
// 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
long requestOffset;
if (unconfirmedReads.isEmpty()) {
requestOffset = currentOffset;
}
else {
final UnconfirmedRead lastRequest = unconfirmedReads.getLast();
requestOffset = lastRequest.offset + lastRequest.length;
}
while (unconfirmedReads.size() <= maxUnconfirmedReads) {
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
int reqLen = Math.min(Math.max(1024, len), maxReadLength);
if (readAheadLimit > requestOffset) {
long remaining = readAheadLimit - requestOffset;
if (reqLen > remaining) {
reqLen = (int) remaining;
}
}
unconfirmedReads.add(new UnconfirmedRead(requestOffset, reqLen));
int reqLen = Math.max(1024, len); // don't be shy!
unconfirmedReads.add(RemoteFile.this.asyncRead(requestOffset, reqLen));
unconfirmedReadOffsets.add(requestOffset);
requestOffset += reqLen;
if (requestOffset >= readAheadLimit) {
break;
}
}
if (!retrieveUnconfirmedRead(true /*blocking*/)) {
long nextOffset = unconfirmedReadOffsets.peek();
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
// currently, we should never get here...

View File

@@ -115,13 +115,9 @@ public class SFTPClient
}
}
public void rename(String oldpath, String newpath) throws IOException {
rename(oldpath, newpath, EnumSet.noneOf(RenameFlags.class));
}
public void rename(String oldpath, String newpath, Set<RenameFlags> renameFlags)
public void rename(String oldpath, String newpath)
throws IOException {
engine.rename(oldpath, newpath, renameFlags);
engine.rename(oldpath, newpath);
}
public void rm(String filename)

View File

@@ -15,7 +15,6 @@
*/
package net.schmizz.sshj.sftp;
import com.hierynomus.sshj.common.ThreadNameProvider;
import net.schmizz.concurrent.Promise;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.LoggerFactory;
@@ -69,7 +68,6 @@ public class SFTPEngine
sub = session.startSubsystem("sftp");
out = sub.getOutputStream();
reader = new PacketReader(this);
ThreadNameProvider.setThreadName(reader, ssh);
pathHelper = new PathHelper(new PathHelper.Canonicalizer() {
@Override
public String canonicalize(String path)
@@ -232,22 +230,13 @@ public class SFTPEngine
return stat(PacketType.LSTAT, path);
}
public void rename(String oldPath, String newPath, Set<RenameFlags> flags)
public void rename(String oldPath, String newPath)
throws IOException {
if (operativeVersion < 1)
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
final Request request = newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset());
// SFTP Version 5 introduced rename flags according to Section 6.5 of the specification
if (operativeVersion >= 5) {
long renameFlagMask = 0L;
for (RenameFlags flag : flags) {
renameFlagMask = renameFlagMask | flag.longValue();
}
request.putUInt32(renameFlagMask);
}
doRequest(request).ensureStatusPacketIsOK();
doRequest(
newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset())
).ensureStatusPacketIsOK();
}
public String canonicalize(String path)

View File

@@ -117,9 +117,9 @@ public class StatefulSFTPClient
}
@Override
public void rename(String oldpath, String newpath, Set<RenameFlags> renameFlags)
public void rename(String oldpath, String newpath)
throws IOException {
super.rename(cwdify(oldpath), cwdify(newpath), renameFlags);
super.rename(cwdify(oldpath), cwdify(newpath));
}
@Override

View File

@@ -1,63 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.signature;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import com.hierynomus.asn1.ASN1OutputStream;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.IOUtils;
public abstract class AbstractSignatureDSA extends AbstractSignature {
protected AbstractSignatureDSA(String algorithm, String signatureName) {
super(algorithm, signatureName);
}
/**
* Get ASN.1 Signature encoded using DER Sequence of integers
*
* @param r DSA Signature R
* @param s DSA Signature S
* @return ASN.1 Encoded Signature
* @throws IOException Thrown when failing to write signature integers
*/
@SuppressWarnings("rawtypes")
protected byte[] encodeAsnSignature(final BigInteger r, final BigInteger s) throws IOException {
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new com.hierynomus.asn1.types.primitive.ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ASN1OutputStream asn1OutputStream = new ASN1OutputStream(new DEREncoder(), baos);
try {
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
} finally {
IOUtils.closeQuietly(asn1OutputStream);
}
return baos.toByteArray();
}
}

View File

@@ -15,19 +15,26 @@
*/
package net.schmizz.sshj.signature;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* DSA {@link Signature}
*/
public class SignatureDSA
extends AbstractSignatureDSA {
extends AbstractSignature {
/**
* A named factory for DSA signature
@@ -83,14 +90,32 @@ public class SignatureDSA
public boolean verify(byte[] sig) {
try {
byte[] sigBlob = extractSig(sig, "ssh-dss");
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sigBlob, 0, 20));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sigBlob, 20, 40));
return signature.verify(encodeAsnSignature(r, s));
return signature.verify(asnEncode(sigBlob));
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
} catch (IOException e) {
throw new SSHRuntimeException(e);
}
}
/**
* Encodes the signature as a DER sequence (ASN.1 format).
*/
private byte[] asnEncode(byte[] sigBlob) throws IOException {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sigBlob, 0, 20));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sigBlob, 20, 40));
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new com.hierynomus.asn1.types.primitive.ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
com.hierynomus.asn1.ASN1OutputStream asn1OutputStream = new com.hierynomus.asn1.ASN1OutputStream(new DEREncoder(), baos);
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
return baos.toByteArray();
}
}

View File

@@ -15,8 +15,9 @@
*/
package net.schmizz.sshj.signature;
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.Buffer;
@@ -25,12 +26,15 @@ import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.List;
/** ECDSA {@link Signature} */
public class SignatureECDSA extends AbstractSignatureDSA {
public class SignatureECDSA extends AbstractSignature {
/** A named factory for ECDSA-256 signature */
public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> {
@@ -87,7 +91,7 @@ public class SignatureECDSA extends AbstractSignatureDSA {
@Override
public byte[] encode(byte[] sig) {
ByteArrayInputStream bais = new ByteArrayInputStream(sig);
ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), bais);
com.hierynomus.asn1.ASN1InputStream asn1InputStream = new com.hierynomus.asn1.ASN1InputStream(new DERDecoder(), bais);
try {
ASN1Sequence sequence = asn1InputStream.readObject();
ASN1Integer r = (ASN1Integer) sequence.get(0);
@@ -106,14 +110,35 @@ public class SignatureECDSA extends AbstractSignatureDSA {
public boolean verify(byte[] sig) {
try {
byte[] sigBlob = extractSig(sig, keyTypeName);
Buffer.PlainBuffer sigbuf = new Buffer.PlainBuffer(sigBlob);
BigInteger r = sigbuf.readMPInt();
BigInteger s = sigbuf.readMPInt();
return signature.verify(encodeAsnSignature(r, s));
return signature.verify(asnEncode(sigBlob));
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
} catch (IOException e) {
throw new SSHRuntimeException(e);
}
}
/**
* Encodes the signature as a DER sequence (ASN.1 format).
*/
private byte[] asnEncode(byte[] sigBlob) throws IOException {
Buffer.PlainBuffer sigbuf = new Buffer.PlainBuffer(sigBlob);
BigInteger r = sigbuf.readMPInt();
BigInteger s = sigbuf.readMPInt();
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
com.hierynomus.asn1.ASN1OutputStream asn1OutputStream = new com.hierynomus.asn1.ASN1OutputStream(new DEREncoder(), baos);
try {
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
} finally {
IOUtils.closeQuietly(asn1OutputStream);
}
return baos.toByteArray();
}
}

View File

@@ -170,22 +170,11 @@ final class KeyExchanger
private void sendKexInit()
throws TransportException {
log.debug("Sending SSH_MSG_KEXINIT");
List<String> knownHostAlgs = findKnownHostAlgs(transport.getRemoteHost(), transport.getRemotePort());
clientProposal = new Proposal(transport.getConfig(), knownHostAlgs);
clientProposal = new Proposal(transport.getConfig());
transport.write(clientProposal.getPacket());
kexInitSent.set();
}
private List<String> findKnownHostAlgs(String hostname, int port) {
for (HostKeyVerifier hkv : hostVerifiers) {
List<String> keyTypes = hkv.findExistingAlgorithms(hostname, port);
if (keyTypes != null && !keyTypes.isEmpty()) {
return keyTypes;
}
}
return Collections.emptyList();
}
private void sendNewKeys()
throws TransportException {
log.debug("Sending SSH_MSG_NEWKEYS");
@@ -243,6 +232,7 @@ final class KeyExchanger
negotiatedAlgs.getKeyExchangeAlgorithm());
transport.setHostKeyAlgorithm(Factory.Named.Util.create(transport.getConfig().getKeyAlgorithms(),
negotiatedAlgs.getSignatureAlgorithm()));
transport.setRSASHA2Support(negotiatedAlgs.getRSASHA2Support());
try {
kex.init(transport,

View File

@@ -26,8 +26,10 @@ public final class NegotiatedAlgorithms {
private final String c2sComp;
private final String s2cComp;
private final boolean rsaSHA2Support;
NegotiatedAlgorithms(String kex, String sig, String c2sCipher, String s2cCipher, String c2sMAC, String s2cMAC,
String c2sComp, String s2cComp) {
String c2sComp, String s2cComp, boolean rsaSHA2Support) {
this.kex = kex;
this.sig = sig;
this.c2sCipher = c2sCipher;
@@ -36,6 +38,7 @@ public final class NegotiatedAlgorithms {
this.s2cMAC = s2cMAC;
this.c2sComp = c2sComp;
this.s2cComp = s2cComp;
this.rsaSHA2Support = rsaSHA2Support;
}
public String getKeyExchangeAlgorithm() {
@@ -70,6 +73,10 @@ public final class NegotiatedAlgorithms {
return s2cComp;
}
public boolean getRSASHA2Support() {
return rsaSHA2Support;
}
@Override
public String toString() {
return ("[ " +
@@ -81,6 +88,7 @@ public final class NegotiatedAlgorithms {
"s2cMAC=" + s2cMAC + "; " +
"c2sComp=" + c2sComp + "; " +
"s2cComp=" + s2cComp + "; " +
"rsaSHA2Support=" + rsaSHA2Support +
" ]");
}

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.key.KeyAlgorithms;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.Factory;
@@ -37,9 +38,9 @@ class Proposal {
private final List<String> s2cComp;
private final SSHPacket packet;
public Proposal(Config config, List<String> knownHostAlgs) {
public Proposal(Config config) {
kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
sig = filterKnownHostKeyAlgorithms(Factory.Named.Util.getNames(config.getKeyAlgorithms()), knownHostAlgs);
sig = Factory.Named.Util.getNames(config.getKeyAlgorithms());
c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());
@@ -126,45 +127,34 @@ class Proposal {
public NegotiatedAlgorithms negotiate(Proposal other)
throws TransportException {
return new NegotiatedAlgorithms(
firstMatch("KeyExchangeAlgorithms", this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
firstMatch("HostKeyAlgorithms", this.getHostKeyAlgorithms(), other.getHostKeyAlgorithms()),
firstMatch("Client2ServerCipherAlgorithms", this.getClient2ServerCipherAlgorithms(),
other.getClient2ServerCipherAlgorithms()),
firstMatch("Server2ClientCipherAlgorithms", this.getServer2ClientCipherAlgorithms(),
other.getServer2ClientCipherAlgorithms()),
firstMatch("Client2ServerMACAlgorithms", this.getClient2ServerMACAlgorithms(),
other.getClient2ServerMACAlgorithms()),
firstMatch("Server2ClientMACAlgorithms", this.getServer2ClientMACAlgorithms(),
other.getServer2ClientMACAlgorithms()),
firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
other.getClient2ServerCompressionAlgorithms()),
firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
other.getServer2ClientCompressionAlgorithms())
firstMatch("KeyExchangeAlgorithms",
this.getKeyExchangeAlgorithms(),
other.getKeyExchangeAlgorithms()),
firstMatch("HostKeyAlgorithms",
this.getHostKeyAlgorithms(),
other.getHostKeyAlgorithms()),
firstMatch("Client2ServerCipherAlgorithms",
this.getClient2ServerCipherAlgorithms(),
other.getClient2ServerCipherAlgorithms()),
firstMatch("Server2ClientCipherAlgorithms",
this.getServer2ClientCipherAlgorithms(),
other.getServer2ClientCipherAlgorithms()),
firstMatch("Client2ServerMACAlgorithms",
this.getClient2ServerMACAlgorithms(),
other.getClient2ServerMACAlgorithms()),
firstMatch("Server2ClientMACAlgorithms",
this.getServer2ClientMACAlgorithms(),
other.getServer2ClientMACAlgorithms()),
firstMatch("Client2ServerCompressionAlgorithms",
this.getClient2ServerCompressionAlgorithms(),
other.getClient2ServerCompressionAlgorithms()),
firstMatch("Server2ClientCompressionAlgorithms",
this.getServer2ClientCompressionAlgorithms(),
other.getServer2ClientCompressionAlgorithms()),
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS)
);
}
private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
if (knownHostKeyAlgorithms != null && !knownHostKeyAlgorithms.isEmpty()) {
List<String> preferredAlgorithms = new ArrayList<String>();
List<String> otherAlgorithms = new ArrayList<String>();
for (String configuredKeyAlgorithm : configuredKeyAlgorithms) {
if (knownHostKeyAlgorithms.contains(configuredKeyAlgorithm)) {
preferredAlgorithms.add(configuredKeyAlgorithm);
} else {
otherAlgorithms.add(configuredKeyAlgorithm);
}
}
preferredAlgorithms.addAll(otherAlgorithms);
return preferredAlgorithms;
} else {
return configuredKeyAlgorithms;
}
}
private static String firstMatch(String ofWhat, List<String> a, List<String> b)
throws TransportException {
for (String aa : a) {

View File

@@ -29,7 +29,7 @@ public final class Reader
public Reader(TransportImpl trans) {
this.trans = trans;
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
setName("sshj-Reader");
setName("reader");
setDaemon(true);
}

View File

@@ -15,7 +15,6 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.common.RemoteAddressProvider;
import com.hierynomus.sshj.key.KeyAlgorithm;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.Service;
@@ -28,12 +27,11 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;
/** Transport layer of the SSH protocol. */
public interface Transport
extends SSHPacketHandler, RemoteAddressProvider {
extends SSHPacketHandler {
/**
* Sets the host information and the streams to be used by this transport. Identification information is exchanged
@@ -87,6 +85,22 @@ public interface Transport
*/
void setTimeoutMs(int timeout);
/**
* @return the interval in seconds at which a heartbeat message is sent to the server
* @deprecated Moved to {@link net.schmizz.keepalive.KeepAlive#getKeepAliveInterval()}. This is accessible through the {@link net.schmizz.sshj.connection.Connection}.
* Scheduled to be removed in 0.12.0
*/
@Deprecated
int getHeartbeatInterval();
/**
* @param interval the interval in seconds, {@code 0} means no heartbeat
* @deprecated Moved to {@link net.schmizz.keepalive.KeepAlive#getKeepAliveInterval()}. This is accessible through the {@link net.schmizz.sshj.connection.Connection}.
* Scheduled to be removed in 0.12.0
*/
@Deprecated
void setHeartbeatInterval(int interval);
/** @return the hostname to which this transport is connected. */
String getRemoteHost();
@@ -210,7 +224,7 @@ public interface Transport
/**
* Specify a {@code listener} that will be notified upon disconnection.
*
* @param listener Disconnect Listener to be configured
* @param listener
*/
void setDisconnectListener(DisconnectListener listener);
@@ -225,5 +239,5 @@ public interface Transport
void die(Exception e);
KeyAlgorithm getHostKeyAlgorithm();
List<KeyAlgorithm> getClientKeyAlgorithms(KeyType keyType) throws TransportException;
KeyAlgorithm getClientKeyAlgorithm(KeyType keyType) throws TransportException;
}

View File

@@ -15,7 +15,6 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.common.ThreadNameProvider;
import com.hierynomus.sshj.key.KeyAlgorithm;
import com.hierynomus.sshj.key.KeyAlgorithms;
import com.hierynomus.sshj.transport.IdentificationStringParser;
@@ -23,6 +22,7 @@ import net.schmizz.concurrent.ErrorDeliveryUtil;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.Service;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
@@ -32,8 +32,6 @@ import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@@ -82,12 +80,20 @@ public final class TransportImpl
private final Reader reader;
/**
* @deprecated Moved to {@link net.schmizz.sshj.SSHClient}
*/
@Deprecated
private final SSHClient sshClient;
private final Encoder encoder;
private final Decoder decoder;
private KeyAlgorithm hostKeyAlgorithm;
private boolean rsaSHA2Support;
private final Event<TransportException> serviceAccept;
private final Event<TransportException> close;
@@ -130,8 +136,8 @@ public final class TransportImpl
public TransportImpl(Config config) {
this.config = config;
this.loggerFactory = config.getLoggerFactory();
this.serviceAccept = new Event<>("service accept", TransportException.chainer, loggerFactory);
this.close = new Event<>("transport close", TransportException.chainer, loggerFactory);
this.serviceAccept = new Event<TransportException>("service accept", TransportException.chainer, loggerFactory);
this.close = new Event<TransportException>("transport close", TransportException.chainer, loggerFactory);
this.nullService = new NullService(this);
this.service = nullService;
this.log = loggerFactory.getLogger(getClass());
@@ -141,6 +147,29 @@ public final class TransportImpl
this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this);
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
this.sshClient = null;
}
/*
* Temporary constructor until we remove support for the set/get Heartbeat interval from transport.
* @deprecated To be removed in 0.12.0
*/
@Deprecated
public TransportImpl(Config config, SSHClient sshClient) {
this.config = config;
this.loggerFactory = config.getLoggerFactory();
this.serviceAccept = new Event<TransportException>("service accept", TransportException.chainer, loggerFactory);
this.close = new Event<TransportException>("transport close", TransportException.chainer, loggerFactory);
this.log = loggerFactory.getLogger(getClass());
this.nullService = new NullService(this);
this.service = nullService;
this.disconnectListener = this;
this.reader = new Reader(this);
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock, loggerFactory);
this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this);
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
this.sshClient = sshClient;
}
@Override
@@ -165,20 +194,9 @@ public final class TransportImpl
throw new TransportException(e);
}
ThreadNameProvider.setThreadName(reader, this);
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.
*/
@@ -222,7 +240,7 @@ public final class TransportImpl
*
* @param buffer The buffer to read from.
* @return empty string if full ident string has not yet been received
* @throws IOException Thrown when protocol version is not supported
* @throws IOException
*/
private String readIdentification(Buffer.PlainBuffer buffer)
throws IOException {
@@ -268,6 +286,20 @@ public final class TransportImpl
this.timeoutMs = timeoutMs;
}
@Override
@Deprecated
public int getHeartbeatInterval() {
log.warn("**Deprecated**: Please use: sshClient.getConnection().getKeepAlive().getKeepAliveInterval()");
return sshClient.getConnection().getKeepAlive().getKeepAliveInterval();
}
@Override
@Deprecated
public void setHeartbeatInterval(int interval) {
log.warn("**Deprecated**: Please use: sshClient.getConnection().getKeepAlive().setKeepAliveInterval()");
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(interval);
}
@Override
public String getRemoteHost() {
return connInfo.host;
@@ -555,7 +587,7 @@ public final class TransportImpl
* Got an SSH_MSG_UNIMPLEMENTED, so lets see where we're at and act accordingly.
*
* @param packet The 'unimplemented' packet received
* @throws TransportException Thrown when key exchange is ongoing
* @throws TransportException
*/
private void gotUnimplemented(SSHPacket packet)
throws SSHException {
@@ -637,19 +669,21 @@ public final class TransportImpl
return this.hostKeyAlgorithm;
}
public void setRSASHA2Support(boolean rsaSHA2Support) {
this.rsaSHA2Support = rsaSHA2Support;
}
@Override
public List<KeyAlgorithm> getClientKeyAlgorithms(KeyType keyType) throws TransportException {
public KeyAlgorithm getClientKeyAlgorithm(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<KeyAlgorithm> available = new ArrayList<>();
if (factories != null)
for (Factory.Named<KeyAlgorithm> f : factories)
if (
f instanceof KeyAlgorithms.Factory && ((KeyAlgorithms.Factory) f).getKeyType().equals(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;
if (f.getName().equals("ssh-rsa") || KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS.contains(f.getName()))
return f.create();
throw new TransportException("Cannot find an available KeyAlgorithm for type " + keyType);
}
}

View File

@@ -20,8 +20,6 @@ import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import net.schmizz.sshj.common.Base64;
@@ -76,11 +74,6 @@ public class FingerprintVerifier implements HostKeyVerifier {
public boolean verify(String h, int p, PublicKey k) {
return SecurityUtils.getFingerprint(k).equals(md5);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
});
} catch (SSHRuntimeException e) {
throw e;
@@ -127,13 +120,8 @@ public class FingerprintVerifier implements HostKeyVerifier {
return Arrays.equals(fingerprintData, digestData);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
@Override
public String toString() {
return "FingerprintVerifier{digestAlgorithm='" + digestAlgorithm + "'}";
}
}
}

View File

@@ -16,7 +16,6 @@
package net.schmizz.sshj.transport.verification;
import java.security.PublicKey;
import java.util.List;
/** Host key verification interface. */
public interface HostKeyVerifier {
@@ -36,12 +35,4 @@ public interface HostKeyVerifier {
*/
boolean verify(String hostname, int port, PublicKey key);
/**
* It is necessary to connect with the type of algorithm that matches an existing know_host entry.
* This will allow a match when we later verify with the negotiated key {@code HostKeyVerifier.verify}
* @param hostname remote hostname
* @param port remote port
* @return existing key types or empty list if no keys known for hostname
*/
List<String> findExistingAlgorithms(String hostname, int port);
}

View File

@@ -90,10 +90,6 @@ public class OpenSSHKnownHosts
}
}
private String adjustHostname(final String hostname, final int port) {
String lowerHN = hostname.toLowerCase();
return (port != 22) ? "[" + lowerHN + "]:" + port : lowerHN;
}
public File getFile() {
return khFile;
@@ -107,7 +103,7 @@ public class OpenSSHKnownHosts
return false;
}
final String adjustedHostname = adjustHostname(hostname, port);
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
boolean foundApplicableHostEntry = false;
for (KnownHostEntry e : entries) {
@@ -131,34 +127,6 @@ public class OpenSSHKnownHosts
return hostKeyUnverifiableAction(adjustedHostname, key);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
final String adjustedHostname = adjustHostname(hostname, port);
List<String> knownHostAlgorithms = new ArrayList<String>();
for (KnownHostEntry e : entries) {
try {
if (e.appliesTo(adjustedHostname)) {
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) {
}
}
return knownHostAlgorithms;
}
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
return false;
}

View File

@@ -16,8 +16,6 @@
package net.schmizz.sshj.transport.verification;
import java.security.PublicKey;
import java.util.Collections;
import java.util.List;
public final class PromiscuousVerifier
implements HostKeyVerifier {
@@ -27,9 +25,4 @@ public final class PromiscuousVerifier
return true;
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
}

View File

@@ -27,12 +27,7 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,6 +53,8 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected char[] passphrase; // for blanking out
protected KeyPairConverter<PrivateKeyInfo> privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter();
protected KeyPair readKeyPair()
@@ -77,19 +74,22 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
if (o instanceof PEMEncryptedKeyPair) {
final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o;
final PEMKeyPair pemKeyPair = readEncryptedKeyPair(encryptedKeyPair);
kp = pemConverter.getKeyPair(pemKeyPair);
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
if (SecurityUtils.getSecurityProvider() != null) {
decryptorBuilder.setProvider(SecurityUtils.getSecurityProvider());
}
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
kp = pemConverter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorBuilder.build(passphrase)));
} finally {
PasswordUtils.blankOut(passphrase);
}
} else if (o instanceof PEMKeyPair) {
kp = pemConverter.getKeyPair((PEMKeyPair) o);
} else if (o instanceof PrivateKeyInfo) {
final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) o;
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
kp = pemConverter.getKeyPair(pemKeyPair);
} else if (o instanceof PKCS8EncryptedPrivateKeyInfo) {
final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) o;
final PrivateKeyInfo privateKeyInfo = readEncryptedPrivateKeyInfo(encryptedPrivateKeyInfo);
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
kp = pemConverter.getKeyPair(pemKeyPair);
} else {
log.warn("Unexpected PKCS8 PEM Object [{}]", o);
}
@@ -114,37 +114,4 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
public String toString() {
return "PKCS8KeyFile{resource=" + resource + "}";
}
private PEMKeyPair readEncryptedKeyPair(final PEMEncryptedKeyPair encryptedKeyPair) throws IOException {
final JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
if (SecurityUtils.getSecurityProvider() != null) {
builder.setProvider(SecurityUtils.getSecurityProvider());
}
char[] passphrase = null;
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
return encryptedKeyPair.decryptKeyPair(builder.build(passphrase));
} finally {
PasswordUtils.blankOut(passphrase);
}
}
private PrivateKeyInfo readEncryptedPrivateKeyInfo(final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) throws EncryptionException {
final JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder();
if (SecurityUtils.getSecurityProvider() != null) {
builder.setProvider(SecurityUtils.getSecurityProvider());
}
char[] passphrase = null;
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
final InputDecryptorProvider inputDecryptorProvider = builder.build(passphrase);
return encryptedPrivateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
} catch (final OperatorCreationException e) {
throw new EncryptionException("Loading Password for Encrypted Private Key Failed", e);
} catch (final PKCSException e) {
throw new EncryptionException("Reading Encrypted Private Key Failed", e);
} finally {
PasswordUtils.blankOut(passphrase);
}
}
}

View File

@@ -29,8 +29,6 @@ import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex;
@@ -42,15 +40,11 @@ import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* <h2>Sample PuTTY file format</h2>
*
* <pre>
* PuTTY-User-Key-File-2: ssh-rsa
* Encryption: none
@@ -76,7 +70,8 @@ import java.util.Map;
*/
public class PuTTYKeyFile extends BaseFileKeyProvider {
public static class Factory implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
@Override
public FileKeyProvider create() {
@@ -89,46 +84,38 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
}
private Integer keyFileVersion;
private byte[] privateKey;
private byte[] publicKey;
private byte[] verifyHmac; // only used by v3 keys
/**
* Key type
* Key type. Either "ssh-rsa" for RSA key, or "ssh-dss" for DSA key.
*/
@Override
public KeyType getType() throws IOException {
String headerName = String.format("PuTTY-User-Key-File-%d", this.keyFileVersion);
return KeyType.fromString(headers.get(headerName));
return KeyType.fromString(headers.get("PuTTY-User-Key-File-2"));
}
public boolean isEncrypted() throws IOException {
// Currently, the only supported encryption types are "aes256-cbc" and "none".
String encryption = headers.get("Encryption");
if ("none".equals(encryption)) {
return false;
}
if ("aes256-cbc".equals(encryption)) {
return true;
}
throw new IOException(String.format("Unsupported encryption: %s", encryption));
public boolean isEncrypted() {
// Currently the only supported encryption types are "aes256-cbc" and "none".
return "aes256-cbc".equals(headers.get("Encryption"));
}
private Map<String, String> payload = new HashMap<String, String>();
private Map<String, String> payload
= new HashMap<String, String>();
/**
* For each line that looks like "Xyz: vvv", it will be stored in this map.
*/
private final Map<String, String> headers = new HashMap<String, String>();
private final Map<String, String> headers
= new HashMap<String, String>();
protected KeyPair readKeyPair() throws IOException {
this.parseKeyPair();
final Buffer.PlainBuffer publicKeyReader = new Buffer.PlainBuffer(publicKey);
final Buffer.PlainBuffer privateKeyReader = new Buffer.PlainBuffer(privateKey);
final KeyType keyType = this.getType();
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
if (KeyType.RSA.equals(keyType)) {
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
if (KeyType.RSA.equals(this.getType())) {
// public key exponent
BigInteger e = publicKeyReader.readMPInt();
// modulus
@@ -144,13 +131,15 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(s.getMessage(), s);
}
try {
return new KeyPair(factory.generatePublic(new RSAPublicKeySpec(n, e)),
factory.generatePrivate(new RSAPrivateKeySpec(n, d)));
return new KeyPair(
factory.generatePublic(new RSAPublicKeySpec(n, e)),
factory.generatePrivate(new RSAPrivateKeySpec(n, d))
);
} catch (InvalidKeySpecException i) {
throw new IOException(i.getMessage(), i);
}
}
if (KeyType.DSA.equals(keyType)) {
if (KeyType.DSA.equals(this.getType())) {
BigInteger p = publicKeyReader.readMPInt();
BigInteger q = publicKeyReader.readMPInt();
BigInteger g = publicKeyReader.readMPInt();
@@ -166,20 +155,22 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(s.getMessage(), s);
}
try {
return new KeyPair(factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)));
return new KeyPair(
factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g))
);
} catch (InvalidKeySpecException e) {
throw new IOException(e.getMessage(), e);
}
}
if (KeyType.ED25519.equals(keyType)) {
if (KeyType.ED25519.equals(this.getType())) {
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519);
EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519);
return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec));
}
final String ecdsaCurve;
switch (keyType) {
switch (this.getType()) {
case ECDSA256:
ecdsaCurve = "P-256";
break;
@@ -196,12 +187,12 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
if (ecdsaCurve != null) {
BigInteger s = new BigInteger(1, privateKeyReader.readBytes());
X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve);
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(),
ecParams.getN());
ECNamedCurveSpec ecCurveSpec =
new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
try {
PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
return new KeyPair(keyType.readPubKeyFromBuffer(publicKeyReader), privateKey);
return new KeyPair(getType().readPubKeyFromBuffer(publicKeyReader), privateKey);
} catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e);
}
@@ -210,7 +201,6 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
protected void parseKeyPair() throws IOException {
this.keyFileVersion = null;
BufferedReader r = new BufferedReader(resource.getReader());
// Parse the text into headers and payloads
try {
@@ -221,9 +211,6 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
if (idx > 0) {
headerName = line.substring(0, idx);
headers.put(headerName, line.substring(idx + 2));
if (headerName.startsWith("PuTTY-User-Key-File-")) {
this.keyFileVersion = Integer.parseInt(headerName.substring(20));
}
} else {
String s = payload.get(headerName);
if (s == null) {
@@ -239,9 +226,6 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} finally {
r.close();
}
if (this.keyFileVersion == null) {
throw new IOException("Invalid key file format: missing \"PuTTY-User-Key-File-?\" entry");
}
// Retrieve keys from payload
publicKey = Base64.decode(payload.get("Public-Lines"));
if (this.isEncrypted()) {
@@ -252,14 +236,8 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
passphrase = "".toCharArray();
}
try {
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), passphrase);
Mac mac;
if (this.keyFileVersion <= 2) {
mac = this.prepareVerifyMacV2(passphrase);
} else {
mac = this.prepareVerifyMacV3();
}
this.verify(mac);
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase));
this.verify(new String(passphrase));
} finally {
PasswordUtils.blankOut(passphrase);
}
@@ -269,158 +247,77 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
/**
* Converts a passphrase into a key, by following the convention that PuTTY
* uses. Only PuTTY v1/v2 key files
* <p><p/>
* Converts a passphrase into a key, by following the convention that PuTTY uses.
* <p/>
* <p/>
* This is used to decrypt the private key when it's encrypted.
*/
private void initCipher(final char[] passphrase, Cipher cipher) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException {
// The field Key-Derivation has been introduced with Putty v3 key file format
// For v3 the algorithms are "Argon2i" "Argon2d" and "Argon2id"
String kdfAlgorithm = headers.get("Key-Derivation");
if (kdfAlgorithm != null) {
kdfAlgorithm = kdfAlgorithm.toLowerCase();
byte[] keyData = this.argon2(kdfAlgorithm, passphrase);
if (keyData == null) {
throw new IOException(String.format("Unsupported key derivation function: %s", kdfAlgorithm));
}
byte[] key = new byte[32];
byte[] iv = new byte[16];
byte[] tag = new byte[32]; // Hmac key
System.arraycopy(keyData, 0, key, 0, 32);
System.arraycopy(keyData, 32, iv, 0, 16);
System.arraycopy(keyData, 48, tag, 0, 32);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
new IvParameterSpec(iv));
verifyHmac = tag;
return;
}
// Key file format v1 + v2
private byte[] toKey(final String passphrase) throws IOException {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
// The encryption key is derived from the passphrase by means of a succession of
// SHA-1 hashes.
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
// The encryption key is derived from the passphrase by means of a succession of SHA-1 hashes.
// Sequence number 0
digest.update(new byte[]{0, 0, 0, 0});
digest.update(encodedPassphrase);
digest.update(passphrase.getBytes());
byte[] key1 = digest.digest();
// Sequence number 1
digest.update(new byte[]{0, 0, 0, 1});
digest.update(encodedPassphrase);
digest.update(passphrase.getBytes());
byte[] key2 = digest.digest();
Arrays.fill(encodedPassphrase, (byte) 0);
byte[] expanded = new byte[32];
System.arraycopy(key1, 0, expanded, 0, 20);
System.arraycopy(key2, 0, expanded, 20, 12);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"),
new IvParameterSpec(new byte[16])); // initial vector=0
byte[] r = new byte[32];
System.arraycopy(key1, 0, r, 0, 20);
System.arraycopy(key2, 0, r, 20, 12);
return r;
} catch (NoSuchAlgorithmException e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* Uses BouncyCastle Argon2 implementation
* Verify the MAC.
*/
private byte[] argon2(String algorithm, final char[] passphrase) throws IOException {
int type;
if ("argon2i".equals(algorithm)) {
type = Argon2Parameters.ARGON2_i;
} else if ("argon2d".equals(algorithm)) {
type = Argon2Parameters.ARGON2_d;
} else if ("argon2id".equals(algorithm)) {
type = Argon2Parameters.ARGON2_id;
} else {
return null;
}
byte[] salt = Hex.decode(headers.get("Argon2-Salt"));
int iterations = Integer.parseInt(headers.get("Argon2-Passes"));
int memory = Integer.parseInt(headers.get("Argon2-Memory"));
int parallelism = Integer.parseInt(headers.get("Argon2-Parallelism"));
Argon2Parameters a2p = new Argon2Parameters.Builder(type)
.withVersion(Argon2Parameters.ARGON2_VERSION_13)
.withIterations(iterations)
.withMemoryAsKB(memory)
.withParallelism(parallelism)
.withSalt(salt).build();
Argon2BytesGenerator generator = new Argon2BytesGenerator();
generator.init(a2p);
byte[] output = new byte[80];
int bytes = generator.generateBytes(passphrase, output);
if (bytes != output.length) {
throw new IOException("Failed to generate key via Argon2");
}
return output;
}
/**
* Verify the MAC (only required for v1/v2 keys. v3 keys are automatically
* verified as part of the decryption process.
*/
private void verify(final Mac mac) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
final DataOutputStream data = new DataOutputStream(out);
// name of algorithm
String keyType = this.getType().toString();
data.writeInt(keyType.length());
data.writeBytes(keyType);
data.writeInt(headers.get("Encryption").length());
data.writeBytes(headers.get("Encryption"));
data.writeInt(headers.get("Comment").length());
data.writeBytes(headers.get("Comment"));
data.writeInt(publicKey.length);
data.write(publicKey);
data.writeInt(privateKey.length);
data.write(privateKey);
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
final String reference = headers.get("Private-MAC");
if (!encoded.equals(reference)) {
throw new IOException("Invalid passphrase");
}
}
private Mac prepareVerifyMacV2(final char[] passphrase) throws IOException {
// The key to the MAC is itself a SHA-1 hash of (v1/v2 key only):
private void verify(final String passphrase) throws IOException {
try {
// The key to the MAC is itself a SHA-1 hash of:
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update("putty-private-key-file-mac-key".getBytes());
if (passphrase != null) {
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
digest.update(encodedPassphrase);
Arrays.fill(encodedPassphrase, (byte) 0);
digest.update(passphrase.getBytes());
}
final byte[] key = digest.digest();
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IOException(e.getMessage(), e);
}
}
private Mac prepareVerifyMacV3() throws IOException {
// for v3 keys the hMac key is included in the Argon output
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(this.verifyHmac, 0, 32, mac.getAlgorithm()));
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
final Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final DataOutputStream data = new DataOutputStream(out);
// name of algorithm
data.writeInt(this.getType().toString().length());
data.writeBytes(this.getType().toString());
data.writeInt(headers.get("Encryption").length());
data.writeBytes(headers.get("Encryption"));
data.writeInt(headers.get("Comment").length());
data.writeBytes(headers.get("Comment"));
data.writeInt(publicKey.length);
data.write(publicKey);
data.writeInt(privateKey.length);
data.write(privateKey);
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
final String reference = headers.get("Private-MAC");
if (!encoded.equals(reference)) {
throw new IOException("Invalid passphrase");
}
} catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e);
}
}
@@ -428,21 +325,17 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
/**
* Decrypt private key
*
* @param privateKey the SSH private key to be decrypted
* @param passphrase To decrypt
*/
private byte[] decrypt(final byte[] privateKey, final char[] passphrase) throws IOException {
private byte[] decrypt(final byte[] key, final String passphrase) throws IOException {
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
this.initCipher(passphrase, cipher);
return cipher.doFinal(privateKey);
final byte[] expanded = this.toKey(passphrase);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"),
new IvParameterSpec(new byte[16])); // initial vector=0
return cipher.doFinal(key);
} catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e);
}
}
public int getKeyFileVersion() {
return keyFileVersion;
}
}

View File

@@ -27,36 +27,17 @@ import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.LinkedList;
import java.util.Queue;
public abstract class KeyedAuthMethod
extends AbstractAuthMethod {
protected final KeyProvider kProv;
private Queue<KeyAlgorithm> available;
public KeyedAuthMethod(String name, KeyProvider kProv) {
super(name);
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)
throws UserAuthException {
PublicKey key;
@@ -69,16 +50,13 @@ public abstract class KeyedAuthMethod
// public key as 2 strings: [ key type | key blob ]
KeyType keyType = KeyType.fromKey(key);
try {
KeyAlgorithm ka = getPublicKeyAlgorithm(keyType);
if (ka != null) {
reqBuf.putString(ka.getKeyAlgorithm())
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
return reqBuf;
}
KeyAlgorithm ka = params.getTransport().getClientKeyAlgorithm(keyType);
reqBuf.putString(ka.getKeyAlgorithm())
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
return reqBuf;
} catch (IOException ioe) {
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType, ioe);
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType);
}
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType);
}
protected SSHPacket putSig(SSHPacket reqBuf)
@@ -93,7 +71,7 @@ public abstract class KeyedAuthMethod
final KeyType kt = KeyType.fromKey(key);
Signature signature;
try {
signature = getPublicKeyAlgorithm(kt).newSignature();
signature = params.getTransport().getClientKeyAlgorithm(kt).newSignature();
} catch (TransportException e) {
throw new UserAuthException("No KeyAlgorithm configured for key " + kt);
}

View File

@@ -15,9 +15,6 @@
*/
package net.schmizz.sshj.userauth.password;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/** Static utility method and factories */
@@ -57,17 +54,4 @@ public class PasswordUtils {
};
}
/**
* Converts a password to a UTF-8 encoded byte array
*
* @param password
* @return
*/
public static byte[] toByteArray(char[] password) {
CharBuffer charBuffer = CharBuffer.wrap(password);
final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes, 0, bytes.length);
return bytes;
}
}

View File

@@ -12,7 +12,7 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
package org.mindrot.jbcrypt;
import java.io.UnsupportedEncodingException;
import java.security.DigestException;

View File

@@ -16,7 +16,6 @@
package com.hierynomus.sshj.userauth.keyprovider
import com.hierynomus.sshj.test.SshFixture
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.userauth.keyprovider.KeyFormat
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator
@@ -40,14 +39,7 @@ class FileKeyProviderSpec extends Specification {
@Unroll
def "should have #format FileKeyProvider enabled by default"() {
given:
// `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)
SSHClient client = fixture.setupConnectedDefaultClient()
when:
client.authPublickey("jeroen", keyfile)
@@ -56,7 +48,7 @@ class FileKeyProviderSpec extends Specification {
client.isAuthenticated()
cleanup:
client?.disconnect()
client.disconnect()
where:
format | keyfile

View File

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

View File

@@ -1,139 +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.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;
}
}

View File

@@ -37,14 +37,14 @@ import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class RemotePortForwarderTest {
private static final Logger log = LoggerFactory.getLogger(RemotePortForwarderTest.class);
private static final PortRange RANGE = new PortRange(9000, 9999);
private static final String LOCALHOST = "127.0.0.1";
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress(LOCALHOST, 8080);
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress("127.0.0.1", 8080);
@Rule
public SshFixture fixture = new SshFixture();
@@ -62,63 +62,61 @@ public class RemotePortForwarderTest {
@Test
public void shouldHaveWorkingHttpServer() throws IOException {
// Just to check that we have a working http server...
assertEquals(200, httpGet( 8080));
assertThat(httpGet("127.0.0.1", 8080), equalTo(200));
}
@Test
public void shouldDynamicallyForwardPortForLocalhost() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", new SinglePort(0));
assertHttpGetSuccess(bind);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
}
@Test
public void shouldDynamicallyForwardPortForAllIPv4() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", new SinglePort(0));
assertHttpGetSuccess(bind);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
}
@Test
public void shouldDynamicallyForwardPortForAllProtocols() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", new SinglePort(0));
assertHttpGetSuccess(bind);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
}
@Test
public void shouldForwardPortForLocalhost() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", RANGE);
assertHttpGetSuccess(bind);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
}
@Test
public void shouldForwardPortForAllIPv4() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", RANGE);
assertHttpGetSuccess(bind);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
}
@Test
public void shouldForwardPortForAllProtocols() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", RANGE);
assertHttpGetSuccess(bind);
}
private void assertHttpGetSuccess(final RemotePortForwarder.Forward bind) throws IOException {
assertEquals(200, httpGet(bind.getPort()));
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
}
private RemotePortForwarder.Forward forwardPort(SSHClient sshClient, String address, PortRange portRange) throws IOException {
while (true) {
try {
return sshClient.getRemotePortForwarder().bind(
RemotePortForwarder.Forward forward = sshClient.getRemotePortForwarder().bind(
// where the server should listen
new RemotePortForwarder.Forward(address, portRange.nextPort()),
// what we do with incoming connections that are forwarded to us
new SocketForwardingConnectListener(HTTP_SERVER_SOCKET_ADDR));
return forward;
} catch (ConnectionException ce) {
if (!portRange.hasNext()) {
throw ce;
@@ -127,9 +125,9 @@ public class RemotePortForwarderTest {
}
}
private int httpGet(int port) throws IOException {
private int httpGet(String server, int port) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
String urlString = "http://" + LOCALHOST + ":" + port;
String urlString = "http://" + server + ":" + port;
log.info("Trying: GET " + urlString);
HttpResponse execute = client.execute(new HttpGet(urlString));
return execute.getStatusLine().getStatusCode();
@@ -142,7 +140,7 @@ public class RemotePortForwarderTest {
}
private static class PortRange {
private final int upper;
private int upper;
private int current;
public PortRange(int lower, int upper) {

View File

@@ -15,66 +15,54 @@
*/
package com.hierynomus.sshj.keepalive;
import com.hierynomus.sshj.test.KnownFailingTests;
import com.hierynomus.sshj.test.SlowTests;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.keepalive.KeepAlive;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.UserAuthException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class KeepAliveThreadTerminationTest {
private static final int KEEP_ALIVE_SECONDS = 1;
private static final long STOP_SLEEP = 1500;
@Rule
public SshFixture fixture = new SshFixture();
@Test
public void shouldNotStartThreadOnSetKeepAliveInterval() {
final SSHClient sshClient = setupClient();
final KeepAlive keepAlive = sshClient.getConnection().getKeepAlive();
assertTrue(keepAlive.isDaemon());
assertFalse(keepAlive.isAlive());
assertEquals(Thread.State.NEW, keepAlive.getState());
}
@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();
@Category({SlowTests.class, KnownFailingTests.class})
public void shouldCorrectlyTerminateThreadOnDisconnect() throws IOException, InterruptedException {
DefaultConfig defaultConfig = new DefaultConfig();
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
final SSHClient sshClient = fixture.setupClient(defaultConfig);
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(KEEP_ALIVE_SECONDS);
return sshClient;
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();
for (long l : threadMXBean.getAllThreadIds()) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(l);
if (threadInfo.getThreadName().equals("keep-alive") && threadInfo.getThreadState() != Thread.State.TERMINATED) {
fail("Found alive keep-alive thread in state " + threadInfo.getThreadState());
}
}
}
}

View File

@@ -17,25 +17,21 @@ package com.hierynomus.sshj.sftp;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.sftp.OpenMode;
import net.schmizz.sshj.sftp.RemoteFile;
import net.schmizz.sshj.sftp.SFTPEngine;
import net.schmizz.sshj.sftp.SFTPException;
import org.apache.sshd.common.util.io.IoUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.*;
import java.security.SecureRandom;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumSet;
import java.util.Random;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class RemoteFileTest {
@Rule
@@ -88,141 +84,4 @@ public class RemoteFileTest {
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));
}
}

View File

@@ -19,17 +19,21 @@ import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
import org.apache.sshd.common.NamedFactory;
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.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.sftp.server.SftpSubsystemFactory;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.junit.rules.ExternalResource;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.util.Collections;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -39,9 +43,9 @@ public class SshFixture extends ExternalResource {
public static final String hostkey = "hostkey.pem";
public static final String fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
private final SshServer server = defaultSshServer();
private SshServer server = defaultSshServer();
private SSHClient client = null;
private final AtomicBoolean started = new AtomicBoolean(false);
private AtomicBoolean started = new AtomicBoolean(false);
private boolean autoStart = true;
public SshFixture(boolean autoStart) {
@@ -104,23 +108,38 @@ public class SshFixture extends ExternalResource {
sshServer.setPort(randomPort());
ClassLoadableResourceKeyPairProvider fileKeyPairProvider = new ClassLoadableResourceKeyPairProvider(hostkey);
sshServer.setKeyPairProvider(fileKeyPairProvider);
sshServer.setPasswordAuthenticator((username, password, session) -> username.equals(password));
sshServer.setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
return username.equals(password);
}
});
sshServer.setGSSAuthenticator(new BogusGSSAuthenticator());
sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
sshServer.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
ScpCommandFactory commandFactory = new ScpCommandFactory();
commandFactory.setDelegateCommandFactory((session, command) -> new ProcessShellFactory(command, command.split(" ")).createShell(session));
commandFactory.setDelegateCommandFactory(new CommandFactory() {
@Override
public Command createCommand(String command) {
return new ProcessShellFactory(command.split(" ")).create();
}
});
sshServer.setCommandFactory(commandFactory);
sshServer.setShellFactory(new ProcessShellFactory("ls", "ls"));
sshServer.setShellFactory(new ProcessShellFactory("ls"));
return sshServer;
}
private int randomPort() {
try {
try (final ServerSocket s = new ServerSocket(0)) {
ServerSocket s = null;
try {
s = new ServerSocket(0);
return s.getLocalPort();
} finally {
if (s != null)
s.close();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
throw new RuntimeException(e);
}
}

View File

@@ -22,8 +22,9 @@ import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.transport.kex.DHGexSHA1;
import net.schmizz.sshj.transport.kex.DHGexSHA256;
import net.schmizz.sshj.transport.kex.ECDHNistP;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.kex.DHGEXServer;
import org.apache.sshd.server.kex.DHGServer;
@@ -37,7 +38,6 @@ import java.util.Collections;
@RunWith(Parameterized.class)
public class KeyExchangeTest extends BaseAlgorithmTest {
@SuppressWarnings("deprecation")
@Parameterized.Parameters(name = "algorithm={0}")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][]{
@@ -56,10 +56,10 @@ public class KeyExchangeTest extends BaseAlgorithmTest {
});
}
private final Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
private final KeyExchangeFactory serverFactory;
private Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
private NamedFactory<KeyExchange> serverFactory;
public KeyExchangeTest(KeyExchangeFactory serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
public KeyExchangeTest(NamedFactory<KeyExchange> serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
this.clientFactory = clientFactory;
this.serverFactory = serverFactory;
}

View File

@@ -20,8 +20,12 @@ import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
import net.schmizz.sshj.userauth.method.ChallengeResponseProvider;
import net.schmizz.sshj.userauth.password.Resource;
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.common.NamedFactory;
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.Rule;
import org.junit.Test;
@@ -39,8 +43,28 @@ public class AuthKeyboardInteractiveTest {
@Before
public void setKeyboardInteractiveAuthenticator() throws IOException {
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthKeyboardInteractiveFactory()));
fixture.getServer().setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
@Override
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();
}
@@ -51,7 +75,7 @@ public class AuthKeyboardInteractiveTest {
sshClient.auth(userAndPassword, new AuthKeyboardInteractive(new ChallengeResponseProvider() {
@Override
public List<String> getSubmethods() {
return new ArrayList<>();
return new ArrayList<String>();
}
@Override

View File

@@ -21,29 +21,56 @@ import net.schmizz.sshj.userauth.UserAuthException;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUpdateProvider;
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.PasswordChangeRequiredException;
import org.apache.sshd.server.auth.password.UserAuthPassword;
import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
import org.apache.sshd.server.session.ServerSession;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.util.Collections;
import java.util.Stack;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
public class AuthPasswordTest {
@Rule
public SshFixture fixture = new SshFixture(false);
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void setPasswordAuthenticator() throws IOException {
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthPasswordFactory()));
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
@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() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
@@ -53,12 +80,6 @@ public class AuthPasswordTest {
return password.equals(username);
}
}
@Override
public boolean handleClientPasswordChangeRequest(
ServerSession session, String username, String oldPassword, String newPassword) {
return username.equals(newPassword);
}
});
fixture.getServer().start();
}
@@ -66,7 +87,8 @@ public class AuthPasswordTest {
@Test
public void shouldNotHandlePasswordChangeIfNoPasswordUpdateProviderSet() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
assertThrows(UserAuthException.class, () -> sshClient.authPassword("jeroen", "changeme"));
expectedException.expect(UserAuthException.class);
sshClient.authPassword("jeroen", "changeme");
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
}
@@ -90,7 +112,8 @@ public class AuthPasswordTest {
@Test
public void shouldHandlePasswordChangeWithWrongPassword() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
assertThrows(UserAuthException.class, () -> sshClient.authPassword("jeroen", new PasswordFinder() {
expectedException.expect(UserAuthException.class);
sshClient.authPassword("jeroen", new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
return "changeme".toCharArray();
@@ -100,7 +123,7 @@ public class AuthPasswordTest {
public boolean shouldRetry(Resource<?> resource) {
return false;
}
}, new StaticPasswordUpdateProvider("bad")));
}, new StaticPasswordUpdateProvider("bad"));
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
}
@@ -130,7 +153,7 @@ public class AuthPasswordTest {
}
private static class StaticPasswordUpdateProvider implements PasswordUpdateProvider {
private final Stack<String> newPasswords = new Stack<>();
private Stack<String> newPasswords = new Stack<String>();
public StaticPasswordUpdateProvider(String... newPasswords) {
for (int i = newPasswords.length - 1; i >= 0; i--) {

View File

@@ -20,9 +20,6 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import static org.junit.Assert.fail;
public class LoadsOfConnects {
@@ -34,23 +31,15 @@ public class LoadsOfConnects {
@Test
public void loadsOfConnects() {
try {
fixture.start();
for (int i = 0; i < 1000; i++) {
log.info("Try " + i);
SSHClient client = fixture.setupConnectedDefaultClient();
client.authPassword("test", "test");
Session s = client.startSession();
Session.Command c = s.exec("ls");
IOUtils.readFully(c.getErrorStream());
IOUtils.readFully(c.getInputStream());
c.close();
s.close();
fixture.start();
fixture.setupConnectedDefaultClient();
fixture.stopClient();
fixture.stopServer();
}
} catch (Exception e) {
fail(e.getMessage());
} finally {
fixture.stopServer();
}
}

View File

@@ -39,12 +39,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.text.DateFormat;
import java.text.ParseException;
@@ -105,7 +103,7 @@ public class OpenSSHKeyFileTest {
assertArrayEquals(new char[passwordChars.length], passwordChars);
}
}
}
};
final PasswordFinder onlyGivesWhenReady = new PasswordFinder() {
@Override
@@ -189,7 +187,7 @@ public class OpenSSHKeyFileTest {
}
@Test
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException {
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n";
@@ -199,7 +197,7 @@ public class OpenSSHKeyFileTest {
}
@Test
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException {
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384"));
String expected = "384 MD5:ee:9b:82:d1:47:01:16:1b:27:da:f5:27:fd:b2:eb:e2";
@@ -209,7 +207,7 @@ public class OpenSSHKeyFileTest {
}
@Test
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException {
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521"));
String expected = "521 MD5:22:e2:f4:3c:61:ae:e9:85:a1:4d:d9:6c:13:aa:eb:00";
@@ -248,12 +246,6 @@ public class OpenSSHKeyFileTest {
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)
public void shouldFailOnIncorrectPassphraseAfterRetries() throws IOException {
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
@@ -282,35 +274,6 @@ public class OpenSSHKeyFileTest {
assertThat(aPrivate.getAlgorithm(), equalTo("RSA"));
}
@Test
public void shouldLoadRSAPrivateCrtKeyAsOpenSSHV1() throws IOException {
final OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
keyFile.init(new File("src/test/resources/keyformats/rsa_opensshv1"));
final PrivateKey privateKey = keyFile.getPrivate();
final PublicKey publicKey = keyFile.getPublic();
assertTrue(publicKey instanceof RSAPublicKey);
final RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
assertTrue(privateKey instanceof RSAPrivateCrtKey);
final RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) privateKey;
assertEquals("Public Key Exponent not matched", rsaPublicKey.getPublicExponent(), rsaPrivateCrtKey.getPublicExponent());
assertEquals("Public Key Modulus not matched", rsaPublicKey.getModulus(), rsaPrivateCrtKey.getModulus());
final BigInteger privateExponent = rsaPrivateCrtKey.getPrivateExponent();
final BigInteger expectedPrimeExponentP = privateExponent.mod(rsaPrivateCrtKey.getPrimeP().subtract(BigInteger.ONE));
assertEquals("Prime Exponent P not matched", expectedPrimeExponentP, rsaPrivateCrtKey.getPrimeExponentP());
final BigInteger expectedPrimeExponentQ = privateExponent.mod(rsaPrivateCrtKey.getPrimeQ().subtract(BigInteger.ONE));
assertEquals("Prime Exponent Q not matched", expectedPrimeExponentQ, rsaPrivateCrtKey.getPrimeExponentQ());
final BigInteger expectedCoefficient = rsaPrivateCrtKey.getPrimeQ().modInverse(rsaPrivateCrtKey.getPrimeP());
assertEquals("Prime CRT Coefficient not matched", expectedCoefficient, rsaPrivateCrtKey.getCrtCoefficient());
}
@Test
public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException {
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
@@ -444,14 +407,6 @@ public class OpenSSHKeyFileTest {
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
public void checkBCRegistration() {
if (!SecurityUtils.isBouncyCastleRegistered()) {

View File

@@ -15,18 +15,13 @@
*/
package net.schmizz.sshj.keyprovider;
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import net.schmizz.sshj.util.KeyUtil;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.File;
import java.io.IOException;
@@ -40,9 +35,6 @@ import static org.junit.Assert.assertEquals;
public class PKCS8KeyFileTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
static final FileKeyProvider rsa = new PKCS8KeyFile();
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
@@ -78,25 +70,6 @@ public class PKCS8KeyFileTest {
assertEquals("RSA", provider.getPrivate().getAlgorithm());
}
@Test
public void testPkcs8RsaEncrypted() throws IOException {
final PKCS8KeyFile provider = new PKCS8KeyFile();
final PasswordFinder passwordFinder = PasswordUtils.createOneOff("passphrase".toCharArray());
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
assertEquals("RSA", provider.getPublic().getAlgorithm());
assertEquals("RSA", provider.getPrivate().getAlgorithm());
}
@Test
public void testPkcs8RsaEncryptedIncorrectPassword() throws IOException {
expectedException.expect(KeyDecryptionFailedException.class);
final PKCS8KeyFile provider = new PKCS8KeyFile();
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(String.class.getSimpleName().toCharArray());
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
provider.getPrivate();
}
@Test
public void testPkcs8Ecdsa() throws IOException {
final PKCS8KeyFile provider = new PKCS8KeyFile();

View File

@@ -18,14 +18,13 @@ package net.schmizz.sshj.keyprovider;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import net.schmizz.sshj.util.UnitTestPasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.Resource;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -209,25 +208,6 @@ public class PuTTYKeyFileTest {
"oYhmT2+0DKBuBVCAM4qRdA==\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" +
"Encryption: aes256-cbc\n" +
"Comment: dsa-key-20140507\n" +
@@ -246,113 +226,6 @@ public class PuTTYKeyFileTest {
"nLEIBzB8WEaMMgDz5qwlqq7eBxLPIIi9uHyjMf7NOsQ=\n" +
"Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\n";
final static String v3_ecdsa = "PuTTY-User-Key-File-3: ecdsa-sha2-nistp256\n" +
"Encryption: none\n" +
"Comment: ecdsa-key-20210819\n" +
"Public-Lines: 3\n" +
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP5mbdlgVmkw\n" +
"LzDkznoY8TXKnok/mlMkpk8FELFNSECnXNdtZ4B8+Bpqnvchhk/jY/0tUU98lFxt\n" +
"JR0o0l8B5y0=\n" +
"Private-Lines: 1\n" +
"AAAAIEblmwyKaGuvc6dLgNeHsc1BuZeQORTSxBF5SBLNyjYc\n" +
"Private-MAC: e1aed15a209f48fdaa5228640f1109a7740340764a96f97ec6023da7f92d07ea";
final static String v3_rsa_argon2id = "PuTTY-User-Key-File-3: ssh-rsa\n" +
"Encryption: aes256-cbc\n" +
"Comment: rsa-key-20210926\n" +
"Public-Lines: 6\n" +
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03\n" +
"/GOHivW0SgGIQbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBf\n" +
"PUagMvaBxKXC8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuX\n" +
"polMDSu6vmkkuKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYag\n" +
"EmO9jmQoytyqoNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7Znw\n" +
"RGW6CtoGYulo0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJ\n" +
"Key-Derivation: Argon2id\n" +
"Argon2-Memory: 8192\n" +
"Argon2-Passes: 21\n" +
"Argon2-Parallelism: 1\n" +
"Argon2-Salt: baf1530601433715467614d044c0e4a5\n" +
"Private-Lines: 14\n" +
"QAJl3mq/QJc8/of4xWbgBuE09GdgIuVhRYGAV5yC5C0dpuiJ+yF/6h7mk36s5E3Q\n" +
"k32l+ZoWHG/kBc8s6N9rTQnIgC/eieNlN5FK3OSSoI9PBvoAtNEVWsR2T4U6ZkAG\n" +
"FbyF3vRWq2h9Ux8flZusySqafQ2AhXP79pr13wvMziv1QbPkPFHWaR1Uvq9w0GJq\n" +
"rfR+M6t8/6aPKhnsCTy8MiAoIcjeZmHiG/vOMIBBoWI7KtpD5IrbO4pIgzRK8m9Z\n" +
"JqQvgWCPnddwCeiDFOZwf/Bm6g+duQYId4upB1IxSs34j21a7ZkMSExDZyV0d13S\n" +
"G59U9pReZ7mHyIjORqeY7ssr/L9aJPPa7YCu4J5a/Bn/ARf/X5XmMnueFZ6H806M\n" +
"ZUtHzeG2sZGoHULpwEaY1zRQs1JD5UAeaFzgDpzD4oeaD8v+FS3RdNlgj2gtWNcl\n" +
"h8nvWD60XbylR0BdbB553xGuC8HC0482xQCCJUc8SMHZ/k2+FKTaf2m2p4dLyKkk\n" +
"Qrw43QcmkgypUPRHKvnVs+6qUYMDHkwtPR1ZGFqHQzlHozvO9NdY/ZXTln/qfPZA\n" +
"5w5TKvy0/GvofhISJCMocnPbkqGR6fDcKbpUjAS/RDgsCKKS5hxf6nhsYUgrXA4G\n" +
"hXIgqGnMefLemjRG7dD/3XE8NmF6Q8mjIideEOBeP4tRCaDC2n90rZ3yChP9bsel\n" +
"yg/TeKxj7OLk+X3ocP3yw2lsp3zOPsptSNtGI7g9VaIPGtxGaqRaIuObdLbBxCeR\n" +
"ZgKSIuWtz8W1kT0aWuZ0aXMPagGao0ZsffmroyVpGbzW3QaI9633Krmf7EyphZoy\n" +
"6tV3Z/GJ5aQJFeMYPOq69ktXRLAWr800822NwEStcxtQHTWbaTk7dxh8+0xwlCgI\n" +
"Private-MAC: 582dea09758afd93a8e248abce358287d384e5ee36d21515ffcc0d42d8c5d86a\n";
final static String v3_rsa_argon2d = "PuTTY-User-Key-File-3: ssh-rsa\n" +
"Encryption: aes256-cbc\n" +
"Comment: rsa-key-20210926\n" +
"Public-Lines: 6\n" +
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03\n" +
"/GOHivW0SgGIQbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBf\n" +
"PUagMvaBxKXC8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuX\n" +
"polMDSu6vmkkuKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYag\n" +
"EmO9jmQoytyqoNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7Znw\n" +
"RGW6CtoGYulo0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJ\n" +
"Key-Derivation: Argon2d\n" +
"Argon2-Memory: 2048\n" +
"Argon2-Passes: 5\n" +
"Argon2-Parallelism: 3\n" +
"Argon2-Salt: 5fa1eb89e9eac0cc562c59bc648cacea\n" +
"Private-Lines: 14\n" +
"CCLbHvtNdkMNqOrSM+CNF874xTjDs01+UanZX7pHmIA94nAbb9ofeEHPcw7pCCWq\n" +
"mxj7GK8BnsEQXIS1yCnRT6yCi1d68FpdXN2QvhlbWpEuzrmw4q71XpCwYpZ3KERo\n" +
"+/o7X2Pi4qhfaS+fgBAl2VwAiHdN2KQFewj6MWJqP2/GaegKyvnZue/3e+v/Edag\n" +
"DCbODfNhfirISUlw5U3SxqmIdrFT+2DKbpVTCLQwTeXL+fmzdYYvjOGQq6Kx7E9L\n" +
"iWf1aLoBZCWfN5gpMxD1F1tX1nBXmFMG8aW+lOr3+BxLMUAtjRQVsmc6Lyqb1RdV\n" +
"eyZp1W0R2+HKmwm59WUQK46HMnXdkwUqArb28VpBE61gj+KMWna9TJP6aJTF2N6m\n" +
"0Wv8D9WCGOrOC+IqnkfkfdSLkupu6PyyhiS69IR9b6vAyDYFxhtlEx6qZpjSKLYr\n" +
"X11I223yPAmSoO1X24RNPpo1uU4k8NfZWH0ZICY3YZ0K3PnETNGd5C38OSptQFor\n" +
"9aY1oV/1VencX/CmGXaQHsV5UJ/SnV78+PPSv3pEeQmd2ljmSx3kTL1BX91n4/Mc\n" +
"jNxE3kMXJ+6DD6OGGU0VbVmYBCrFDD4Mfj8yyLKOjJgEZubCLaZoI7WhDk4qZcui\n" +
"hzPt1tshrjIN6VKubqg84BVOWmJ3MmDD76ci9d5ILeAm4zzsliuagSLa+Y6t03hs\n" +
"PmRnFSiCv1zrqLl20PcrPEsifGeC/o1839/9E0Gywy/JDjlbucxfU9qHOntnqQJM\n" +
"8cAjXyuzgkKC5yzk/Py3VnjWegENrfM5Zf/eXFYFzD0cIA0ou2ap+Dcln14ckGFZ\n" +
"kir9AVgxyOiQikD8za+QjZ2rLeuzODe9mKPPKitI4npanpGcWRl+RPCG4t9poacO\n" +
"Private-MAC: d08aebc419131c109bbf8c200848f47eafedab9286b372c3155e8dc27e6b84cd\n";
final static String v3_rsa_argon2i = "PuTTY-User-Key-File-3: ssh-rsa\n" +
"Encryption: aes256-cbc\n" +
"Comment: rsa-key-20210926\n" +
"Public-Lines: 6\n" +
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03\n" +
"/GOHivW0SgGIQbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBf\n" +
"PUagMvaBxKXC8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuX\n" +
"polMDSu6vmkkuKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYag\n" +
"EmO9jmQoytyqoNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7Znw\n" +
"RGW6CtoGYulo0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJ\n" +
"Key-Derivation: Argon2i\n" +
"Argon2-Memory: 1024\n" +
"Argon2-Passes: 5\n" +
"Argon2-Parallelism: 2\n" +
"Argon2-Salt: 2845c351de77c5aa9604e407ca830136\n" +
"Private-Lines: 14\n" +
"Ws3CZMJ0xYa/W6s0YZqn4j8ihXK81lw88iuXrmzWu+L88+RVGTBGvOvmE0oqLsMw\n" +
"YPIi7/eOaik1jZ+dnnD/PeJbVOqch7z2fSK1cVXMyNggPvFBQVjtxrFRhvGtIC0R\n" +
"5py8Z7Cfi0W9N/xyjHIvNrGwuvUQpBKeK1C/zYweQJF/clBSovnV/sGGRbtEd+jk\n" +
"rY8svRKSvX0HY0P4xftwH+E40XZhUdG2JetleCNIw0ohShuCSiO1fauxI3Az/i2J\n" +
"Ef3pRfMLCE91QW4/3nY2ofK6yyufNhyFSjqIaDkQUNBi4EYG1W2/29mK9zLpfa+Z\n" +
"eiujzOZJfI8QPar7gTp2sdrq7ND2YUniatwqpq9+vefTkWvMEhwuNAGvfRWgJ2qq\n" +
"IbB/EWtvNj8vA3z3M2j0ksMRvJSGpU1n8MKVdWe5PSjvpMiCaqtOTtrP3iqS+bwJ\n" +
"WjhV+JVod5RE0fCXnBcCkE8XdSu20m04aRIVHJvnIaKH7vZXThDdG9AhpSrUcvWM\n" +
"OVD5q0L9W9wcVQzN7XtQhTEjm3zja+tOo0gYn0Z/497kkxdL/g/su5kpPQsbbsLF\n" +
"0ROS5U2GZX0Le+QVg6hGIfqskBoCQp+ErTXFzIu+0//MoaZSACtW48ljeIpDj0fG\n" +
"v2Fhc9tbpTJKvQh6wlm9gkMBSV+XcRWUMh5zBPecmR6/v9O4/MCsOse89MNs4LxL\n" +
"sLRUABdjziKnjomq/1FlozlGGfF+v+VLhjjc1xq5ms+BEqkXUsWoJl8NNST6NqkN\n" +
"2T4nFzZA6b+RwFJFqYHF+BvgkQ5j0hEbXo1qlqKIf3Vk+/rouPkLyUIiHxZxdX4m\n" +
"P/LtnH79FPDQFbFl6826Ui1TPISAf3pTwKFI43HgKRrya3F5GPeQphsZHlu155JO\n" +
"Private-MAC: 1be8357d497fd4d641ce50a142c5a91ef3b0279355d2996e0c1f13e376394301\n";
@Test
public void test2048() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
@@ -400,8 +273,17 @@ public class PuTTYKeyFileTest {
// -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \
// --new-passphrase <(echo 123456)
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"),
new UnitTestPasswordFinder("123456"));
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"), new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
return "123456".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
@@ -459,72 +341,21 @@ public class PuTTYKeyFileTest {
assertEquals(key.getPublic(), referenceKey.getPublic());
}
@Test
public void testV3KeyArgon2id() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(v3_ecdsa));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
}
@Test
public void testRSAv3EncryptedKeyArgon2id() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(v3_rsa_argon2id), new UnitTestPasswordFinder("changeit"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
}
@Test
public void testRSAv3EncryptedKeyArgon2d() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(v3_rsa_argon2d), new UnitTestPasswordFinder("changeit"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
}
@Test
public void testRSAv3EncryptedKeyArgon2i() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(v3_rsa_argon2i), new UnitTestPasswordFinder("changeit"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
}
@Test
public void testCorrectPassphraseRsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppk1024_passphrase), new UnitTestPasswordFinder("123456"));
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
}
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
// correct passphrase
return "123456".toCharArray();
}
@Test
public void testCorrectPassphraseUmlautRsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppk1024_umlaut_passphrase), new UnitTestPasswordFinder("äöü"));
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
@@ -533,8 +364,18 @@ public class PuTTYKeyFileTest {
@Test(expected = IOException.class)
public void testWrongPassphraseRsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppk1024_passphrase),
new UnitTestPasswordFinder("egfsdgdfgsdfsdfasfs523534dgdsgdfa"));
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
// wrong passphrase
return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPublic());
assertNull(key.getPrivate());
}
@@ -542,7 +383,18 @@ public class PuTTYKeyFileTest {
@Test
public void testCorrectPassphraseDsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppkdsa_passphrase), new UnitTestPasswordFinder("secret"));
key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
// correct passphrase
return "secret".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
@@ -551,8 +403,18 @@ public class PuTTYKeyFileTest {
@Test(expected = IOException.class)
public void testWrongPassphraseDsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppkdsa_passphrase),
new UnitTestPasswordFinder("egfsdgdfgsdfsdfasfs523534dgdsgdfa"));
key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
// wrong passphrase
return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPublic());
assertNull(key.getPrivate());
}

View File

@@ -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 net.schmizz.sshj.util;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.Resource;
public class UnitTestPasswordFinder implements PasswordFinder {
private final char[] password;
public UnitTestPasswordFinder(String password) {
this.password = password.toCharArray();
}
public UnitTestPasswordFinder(char[] password) {
this.password = password;
}
@Override
public char[] reqPassword(Resource<?> resource) {
return password;
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
}

View File

@@ -12,23 +12,19 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
package org.mindrot.jbcrypt;
import org.junit.Test;
import junit.framework.TestCase;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* JUnit unit tests for BCrypt routines
* @author Damien Miller
* @version 0.2
*/
public class BCryptTest {
String[][] test_vectors = {
public class TestBCrypt extends TestCase {
String test_vectors[][] = {
{ "",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
@@ -91,10 +87,17 @@ public class BCryptTest {
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
};
/**
* Entry point for unit tests
* @param args unused
*/
public static void main(String[] args) {
junit.textui.TestRunner.run(TestBCrypt.class);
}
/**
* Test method for 'BCrypt.hashpw(String, String)'
*/
@Test
public void testHashpw() {
System.out.print("BCrypt.hashpw(): ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -105,16 +108,16 @@ public class BCryptTest {
assertEquals(hashed, expected);
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.gensalt(int)'
*/
@Test
public void testGensaltInt() {
System.out.print("BCrypt.gensalt(log_rounds):");
for (int i = 4; i <= 12; i++) {
System.out.print(" " + i + ":");
System.out.print(" " + Integer.toString(i) + ":");
for (int j = 0; j < test_vectors.length; j += 4) {
String plain = test_vectors[j][0];
String salt = BCrypt.gensalt(i);
@@ -124,12 +127,12 @@ public class BCryptTest {
System.out.print(".");
}
}
System.out.println("");
}
/**
* Test method for 'BCrypt.gensalt()'
*/
@Test
public void testGensalt() {
System.out.print("BCrypt.gensalt(): ");
for (int i = 0; i < test_vectors.length; i += 4) {
@@ -140,13 +143,13 @@ public class BCryptTest {
assertEquals(hashed1, hashed2);
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting success
*/
@Test
public void testCheckpw_success() {
System.out.print("BCrypt.checkpw w/ good passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -155,13 +158,13 @@ public class BCryptTest {
assertTrue(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting failure
*/
@Test
public void testCheckpw_failure() {
System.out.print("BCrypt.checkpw w/ bad passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -171,12 +174,12 @@ public class BCryptTest {
assertFalse(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
System.out.println("");
}
/**
* Test for correct hashing of non-US-ASCII passwords
*/
@Test
public void testInternationalChars() {
System.out.print("BCrypt.hashpw w/ international chars: ");
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
@@ -189,6 +192,7 @@ public class BCryptTest {
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
assertFalse(BCrypt.checkpw(pw1, h2));
System.out.print(".");
System.out.println("");
}
private static class BCryptHashTV {
@@ -238,8 +242,7 @@ public class BCryptTest {
}),
};
@Test
public void testBCryptHashTestVectors() {
public void testBCryptHashTestVectors() throws Exception {
System.out.print("BCrypt.hash w/ known vectors: ");
for (BCryptHashTV tv : bcrypt_hash_test_vectors) {
byte[] output = new byte[tv.out.length];
@@ -247,6 +250,7 @@ public class BCryptTest {
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
private static class BCryptPbkdfTV {
@@ -277,8 +281,7 @@ public class BCryptTest {
(byte) 0x83, (byte) 0x3c, (byte) 0xf0, (byte) 0xdc, (byte) 0xf5, (byte) 0x6d, (byte) 0xb6, (byte) 0x56, (byte) 0x08, (byte) 0xe8, (byte) 0xf0, (byte) 0xdc, (byte) 0x0c, (byte) 0xe8, (byte) 0x82, (byte) 0xbd}),
};
@Test
public void testBCryptPbkdfTestVectors() {
public void testBCryptPbkdfTestVectors() throws Exception {
System.out.print("BCrypt.pbkdf w/ known vectors: ");
for (BCryptPbkdfTV tv : bcrypt_pbkdf_test_vectors) {
byte[] output = new byte[tv.out.length];
@@ -286,5 +289,6 @@ public class BCryptTest {
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
}

View File

@@ -1,30 +0,0 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIizi0oXD8HM0CAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBAvGR0fYj/JgxVzxslaeVRtBIIE
0IiXAnJ0YtAHia4QOziIghhJ+qdJXLvAssQ5gK6wPqMUWV8Zut+/7mb0mObI5qyb
afv4GmS5TyI5c+gYHzXGt++Lqp9JSdisdLEPsCFHYtRd6ujC1D2EXaFOpTRBFFuw
QCQa+qQFqEcDu31IFo7Obj86V2NIE8O8zWJvtTih5EloEaJmZK+lVjsXWRWRRFqh
ZNU7PanlADb6N+xXyn2VnQbiFouOCTvmCN8bzvpCg8wJMyfMJU4gJbJE2cVD0nYI
sz63ZO1I1ljhx7FKsTlnza6PsGbL3StN8eTlNUEL6SGiMllPXdKXXte5VqIJzoJ5
a8OAC9Gmou6YRSttCGSaUCvKGCl0iEAe86vv1PiM873DNuer/IgUGXGBSk8uG5qm
7PTYs8kzvovcuMHUg1O2t5aKCCVah3o2jfY+koeQtBq01kP5jQ0VB8w0trKsT614
BJhN7Es9AkNya/qvDoysmebc0NJQt8rDLkvumn2jlWCDVM56Xl4P/qknllGilSyf
wWClZzsd5Q8MflbjqCwiYp19ZK4IHW7Y6hxdBZWWFd5oWw47GwWPTLdgnsGtp0dW
TK3IamTLO5T2BK2NdctXZ9CCn8ReuRVBA2jTNp6PWrcVxhHD4uUr1FE++e2Q+cQM
iljqis/md4lmpb74lhCNbhxqiFoVSe3XzvVtNxVXNYqxb9PqRKxWXWtyYk7/jo2u
Q284skAMcPvuGkl9Ba5RTup7t4V/eQfPTHeY/rnKyTd+hlUb+Tc26EOIKwSCxSZy
q36h4JHVjM3BRQzNMpH/GyuW1+qUw9SFOcuqtTwOxDe5rUis0sFXQyyXc/3IZcYZ
HSQzdmzFXNXysVRox5+0AfuEAOj8bc4bn0Sr7UQrYKCtJHOnl01mT51pdRCZJV/j
fRYIbxJG0yqCxPoEh0sh3fwuiNkkHHTsaDZ8aXyXL1K0C+OzXlYkbc8i8iaW7UOc
ymPD7BdmLLSADH2nb3M+QORF5IJtQ+8j11B+App2V7ao35azGScCda9rDMZCPl/6
4OwPtRWEOlUzZVq3Uid61w3Expc4zaZ4gyXG9q/UU4/TabPt4fVv6dMTb5tpOinG
CgBqBK5ihDOFUJxrFZ5OMy9SjJvecDslRrSxykPCHMx93iGfZlPmYfLfMa/AW3nr
uXVTtd6Pk4P7z4/LdsJqqlHVKqkaZI5nrCrUMQwSjhAOBhEkipyQWj+3OYgfheIi
FbNP0hdSvez5JCWin/aoc3Xe8oKM0i2liM7VcNKbKwPNULTmj7g08LUxjwT8pzPj
z1kjlKEJgEF/d+OQ6nhU2moKvIPyxzi/+FBfVG2H8Tm+57RlwRSsv2pe7XAn+0jW
xLSQFHgvZj3kcN9kT4o8A812kWgn3HV3ve2s/1sVKPvLrU8AjAHWW0pJ8fCGqAF+
Q9cWPwtd2D3KJemdUUXPe2Vd/WbtfRmWzPtsVFdA4BRODgOIQNGNnjv6mkezpHrM
ixXKvSqDf1GuYNzLTNo91EmaAGgSA0T1ZMEesPXLhxxBCdw8Dyq90Uzp3Vzbiia6
Sy5UKaoU73xI3HfPo5d77n5vdy9UoJ5Oje6oq4DXB3WstDK/WBeaju5eCezVBhSA
ksBiPIHfhkDdnTOzrCATAQBRO92VQV4b3yEXic3Q6FIH
-----END ENCRYPTED PRIVATE KEY-----

View File

@@ -1,3 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfrL8SxDyrkNlsJdAmc7Z0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBiAAAAkLVqaDIfs+sPBNyy7ytdLnP/xH7Nt5FIXx3Upw6wKuMGdBzbFQLcvu60Le+SFP3uUfXE8TcHramXbH0n+UBMW6raCAKOkHUU1BtrKxPG1eKU/LBx3Bk5FxyKm7fo0XsCUmqSVK25EHOJfYq1QwIbWICkvQUNu+2Hg8/MQKoFJMentI+GqjdaG76f6Wf+aj9UwA==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBi sshjtest@TranceLove

View File

@@ -1,30 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz
c2gtcnNhAAAAAwEAAQAAAQEAgY1kBzKSgEJ1N72WRf4h5+EQPOuzsflNN/xjh4r1
tEoBiEG4SnEklr082DhPhkHbV/U9iica++s/5QeJ4IZHFpKS6EFvdYDQXz1GoDL2
gcSlwvHDamjJvfbsOSqUYPEqUyVscGD5ULc5ssWr9D0RXjjErIkbJ4zbl6aJTA0r
ur5pJLiqz874pz2W7F6GXjMm3JtYJ9mavu4pOLrNInGhsUSUbhfp1amGoBJjvY5k
KMrcqqDVs7z2BIf3YfOYREsLSCl4mjDo0IOdnVuQbhCzxle2cWikSu2Z8ERlugra
BmLpaNJic+SGbJQ60SvxCtm0hg265QHGGmo0TgemgQ8miQAAA9DskMes7JDHrAAA
AAdzc2gtcnNhAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03/GOHivW0SgGI
QbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBfPUagMvaBxKXC
8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuXpolMDSu6vmkk
uKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYagEmO9jmQoytyq
oNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7ZnwRGW6CtoGYulo
0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJAAAAAwEAAQAAAQBJt9bvcYuD
iE2DBlJ4SX+pnpvKzqRV5XJXJTrNafkeOe5dRmhDk9YqIEx7DK/Tya2yg04dSttD
9j1Jady+8imJYqZNms59omrvhsKlbdpvRSK9pyx3ZGFHwzXv4ZbFAvX+khD+cW/s
yhX+8BREymsTnmHre6kD/FcIGC+QIv57J/snTHLIWRYMvHssgEBAvtjyTmXXurzr
9g92uAp/RwpIIiWB695lQbWWv9jwTFl9MinFu0ckRjp8djwvoK54vyWIvzxAOH7X
D8t3qmEAuekrf4uo6np3CqkHOU/5SAEs+o0KZ729/pS4fs8vWLi8qRXmRxbPrcvq
+uTTEjeQw7gBAAAAgQCtka9gYjdGNVz61o5wQWzHyMsszUZBlZwrXEbOnLYuz9A0
dwgb9yRuZFbDpGja5eLMjECua+ZvXu4XLk16SOr/y70Q4WRpv9I/Pk/QTDWepcs9
jmjp4IikYwBNBPZQPhnbLJ/9wrALOy0dFOuG76j8/jhoxDCGnxKbxNursWaxUQAA
AIEA/zvWbAkmleL4Xc70PtlsUGYkIitT8zb1FYj2L/neW63iSdn+9dPjJ8jCyIhK
R11jwlyKoyS765cZtZJCfSf5RkoF7p7I7m3dSXZ2+3ccS9Kv1VvE+mBIxLpySXPh
C9rmILzDHXU4VfhQ08Cp4Fd87wZC0Z1PlOeWWS/k9Vqcn0EAAACBAIHw9Z3xfqG3
tEuWima0iLbEZhNRjvxqbyCBLG2MoA8EwQN1lMNzpYKP4VxTdkuS8xF2xhDzr8xT
Ap0Lb8Q/gr2bZB2a8AsxeuYSlon1svVUDy+IGeD9JLx79lyevwAjcZ8lx6fOyC3E
/vMXJ14gHWfzamKvRGLy6UZ4A+t6431JAAAAEHJzYS1rZXktMjAyMTA5MjYBAgME
BQYHCAkK
-----END OPENSSH PRIVATE KEY-----