Compare commits

..

19 Commits

Author SHA1 Message Date
Jeroen van Erp
3256f5336d Update builds and release pipeline 2021-10-12 11:16:51 +02:00
Jeroen van Erp
ad87db9196 Update release notes 2021-10-12 10:13:24 +02:00
Jeroen van Erp
781f2dc632 Update vscode config
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-10-12 09:58:34 +02:00
Jan S
b2115dea6f full support for encrypted PuTTY v3 files (#730)
* full support for encrypted PuTTY v3 files (Argon2 library not included)

* simplified the PuTTYKeyDerivation interface and provided an abstract PuTTYArgon2 class for an easy Argon2 integration

* use Argon2 implementation from Bouncy Castle

* missing license header added

* license header again

* unit tests extended to cover all Argon2 variants and non-standard Argon2 parameters; verify the loaded keys
2021-10-12 09:47:11 +02:00
Jan S
d6d6f0dd33 only process supported Putty v3 keys + minor optimizations (#729) 2021-10-02 18:03:21 +02:00
Jeroen van Erp
93de1ecf47 Add license header 2021-09-29 09:27:29 +02:00
Jeroen van Erp
46ca5375d0 Remove long deprecated code 2021-09-28 21:56:52 +02:00
Jeroen van Erp
771ac0e346 Remove duplicated code
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-28 21:50:01 +02:00
Jeroen van Erp
eb09a16aef Send EOF on channel Close (Fixes #143, #496, #553, #554)
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-27 22:58:12 +02:00
Luca Milanesio
53d241e4e3 Enable renaming with flags (#652)
* Enable renaming with flags

The SFTP protocol allows to rename files by specifying
extra flags:

- OVERWRITE
- ATOMIC
- NATIVE

The flags are exposed through a new RenameFlags enum and
can be passed as parameters to the rename() method in
SFTPClient/SFTPEngine.

Relates to #563

* Update RenameFlags.java

* Update RenameFlags.java

* Align license header with all other files

* Make RenameFlags parameter in line with OpenMode(s)

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-27 13:33:16 +02:00
exceptionfactory
03dd1aaf49 Update OpenSSH Key V1 parsing using CRT information for RSA Private Keys (#726)
* Update OpenSSH Key V1 parsing using CRT information for RSA Private Keys

* Remove unndeeded BC call.

Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-27 12:53:16 +02:00
Jeroen van Erp
7742d9b661 Upgrade to asn-one 0.6.0 (Fixes #678) 2021-09-27 12:11:05 +02:00
Bernie
14bf93e677 Prefer known algorithm for known host (#721)
* Prefer known algorithm for known host

(#642, #635... 10? issues)

Try to find the Algorithm that was used when a known_host
entry was created and make that the first choice for the
current connection attempt.

If the current connection algorithm matches the
algorithm used when the known_host entry was created
we can get a fair verification.

* Add support for multiple matching hostkeys, in configuration order

Co-authored-by: Bernie Day <bday@jvncomm.com>
Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-23 13:09:14 +02:00
exceptionfactory
753e3a50e5 Upgraded SLF4J to 1.7.32 and Logback to 1.2.6 (#722) 2021-09-23 08:22:18 +02:00
Bernd Schuller
2e1ef9dbcd Support v3 PuTTY keys (#716)
* Support v3 PuTTY keys

* add test for putty v3 key

* Format PuTTYKeyFile to fix Codacy warnings

Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-20 12:20:30 +02:00
exceptionfactory
6f9873712f Move BCrypt class to avoid conflict with org.mindrot:jbcrypt (#717)
- Renamed BCryptTest and updated using JUnit Test annotations
2021-09-02 09:50:35 +02:00
exceptionfactory
8e8e04ff1f Updated Build and Code Quality badges (#714) 2021-08-28 15:48:20 +02:00
exceptionfactory
b47e6fa012 Add PKCS8 parsing for encrypted PEM ASN.1 Private Keys (#713)
- Added unit tests for encrypted PKCS8 RSA Private Key
2021-08-27 23:37:37 +02:00
Jeroen van Erp
f38fcbe57e Add Automatic-Module-Name to MANIFEST.MF (#712) 2021-08-27 16:29:51 +02:00
36 changed files with 1040 additions and 459 deletions

View File

@@ -24,19 +24,6 @@ jobs:
run: chmod +x gradlew run: chmod +x gradlew
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew check run: ./gradlew check
# 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: integration:
name: Integration test name: Integration test

49
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
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,5 +2,6 @@
"java.checkstyle.configuration": "${workspaceFolder}/gradle/config/checkstyle/checkstyle.xml", "java.checkstyle.configuration": "${workspaceFolder}/gradle/config/checkstyle/checkstyle.xml",
"files.watcherExclude": { "files.watcherExclude": {
"**/target": true "**/target": true
} },
"java.configuration.updateBuildConfiguration": "automatic"
} }

View File

@@ -4,9 +4,8 @@ Jeroen van Erp
:sshj_version: 0.31.0 :sshj_version: 0.31.0
:source-highlighter: pygments :source-highlighter: pygments
image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"] image:https://github.com/hierynomus/sshj/actions/workflows/gradle.yml/badge.svg[link="https://github.com/hierynomus/sshj/actions/workflows/gradle.yml"]
image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"] image:https://app.codacy.com/project/badge/Grade/2c8a5a67c6a54ed89c9a699fd6b27305["Codacy Grade", link="https://app.codacy.com/gh/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: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: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"] 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"]
@@ -105,6 +104,19 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
Fork away! Fork away!
== Release history == Release history
SSHJ 0.32.0 (2021-10-12)::
* Send EOF on channel close (Fixes https://github.com/hierynomus/sshj/issue/143[#143], https://github.com/hierynomus/sshj/issue/496[#496], https://github.com/hierynomus/sshj/issue/553[#553], https://github.com/hierynomus/sshj/issue/554[#554])
* 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):: SSHJ 0.31.0 (2021-02-08)::
* Bump dependencies (asn-one 0.5.0, BouncyCastle 1.68, slf4j-api 1.7.30) * 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 * Merged https://github.com/hierynomus/sshj/pull/660[#660]: Support ED25519 and ECDSA keys in PuTTY format

View File

@@ -6,18 +6,22 @@ plugins {
id "java" id "java"
id "groovy" id "groovy"
id "jacoco" id "jacoco"
id "com.github.blindpirate.osgi" version '0.0.3' id "com.github.blindpirate.osgi" version '0.0.6'
id "maven-publish" id "maven-publish"
id 'pl.allegro.tech.build.axion-release' version '1.11.0' id "signing"
id "com.bmuschko.docker-remote-api" version "6.4.0" id 'pl.allegro.tech.build.axion-release' version '1.13.3'
id "com.github.hierynomus.license" version "0.12.1" id "com.bmuschko.docker-remote-api" version "7.1.0"
id "com.jfrog.bintray" version "1.8.5" id "com.github.hierynomus.license" version "0.16.1"
id 'ru.vyarus.java-lib' version '1.0.5' id 'ru.vyarus.github-info' version '1.2.0'
// id 'ru.vyarus.pom' version '1.0.3' id "io.github.gradle-nexus.publish-plugin" version "1.0.0"
id 'ru.vyarus.github-info' version '1.1.0' }
repositories {
mavenCentral()
} }
group = "com.hierynomus" group = "com.hierynomus"
defaultTasks ["build"]
ext.moduleName = "${project.group}.${project.name}" ext.moduleName = "${project.group}.${project.name}"
scmVersion { scmVersion {
@@ -33,23 +37,17 @@ scmVersion {
project.version = scmVersion.version project.version = scmVersion.version
defaultTasks "build" configurations.implementation.transitive = false
repositories {
mavenCentral()
}
configurations.compile.transitive = false
def bouncycastleVersion = "1.69" def bouncycastleVersion = "1.69"
def sshdVersion = "2.1.0" def sshdVersion = "2.1.0"
dependencies { dependencies {
implementation "org.slf4j:slf4j-api:1.7.30" implementation "org.slf4j:slf4j-api:1.7.32"
implementation "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
implementation "com.jcraft:jzlib:1.1.3" implementation "com.jcraft:jzlib:1.1.3"
implementation "com.hierynomus:asn-one:0.5.0" implementation "com.hierynomus:asn-one:0.6.0"
implementation "net.i2p.crypto:eddsa:0.3.0" implementation "net.i2p.crypto:eddsa:0.3.0"
@@ -59,7 +57,7 @@ dependencies {
testImplementation "org.apache.sshd:sshd-core:$sshdVersion" testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion" testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion"
testImplementation "org.apache.sshd:sshd-scp:$sshdVersion" testImplementation "org.apache.sshd:sshd-scp:$sshdVersion"
testRuntimeOnly "ch.qos.logback:logback-classic:1.2.3" testRuntimeOnly "ch.qos.logback:logback-classic:1.2.6"
testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4' testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.9' testImplementation 'org.apache.httpcomponents:httpclient:4.5.9'
@@ -71,7 +69,7 @@ license {
mapping { mapping {
java = 'SLASHSTAR_STYLE' java = 'SLASHSTAR_STYLE'
} }
excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java', '**/org/mindrot/jbcrypt/*.java']) excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java', '**/com/hierynomus/sshj/userauth/keyprovider/bcrypt/*.java'])
} }
if (!JavaVersion.current().isJava9Compatible()) { if (!JavaVersion.current().isJava9Compatible()) {
@@ -85,7 +83,6 @@ if (JavaVersion.current().isJava8Compatible()) {
} }
} }
compileJava { compileJava {
options.compilerArgs.addAll(['--release', '7']) options.compilerArgs.addAll(['--release', '7'])
} }
@@ -123,6 +120,12 @@ jar {
} }
} }
java {
withJavadocJar()
withSourcesJar()
}
sourcesJar { sourcesJar {
manifest { manifest {
attributes( attributes(
@@ -188,71 +191,82 @@ github {
license 'Apache' license 'Apache'
} }
pom { publishing {
description "SSHv2 library for Java" publications {
url "https://github.com/hierynomus/sshj" maven(MavenPublication) {
inceptionYear "2009" from(components.java)
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"
}
}
}
} }
if (project.hasProperty("bintrayUsername") && project.hasProperty("bintrayApiKey")) { project.signing {
bintray { required { project.gradle.taskGraph.hasTask("release") }
user = project.property("bintrayUsername") sign publishing.publications.maven
key = project.property("bintrayApiKey")
publish = true if (project.hasProperty("signingKeyId") || project.hasProperty("signingKey")) {
publications = ["maven"] def signingKeyId = project.findProperty("signingKeyId")
pkg { def signingKey = project.findProperty("signingKey")
repo = "maven" def signingPassword = project.findProperty("signingPassword")
name = "${project.name}" if (signingKeyId) {
licenses = ["Apache-2.0"] useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
vcsUrl = "https://github.com/hierynomus/sshj.git" } else if (signingKey) {
labels = ["ssh", "sftp", "secure-shell", "network", "file-transfer"] useInMemoryPgpKeys(signingKey, signingPassword)
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()) project.plugins.withType(MavenPublishPlugin).all {
gpg { PublishingExtension publishing = project.extensions.getByType(PublishingExtension)
sign = true publishing.publications.withType(MavenPublication).all { mavenPublication ->
passphrase = project.property("signing.password") mavenPublication.pom {
} name = "${project.name}"
mavenCentralSync { description = 'SSHv2 library for Java'
sync = true inceptionYear = '2009'
user = project.property("sonatypeUsername") url = "https://github.com/hierynomus/${project.name}"
password = project.property("sonatypePassword") licenses {
close = 1 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"
} }
} }
} }
}
nexusPublishing {
repositories {
sonatype() //sonatypeUsername and sonatypePassword properties are used automatically
}
connectTimeout = Duration.ofMinutes(3)
clientTimeout = Duration.ofMinutes(3)
} }
jacocoTestReport { jacocoTestReport {
@@ -295,7 +309,7 @@ task stopItestContainer(type: DockerStopContainer) {
task forkedUploadRelease(type: GradleBuild) { task forkedUploadRelease(type: GradleBuild) {
buildFile = project.buildFile buildFile = project.buildFile
tasks = ["bintrayUpload"] tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
} }
project.tasks.integrationTest.dependsOn(startItestContainer) project.tasks.integrationTest.dependsOn(startItestContainer)

View File

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

View File

@@ -30,7 +30,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.mindrot.jbcrypt.BCrypt; import com.hierynomus.sshj.userauth.keyprovider.bcrypt.BCrypt;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.*; import java.security.*;
import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPrivateKeySpec;
import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Arrays; import java.util.Arrays;
/** /**
@@ -245,13 +245,9 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519")))); kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
break; break;
case RSA: case RSA:
BigInteger n = keyBuffer.readMPInt(); // Modulus final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
keyBuffer.readMPInt(); // Public Exponent final PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(rsaPrivateCrtKeySpec);
BigInteger d = keyBuffer.readMPInt(); // Private Exponent kp = new KeyPair(publicKey, privateKey);
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; break;
case ECDSA256: case ECDSA256:
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256")); kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
@@ -284,6 +280,35 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN()); ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec); ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks); 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

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

View File

@@ -140,7 +140,7 @@ public class SSHClient
super(DEFAULT_PORT); super(DEFAULT_PORT);
loggerFactory = config.getLoggerFactory(); loggerFactory = config.getLoggerFactory();
log = loggerFactory.getLogger(getClass()); log = loggerFactory.getLogger(getClass());
this.trans = new TransportImpl(config, this); this.trans = new TransportImpl(config);
this.auth = new UserAuthImpl(trans); this.auth = new UserAuthImpl(trans);
this.conn = new ConnectionImpl(trans, config.getKeepAliveProvider()); this.conn = new ConnectionImpl(trans, config.getKeepAliveProvider());
} }

View File

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

View File

@@ -0,0 +1,32 @@
/*
* 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.sftp;
public enum RenameFlags {
OVERWRITE(1),
ATOMIC(2),
NATIVE(4);
private final long flag;
RenameFlags(long flag) {
this.flag = flag;
}
public long longValue() {
return flag;
}
}

View File

@@ -115,9 +115,13 @@ public class SFTPClient
} }
} }
public void rename(String oldpath, String newpath) 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)
throws IOException { throws IOException {
engine.rename(oldpath, newpath); engine.rename(oldpath, newpath, renameFlags);
} }
public void rm(String filename) public void rm(String filename)

View File

@@ -230,12 +230,18 @@ public class SFTPEngine
return stat(PacketType.LSTAT, path); return stat(PacketType.LSTAT, path);
} }
public void rename(String oldPath, String newPath) public void rename(String oldPath, String newPath, Set<RenameFlags> flags)
throws IOException { throws IOException {
if (operativeVersion < 1) if (operativeVersion < 1)
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion); throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
long renameFlagMask = 0L;
for (RenameFlags flag : flags) {
renameFlagMask = renameFlagMask | flag.longValue();
}
doRequest( doRequest(
newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset()) newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset()).putUInt32(renameFlagMask)
).ensureStatusPacketIsOK(); ).ensureStatusPacketIsOK();
} }

View File

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

View File

@@ -0,0 +1,63 @@
/*
* 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,26 +15,19 @@
*/ */
package net.schmizz.sshj.signature; 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.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException; import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
/** /**
* DSA {@link Signature} * DSA {@link Signature}
*/ */
public class SignatureDSA public class SignatureDSA
extends AbstractSignature { extends AbstractSignatureDSA {
/** /**
* A named factory for DSA signature * A named factory for DSA signature
@@ -90,32 +83,14 @@ public class SignatureDSA
public boolean verify(byte[] sig) { public boolean verify(byte[] sig) {
try { try {
byte[] sigBlob = extractSig(sig, "ssh-dss"); byte[] sigBlob = extractSig(sig, "ssh-dss");
return signature.verify(asnEncode(sigBlob)); 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));
} catch (SignatureException e) { } catch (SignatureException e) {
throw new SSHRuntimeException(e); throw new SSHRuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
throw new SSHRuntimeException(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,9 +15,8 @@
*/ */
package net.schmizz.sshj.signature; package net.schmizz.sshj.signature;
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder; 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.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer; import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.Buffer; import net.schmizz.sshj.common.Buffer;
@@ -26,15 +25,12 @@ import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException; import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.ArrayList;
import java.util.List;
/** ECDSA {@link Signature} */ /** ECDSA {@link Signature} */
public class SignatureECDSA extends AbstractSignature { public class SignatureECDSA extends AbstractSignatureDSA {
/** A named factory for ECDSA-256 signature */ /** A named factory for ECDSA-256 signature */
public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> { public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> {
@@ -91,7 +87,7 @@ public class SignatureECDSA extends AbstractSignature {
@Override @Override
public byte[] encode(byte[] sig) { public byte[] encode(byte[] sig) {
ByteArrayInputStream bais = new ByteArrayInputStream(sig); ByteArrayInputStream bais = new ByteArrayInputStream(sig);
com.hierynomus.asn1.ASN1InputStream asn1InputStream = new com.hierynomus.asn1.ASN1InputStream(new DERDecoder(), bais); ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), bais);
try { try {
ASN1Sequence sequence = asn1InputStream.readObject(); ASN1Sequence sequence = asn1InputStream.readObject();
ASN1Integer r = (ASN1Integer) sequence.get(0); ASN1Integer r = (ASN1Integer) sequence.get(0);
@@ -110,35 +106,14 @@ public class SignatureECDSA extends AbstractSignature {
public boolean verify(byte[] sig) { public boolean verify(byte[] sig) {
try { try {
byte[] sigBlob = extractSig(sig, keyTypeName); byte[] sigBlob = extractSig(sig, keyTypeName);
return signature.verify(asnEncode(sigBlob)); Buffer.PlainBuffer sigbuf = new Buffer.PlainBuffer(sigBlob);
BigInteger r = sigbuf.readMPInt();
BigInteger s = sigbuf.readMPInt();
return signature.verify(encodeAsnSignature(r, s));
} catch (SignatureException e) { } catch (SignatureException e) {
throw new SSHRuntimeException(e); throw new SSHRuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
throw new SSHRuntimeException(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,11 +170,22 @@ final class KeyExchanger
private void sendKexInit() private void sendKexInit()
throws TransportException { throws TransportException {
log.debug("Sending SSH_MSG_KEXINIT"); log.debug("Sending SSH_MSG_KEXINIT");
clientProposal = new Proposal(transport.getConfig()); List<String> knownHostAlgs = findKnownHostAlgs(transport.getRemoteHost(), transport.getRemotePort());
clientProposal = new Proposal(transport.getConfig(), knownHostAlgs);
transport.write(clientProposal.getPacket()); transport.write(clientProposal.getPacket());
kexInitSent.set(); 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() private void sendNewKeys()
throws TransportException { throws TransportException {
log.debug("Sending SSH_MSG_NEWKEYS"); log.debug("Sending SSH_MSG_NEWKEYS");

View File

@@ -38,9 +38,9 @@ class Proposal {
private final List<String> s2cComp; private final List<String> s2cComp;
private final SSHPacket packet; private final SSHPacket packet;
public Proposal(Config config) { public Proposal(Config config, List<String> knownHostAlgs) {
kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories()); kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
sig = Factory.Named.Util.getNames(config.getKeyAlgorithms()); sig = filterKnownHostKeyAlgorithms(Factory.Named.Util.getNames(config.getKeyAlgorithms()), knownHostAlgs);
c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories()); c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories()); c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories()); c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());
@@ -127,32 +127,43 @@ class Proposal {
public NegotiatedAlgorithms negotiate(Proposal other) public NegotiatedAlgorithms negotiate(Proposal other)
throws TransportException { throws TransportException {
return new NegotiatedAlgorithms( return new NegotiatedAlgorithms(
firstMatch("KeyExchangeAlgorithms", firstMatch("KeyExchangeAlgorithms", this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
this.getKeyExchangeAlgorithms(), firstMatch("HostKeyAlgorithms", this.getHostKeyAlgorithms(), other.getHostKeyAlgorithms()),
other.getKeyExchangeAlgorithms()), firstMatch("Client2ServerCipherAlgorithms", this.getClient2ServerCipherAlgorithms(),
firstMatch("HostKeyAlgorithms", other.getClient2ServerCipherAlgorithms()),
this.getHostKeyAlgorithms(), firstMatch("Server2ClientCipherAlgorithms", this.getServer2ClientCipherAlgorithms(),
other.getHostKeyAlgorithms()), other.getServer2ClientCipherAlgorithms()),
firstMatch("Client2ServerCipherAlgorithms", firstMatch("Client2ServerMACAlgorithms", this.getClient2ServerMACAlgorithms(),
this.getClient2ServerCipherAlgorithms(), other.getClient2ServerMACAlgorithms()),
other.getClient2ServerCipherAlgorithms()), firstMatch("Server2ClientMACAlgorithms", this.getServer2ClientMACAlgorithms(),
firstMatch("Server2ClientCipherAlgorithms", other.getServer2ClientMACAlgorithms()),
this.getServer2ClientCipherAlgorithms(), firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
other.getServer2ClientCipherAlgorithms()), other.getClient2ServerCompressionAlgorithms()),
firstMatch("Client2ServerMACAlgorithms", firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
this.getClient2ServerMACAlgorithms(), other.getServer2ClientCompressionAlgorithms()),
other.getClient2ServerMACAlgorithms()), other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS));
firstMatch("Server2ClientMACAlgorithms", }
this.getServer2ClientMACAlgorithms(),
other.getServer2ClientMACAlgorithms()), private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
firstMatch("Client2ServerCompressionAlgorithms", if (knownHostKeyAlgorithms != null && !knownHostKeyAlgorithms.isEmpty()) {
this.getClient2ServerCompressionAlgorithms(), List<String> preferredAlgorithms = new ArrayList<String>();
other.getClient2ServerCompressionAlgorithms()), List<String> otherAlgorithms = new ArrayList<String>();
firstMatch("Server2ClientCompressionAlgorithms",
this.getServer2ClientCompressionAlgorithms(), for (String configuredKeyAlgorithm : configuredKeyAlgorithms) {
other.getServer2ClientCompressionAlgorithms()), if (knownHostKeyAlgorithms.contains(configuredKeyAlgorithm)) {
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS) 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) private static String firstMatch(String ofWhat, List<String> a, List<String> b)

View File

@@ -85,22 +85,6 @@ public interface Transport
*/ */
void setTimeoutMs(int timeout); 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. */ /** @return the hostname to which this transport is connected. */
String getRemoteHost(); String getRemoteHost();

View File

@@ -80,12 +80,6 @@ public final class TransportImpl
private final Reader reader; private final Reader reader;
/**
* @deprecated Moved to {@link net.schmizz.sshj.SSHClient}
*/
@Deprecated
private final SSHClient sshClient;
private final Encoder encoder; private final Encoder encoder;
private final Decoder decoder; private final Decoder decoder;
@@ -147,29 +141,6 @@ public final class TransportImpl
this.decoder = new Decoder(this); this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this); this.kexer = new KeyExchanger(this);
this.clientID = String.format("SSH-2.0-%s", config.getVersion()); 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 @Override
@@ -286,20 +257,6 @@ public final class TransportImpl
this.timeoutMs = timeoutMs; 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 @Override
public String getRemoteHost() { public String getRemoteHost() {
return connInfo.host; return connInfo.host;

View File

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

View File

@@ -16,6 +16,7 @@
package net.schmizz.sshj.transport.verification; package net.schmizz.sshj.transport.verification;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.List;
/** Host key verification interface. */ /** Host key verification interface. */
public interface HostKeyVerifier { public interface HostKeyVerifier {
@@ -35,4 +36,12 @@ public interface HostKeyVerifier {
*/ */
boolean verify(String hostname, int port, PublicKey key); 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,6 +90,10 @@ 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() { public File getFile() {
return khFile; return khFile;
@@ -103,7 +107,7 @@ public class OpenSSHKnownHosts
return false; return false;
} }
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname; final String adjustedHostname = adjustHostname(hostname, port);
boolean foundApplicableHostEntry = false; boolean foundApplicableHostEntry = false;
for (KnownHostEntry e : entries) { for (KnownHostEntry e : entries) {
@@ -127,6 +131,22 @@ public class OpenSSHKnownHosts
return hostKeyUnverifiableAction(adjustedHostname, key); 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)) {
knownHostAlgorithms.add(e.getType().toString());
}
} catch (IOException ioe) {
}
}
return knownHostAlgorithms;
}
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) { protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
return false; return false;
} }

View File

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

View File

@@ -27,7 +27,12 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -53,8 +58,6 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
protected final Logger log = LoggerFactory.getLogger(getClass()); protected final Logger log = LoggerFactory.getLogger(getClass());
protected char[] passphrase; // for blanking out
protected KeyPairConverter<PrivateKeyInfo> privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter(); protected KeyPairConverter<PrivateKeyInfo> privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter();
protected KeyPair readKeyPair() protected KeyPair readKeyPair()
@@ -74,22 +77,19 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
if (o instanceof PEMEncryptedKeyPair) { if (o instanceof PEMEncryptedKeyPair) {
final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o; final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o;
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); final PEMKeyPair pemKeyPair = readEncryptedKeyPair(encryptedKeyPair);
if (SecurityUtils.getSecurityProvider() != null) { kp = pemConverter.getKeyPair(pemKeyPair);
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) { } else if (o instanceof PEMKeyPair) {
kp = pemConverter.getKeyPair((PEMKeyPair) o); kp = pemConverter.getKeyPair((PEMKeyPair) o);
} else if (o instanceof PrivateKeyInfo) { } else if (o instanceof PrivateKeyInfo) {
final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) o; final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) o;
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo); final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
kp = pemConverter.getKeyPair(pemKeyPair); 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 { } else {
log.warn("Unexpected PKCS8 PEM Object [{}]", o); log.warn("Unexpected PKCS8 PEM Object [{}]", o);
} }
@@ -114,4 +114,37 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
public String toString() { public String toString() {
return "PKCS8KeyFile{resource=" + resource + "}"; 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,6 +29,8 @@ import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.password.PasswordUtils; import net.schmizz.sshj.userauth.password.PasswordUtils;
import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters; 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.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
@@ -40,11 +42,15 @@ import java.io.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.*; import java.security.*;
import java.security.spec.*; import java.security.spec.*;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* <h2>Sample PuTTY file format</h2> * <h2>Sample PuTTY file format</h2>
*
* <pre> * <pre>
* PuTTY-User-Key-File-2: ssh-rsa * PuTTY-User-Key-File-2: ssh-rsa
* Encryption: none * Encryption: none
@@ -70,8 +76,7 @@ import java.util.Map;
*/ */
public class PuTTYKeyFile extends BaseFileKeyProvider { public class PuTTYKeyFile extends BaseFileKeyProvider {
public static class Factory public static class Factory implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
@Override @Override
public FileKeyProvider create() { public FileKeyProvider create() {
@@ -84,38 +89,46 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} }
} }
private Integer keyFileVersion;
private byte[] privateKey; private byte[] privateKey;
private byte[] publicKey; private byte[] publicKey;
private byte[] verifyHmac; // only used by v3 keys
/** /**
* Key type. Either "ssh-rsa" for RSA key, or "ssh-dss" for DSA key. * Key type
*/ */
@Override @Override
public KeyType getType() throws IOException { public KeyType getType() throws IOException {
return KeyType.fromString(headers.get("PuTTY-User-Key-File-2")); String headerName = String.format("PuTTY-User-Key-File-%d", this.keyFileVersion);
return KeyType.fromString(headers.get(headerName));
} }
public boolean isEncrypted() { public boolean isEncrypted() throws IOException {
// Currently the only supported encryption types are "aes256-cbc" and "none". // Currently, the only supported encryption types are "aes256-cbc" and "none".
return "aes256-cbc".equals(headers.get("Encryption")); 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));
} }
private Map<String, String> payload private Map<String, String> payload = new HashMap<String, String>();
= new HashMap<String, String>();
/** /**
* For each line that looks like "Xyz: vvv", it will be stored in this map. * For each line that looks like "Xyz: vvv", it will be stored in this map.
*/ */
private final Map<String, String> headers private final Map<String, String> headers = new HashMap<String, String>();
= new HashMap<String, String>();
protected KeyPair readKeyPair() throws IOException { protected KeyPair readKeyPair() throws IOException {
this.parseKeyPair(); this.parseKeyPair();
final Buffer.PlainBuffer publicKeyReader = new Buffer.PlainBuffer(publicKey); final Buffer.PlainBuffer publicKeyReader = new Buffer.PlainBuffer(publicKey);
final Buffer.PlainBuffer privateKeyReader = new Buffer.PlainBuffer(privateKey); final Buffer.PlainBuffer privateKeyReader = new Buffer.PlainBuffer(privateKey);
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name. final KeyType keyType = this.getType();
if (KeyType.RSA.equals(this.getType())) { publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
if (KeyType.RSA.equals(keyType)) {
// public key exponent // public key exponent
BigInteger e = publicKeyReader.readMPInt(); BigInteger e = publicKeyReader.readMPInt();
// modulus // modulus
@@ -131,15 +144,13 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(s.getMessage(), s); throw new IOException(s.getMessage(), s);
} }
try { try {
return new KeyPair( return new KeyPair(factory.generatePublic(new RSAPublicKeySpec(n, e)),
factory.generatePublic(new RSAPublicKeySpec(n, e)), factory.generatePrivate(new RSAPrivateKeySpec(n, d)));
factory.generatePrivate(new RSAPrivateKeySpec(n, d))
);
} catch (InvalidKeySpecException i) { } catch (InvalidKeySpecException i) {
throw new IOException(i.getMessage(), i); throw new IOException(i.getMessage(), i);
} }
} }
if (KeyType.DSA.equals(this.getType())) { if (KeyType.DSA.equals(keyType)) {
BigInteger p = publicKeyReader.readMPInt(); BigInteger p = publicKeyReader.readMPInt();
BigInteger q = publicKeyReader.readMPInt(); BigInteger q = publicKeyReader.readMPInt();
BigInteger g = publicKeyReader.readMPInt(); BigInteger g = publicKeyReader.readMPInt();
@@ -155,22 +166,20 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(s.getMessage(), s); throw new IOException(s.getMessage(), s);
} }
try { try {
return new KeyPair( return new KeyPair(factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)), factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)));
factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g))
);
} catch (InvalidKeySpecException e) { } catch (InvalidKeySpecException e) {
throw new IOException(e.getMessage(), e); throw new IOException(e.getMessage(), e);
} }
} }
if (KeyType.ED25519.equals(this.getType())) { if (KeyType.ED25519.equals(keyType)) {
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519"); EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519); EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519);
EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519); EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519);
return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec)); return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec));
} }
final String ecdsaCurve; final String ecdsaCurve;
switch (this.getType()) { switch (keyType) {
case ECDSA256: case ECDSA256:
ecdsaCurve = "P-256"; ecdsaCurve = "P-256";
break; break;
@@ -187,12 +196,12 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
if (ecdsaCurve != null) { if (ecdsaCurve != null) {
BigInteger s = new BigInteger(1, privateKeyReader.readBytes()); BigInteger s = new BigInteger(1, privateKeyReader.readBytes());
X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve); X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve);
ECNamedCurveSpec ecCurveSpec = ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(),
new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(), ecParams.getN()); ecParams.getN());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec); ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
try { try {
PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks); PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
return new KeyPair(getType().readPubKeyFromBuffer(publicKeyReader), privateKey); return new KeyPair(keyType.readPubKeyFromBuffer(publicKeyReader), privateKey);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e); throw new IOException(e.getMessage(), e);
} }
@@ -201,6 +210,7 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} }
protected void parseKeyPair() throws IOException { protected void parseKeyPair() throws IOException {
this.keyFileVersion = null;
BufferedReader r = new BufferedReader(resource.getReader()); BufferedReader r = new BufferedReader(resource.getReader());
// Parse the text into headers and payloads // Parse the text into headers and payloads
try { try {
@@ -211,6 +221,9 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
if (idx > 0) { if (idx > 0) {
headerName = line.substring(0, idx); headerName = line.substring(0, idx);
headers.put(headerName, line.substring(idx + 2)); headers.put(headerName, line.substring(idx + 2));
if (headerName.startsWith("PuTTY-User-Key-File-")) {
this.keyFileVersion = Integer.parseInt(headerName.substring(20));
}
} else { } else {
String s = payload.get(headerName); String s = payload.get(headerName);
if (s == null) { if (s == null) {
@@ -226,6 +239,9 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} finally { } finally {
r.close(); r.close();
} }
if (this.keyFileVersion == null) {
throw new IOException("Invalid key file format: missing \"PuTTY-User-Key-File-?\" entry");
}
// Retrieve keys from payload // Retrieve keys from payload
publicKey = Base64.decode(payload.get("Public-Lines")); publicKey = Base64.decode(payload.get("Public-Lines"));
if (this.isEncrypted()) { if (this.isEncrypted()) {
@@ -236,8 +252,14 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
passphrase = "".toCharArray(); passphrase = "".toCharArray();
} }
try { try {
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase)); privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), passphrase);
this.verify(new String(passphrase)); Mac mac;
if (this.keyFileVersion <= 2) {
mac = this.prepareVerifyMacV2(passphrase);
} else {
mac = this.prepareVerifyMacV3();
}
this.verify(mac);
} finally { } finally {
PasswordUtils.blankOut(passphrase); PasswordUtils.blankOut(passphrase);
} }
@@ -247,77 +269,158 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} }
/** /**
* Converts a passphrase into a key, by following the convention that PuTTY uses. * Converts a passphrase into a key, by following the convention that PuTTY
* <p/> * uses. Only PuTTY v1/v2 key files
* <p/> * <p><p/>
* This is used to decrypt the private key when it's encrypted. * This is used to decrypt the private key when it's encrypted.
*/ */
private byte[] toKey(final String passphrase) throws IOException { 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
try { try {
MessageDigest digest = MessageDigest.getInstance("SHA-1"); MessageDigest digest = MessageDigest.getInstance("SHA-1");
// The encryption key is derived from the passphrase by means of a succession of SHA-1 hashes. // The encryption key is derived from the passphrase by means of a succession of
// SHA-1 hashes.
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
// Sequence number 0 // Sequence number 0
digest.update(new byte[]{0, 0, 0, 0}); digest.update(new byte[]{0, 0, 0, 0});
digest.update(passphrase.getBytes()); digest.update(encodedPassphrase);
byte[] key1 = digest.digest(); byte[] key1 = digest.digest();
// Sequence number 1 // Sequence number 1
digest.update(new byte[]{0, 0, 0, 1}); digest.update(new byte[]{0, 0, 0, 1});
digest.update(passphrase.getBytes()); digest.update(encodedPassphrase);
byte[] key2 = digest.digest(); byte[] key2 = digest.digest();
byte[] r = new byte[32]; Arrays.fill(encodedPassphrase, (byte) 0);
System.arraycopy(key1, 0, r, 0, 20);
System.arraycopy(key2, 0, r, 20, 12); 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
return r;
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new IOException(e.getMessage(), e); throw new IOException(e.getMessage(), e);
} }
} }
/** /**
* Verify the MAC. * Uses BouncyCastle Argon2 implementation
*/ */
private void verify(final String passphrase) throws IOException { 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):
try { try {
// The key to the MAC is itself a SHA-1 hash of:
MessageDigest digest = MessageDigest.getInstance("SHA-1"); MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update("putty-private-key-file-mac-key".getBytes()); digest.update("putty-private-key-file-mac-key".getBytes());
if (passphrase != null) { if (passphrase != null) {
digest.update(passphrase.getBytes()); byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
digest.update(encodedPassphrase);
Arrays.fill(encodedPassphrase, (byte) 0);
} }
final byte[] key = digest.digest(); final byte[] key = digest.digest();
Mac mac = Mac.getInstance("HmacSHA1");
final Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm())); mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IOException(e.getMessage(), e);
}
}
final ByteArrayOutputStream out = new ByteArrayOutputStream(); private Mac prepareVerifyMacV3() throws IOException {
final DataOutputStream data = new DataOutputStream(out); // for v3 keys the hMac key is included in the Argon output
// name of algorithm try {
data.writeInt(this.getType().toString().length()); Mac mac = Mac.getInstance("HmacSHA256");
data.writeBytes(this.getType().toString()); mac.init(new SecretKeySpec(this.verifyHmac, 0, 32, mac.getAlgorithm()));
return mac;
data.writeInt(headers.get("Encryption").length()); } catch (NoSuchAlgorithmException | InvalidKeyException e) {
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); throw new IOException(e.getMessage(), e);
} }
} }
@@ -325,17 +428,21 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
/** /**
* Decrypt private key * Decrypt private key
* *
* @param privateKey the SSH private key to be decrypted
* @param passphrase To decrypt * @param passphrase To decrypt
*/ */
private byte[] decrypt(final byte[] key, final String passphrase) throws IOException { private byte[] decrypt(final byte[] privateKey, final char[] passphrase) throws IOException {
try { try {
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
final byte[] expanded = this.toKey(passphrase); this.initCipher(passphrase, cipher);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"), return cipher.doFinal(privateKey);
new IvParameterSpec(new byte[16])); // initial vector=0
return cipher.doFinal(key);
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e); throw new IOException(e.getMessage(), e);
} }
} }
public int getKeyFileVersion() {
return keyFileVersion;
}
} }

View File

@@ -15,6 +15,8 @@
*/ */
package net.schmizz.sshj.userauth.password; package net.schmizz.sshj.userauth.password;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
/** Static utility method and factories */ /** Static utility method and factories */
@@ -54,4 +56,14 @@ 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);
return StandardCharsets.UTF_8.encode(charBuffer).array();
}
} }

View File

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

View File

@@ -20,6 +20,9 @@ import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.channel.direct.Session;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
public class LoadsOfConnects { public class LoadsOfConnects {
@@ -31,15 +34,23 @@ public class LoadsOfConnects {
@Test @Test
public void loadsOfConnects() { public void loadsOfConnects() {
try { try {
fixture.start();
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
log.info("Try " + i); log.info("Try " + i);
fixture.start(); SSHClient client = fixture.setupConnectedDefaultClient();
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.stopClient(); fixture.stopClient();
fixture.stopServer();
} }
} catch (Exception e) { } catch (Exception e) {
fail(e.getMessage()); fail(e.getMessage());
} finally {
fixture.stopServer();
} }
} }

View File

@@ -43,6 +43,7 @@ import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
@@ -103,7 +104,7 @@ public class OpenSSHKeyFileTest {
assertArrayEquals(new char[passwordChars.length], passwordChars); assertArrayEquals(new char[passwordChars.length], passwordChars);
} }
} }
}; }
final PasswordFinder onlyGivesWhenReady = new PasswordFinder() { final PasswordFinder onlyGivesWhenReady = new PasswordFinder() {
@Override @Override
@@ -187,7 +188,7 @@ public class OpenSSHKeyFileTest {
} }
@Test @Test
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException, GeneralSecurityException { public void shouldHaveCorrectFingerprintForECDSA256() throws IOException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile(); OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256")); 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"; 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";
@@ -197,7 +198,7 @@ public class OpenSSHKeyFileTest {
} }
@Test @Test
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException, GeneralSecurityException { public void shouldHaveCorrectFingerprintForECDSA384() throws IOException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile(); OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384")); 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"; String expected = "384 MD5:ee:9b:82:d1:47:01:16:1b:27:da:f5:27:fd:b2:eb:e2";
@@ -207,7 +208,7 @@ public class OpenSSHKeyFileTest {
} }
@Test @Test
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException, GeneralSecurityException { public void shouldHaveCorrectFingerprintForECDSA521() throws IOException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile(); OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521")); 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"; String expected = "521 MD5:22:e2:f4:3c:61:ae:e9:85:a1:4d:d9:6c:13:aa:eb:00";
@@ -274,6 +275,35 @@ public class OpenSSHKeyFileTest {
assertThat(aPrivate.getAlgorithm(), equalTo("RSA")); 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 @Test
public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException { public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException {
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();

View File

@@ -15,13 +15,18 @@
*/ */
package net.schmizz.sshj.keyprovider; package net.schmizz.sshj.keyprovider;
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; 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 net.schmizz.sshj.util.KeyUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -35,6 +40,9 @@ import static org.junit.Assert.assertEquals;
public class PKCS8KeyFileTest { public class PKCS8KeyFileTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
static final FileKeyProvider rsa = new PKCS8KeyFile(); static final FileKeyProvider rsa = new PKCS8KeyFile();
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469"; static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
@@ -70,6 +78,25 @@ public class PKCS8KeyFileTest {
assertEquals("RSA", provider.getPrivate().getAlgorithm()); 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 @Test
public void testPkcs8Ecdsa() throws IOException { public void testPkcs8Ecdsa() throws IOException {
final PKCS8KeyFile provider = new PKCS8KeyFile(); final PKCS8KeyFile provider = new PKCS8KeyFile();

View File

@@ -18,13 +18,14 @@ package net.schmizz.sshj.keyprovider;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile; import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import net.schmizz.sshj.userauth.password.PasswordFinder; import net.schmizz.sshj.util.UnitTestPasswordFinder;
import net.schmizz.sshj.userauth.password.Resource;
import org.junit.Test; import org.junit.Test;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@@ -226,6 +227,113 @@ public class PuTTYKeyFileTest {
"nLEIBzB8WEaMMgDz5qwlqq7eBxLPIIi9uHyjMf7NOsQ=\n" + "nLEIBzB8WEaMMgDz5qwlqq7eBxLPIIi9uHyjMf7NOsQ=\n" +
"Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\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 @Test
public void test2048() throws Exception { public void test2048() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile(); PuTTYKeyFile key = new PuTTYKeyFile();
@@ -273,17 +381,8 @@ public class PuTTYKeyFileTest {
// -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \ // -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \
// --new-passphrase <(echo 123456) // --new-passphrase <(echo 123456)
PuTTYKeyFile key = new PuTTYKeyFile(); PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"), new PasswordFinder() { key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"),
@Override new UnitTestPasswordFinder("123456"));
public char[] reqPassword(Resource<?> resource) {
return "123456".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPrivate()); assertNotNull(key.getPrivate());
assertNotNull(key.getPublic()); assertNotNull(key.getPublic());
@@ -341,21 +440,63 @@ public class PuTTYKeyFileTest {
assertEquals(key.getPublic(), referenceKey.getPublic()); 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 @Test
public void testCorrectPassphraseRsa() throws Exception { public void testCorrectPassphraseRsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile(); PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() { key.init(new StringReader(ppk1024_passphrase), new UnitTestPasswordFinder("123456"));
@Override
public char[] reqPassword(Resource<?> resource) {
// correct passphrase
return "123456".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 // Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
assertNotNull(key.getPrivate()); assertNotNull(key.getPrivate());
assertNotNull(key.getPublic()); assertNotNull(key.getPublic());
@@ -364,18 +505,8 @@ public class PuTTYKeyFileTest {
@Test(expected = IOException.class) @Test(expected = IOException.class)
public void testWrongPassphraseRsa() throws Exception { public void testWrongPassphraseRsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile(); PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() { key.init(new StringReader(ppk1024_passphrase),
@Override new UnitTestPasswordFinder("egfsdgdfgsdfsdfasfs523534dgdsgdfa"));
public char[] reqPassword(Resource<?> resource) {
// wrong passphrase
return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPublic()); assertNotNull(key.getPublic());
assertNull(key.getPrivate()); assertNull(key.getPrivate());
} }
@@ -383,18 +514,7 @@ public class PuTTYKeyFileTest {
@Test @Test
public void testCorrectPassphraseDsa() throws Exception { public void testCorrectPassphraseDsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile(); PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() { key.init(new StringReader(ppkdsa_passphrase), new UnitTestPasswordFinder("secret"));
@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 // Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
assertNotNull(key.getPrivate()); assertNotNull(key.getPrivate());
assertNotNull(key.getPublic()); assertNotNull(key.getPublic());
@@ -403,18 +523,8 @@ public class PuTTYKeyFileTest {
@Test(expected = IOException.class) @Test(expected = IOException.class)
public void testWrongPassphraseDsa() throws Exception { public void testWrongPassphraseDsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile(); PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() { key.init(new StringReader(ppkdsa_passphrase),
@Override new UnitTestPasswordFinder("egfsdgdfgsdfsdfasfs523534dgdsgdfa"));
public char[] reqPassword(Resource<?> resource) {
// wrong passphrase
return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPublic()); assertNotNull(key.getPublic());
assertNull(key.getPrivate()); assertNull(key.getPrivate());
} }

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

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

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