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 1044 additions and 459 deletions

View File

@@ -24,19 +24,6 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
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:
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",
"files.watcherExclude": {
"**/target": true
}
},
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@@ -4,9 +4,8 @@ Jeroen van Erp
:sshj_version: 0.31.0
: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://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"]
image:https://api.codacy.com/project/badge/Grade/14a0a316bb9149739b5ea26dbfa8da8a["Codacy code quality", link="https://www.codacy.com/app/jeroen_2/sshj?utm_source=github.com&utm_medium=referral&utm_content=hierynomus/sshj&utm_campaign=Badge_Grade"]
image:https://github.com/hierynomus/sshj/actions/workflows/gradle.yml/badge.svg[link="https://github.com/hierynomus/sshj/actions/workflows/gradle.yml"]
image:https://app.codacy.com/project/badge/Grade/2c8a5a67c6a54ed89c9a699fd6b27305["Codacy Grade", link="https://app.codacy.com/gh/hierynomus/sshj"]
image:https://codecov.io/gh/hierynomus/sshj/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/hierynomus/sshj"]
image:http://www.javadoc.io/badge/com.hierynomus/sshj.svg?color=blue["JavaDocs", link="http://www.javadoc.io/doc/com.hierynomus/sshj"]
image:https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj/badge.svg["Maven Central",link="https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj"]
@@ -105,6 +104,19 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
Fork away!
== 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)::
* Bump dependencies (asn-one 0.5.0, BouncyCastle 1.68, slf4j-api 1.7.30)
* Merged https://github.com/hierynomus/sshj/pull/660[#660]: Support ED25519 and ECDSA keys in PuTTY format

View File

@@ -6,18 +6,23 @@ plugins {
id "java"
id "groovy"
id "jacoco"
id "com.github.blindpirate.osgi" version '0.0.3'
id "com.github.blindpirate.osgi" version '0.0.6'
id "maven-publish"
id 'pl.allegro.tech.build.axion-release' version '1.11.0'
id "com.bmuschko.docker-remote-api" version "6.4.0"
id "com.github.hierynomus.license" version "0.12.1"
id "com.jfrog.bintray" version "1.8.5"
id 'ru.vyarus.java-lib' version '1.0.5'
// id 'ru.vyarus.pom' version '1.0.3'
id 'ru.vyarus.github-info' version '1.1.0'
id "signing"
id 'pl.allegro.tech.build.axion-release' version '1.13.3'
id "com.bmuschko.docker-remote-api" version "7.1.0"
id "com.github.hierynomus.license" version "0.16.1"
id 'ru.vyarus.github-info' version '1.2.0'
id "io.github.gradle-nexus.publish-plugin" version "1.0.0"
}
repositories {
mavenCentral()
}
group = "com.hierynomus"
defaultTasks ["build"]
ext.moduleName = "${project.group}.${project.name}"
scmVersion {
tag {
@@ -32,23 +37,17 @@ scmVersion {
project.version = scmVersion.version
defaultTasks "build"
repositories {
mavenCentral()
}
configurations.compile.transitive = false
configurations.implementation.transitive = false
def bouncycastleVersion = "1.69"
def sshdVersion = "2.1.0"
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:bcpkix-jdk15on:$bouncycastleVersion"
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"
@@ -58,7 +57,7 @@ dependencies {
testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
testImplementation "org.apache.sshd:sshd-sftp:$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.apache.httpcomponents:httpclient:4.5.9'
@@ -70,7 +69,7 @@ license {
mapping {
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()) {
@@ -84,7 +83,6 @@ if (JavaVersion.current().isJava8Compatible()) {
}
}
compileJava {
options.compilerArgs.addAll(['--release', '7'])
}
@@ -100,7 +98,10 @@ task writeSshjVersionProperties {
jar.dependsOn writeSshjVersionProperties
jar {
inputs.property("moduleName", moduleName)
manifest {
attributes 'Automatic-Module-Name': moduleName
// please see http://bnd.bndtools.org/chapters/390-wrapping.html
instruction "Bundle-Description", "SSHv2 library for Java"
instruction "Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -119,6 +120,12 @@ jar {
}
}
java {
withJavadocJar()
withSourcesJar()
}
sourcesJar {
manifest {
attributes(
@@ -184,71 +191,82 @@ github {
license 'Apache'
}
pom {
description "SSHv2 library for Java"
url "https://github.com/hierynomus/sshj"
inceptionYear "2009"
publishing {
publications {
maven(MavenPublication) {
from(components.java)
}
}
}
project.signing {
required { project.gradle.taskGraph.hasTask("release") }
sign publishing.publications.maven
if (project.hasProperty("signingKeyId") || project.hasProperty("signingKey")) {
def signingKeyId = project.findProperty("signingKeyId")
def signingKey = project.findProperty("signingKey")
def signingPassword = project.findProperty("signingPassword")
if (signingKeyId) {
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
} else if (signingKey) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
}
}
project.plugins.withType(MavenPublishPlugin).all {
PublishingExtension publishing = project.extensions.getByType(PublishingExtension)
publishing.publications.withType(MavenPublication).all { mavenPublication ->
mavenPublication.pom {
name = "${project.name}"
description = 'SSHv2 library for Java'
inceptionYear = '2009'
url = "https://github.com/hierynomus/${project.name}"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0"
}
}
developers {
developer {
id "hierynomus"
name "Jeroen van Erp"
email "jeroen@javadude.nl"
roles {
role "Lead 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 {
role "Previous lead 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 "iterage GmbH"
organizationUrl "https://iterate.ch"
roles {
role "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"
}
}
}
}
if (project.hasProperty("bintrayUsername") && project.hasProperty("bintrayApiKey")) {
bintray {
user = project.property("bintrayUsername")
key = project.property("bintrayApiKey")
publish = true
publications = ["maven"]
pkg {
repo = "maven"
name = "${project.name}"
licenses = ["Apache-2.0"]
vcsUrl = "https://github.com/hierynomus/sshj.git"
labels = ["ssh", "sftp", "secure-shell", "network", "file-transfer"]
githubRepo = "hierynomus/sshj"
version {
name = "${project.version}"
vcsTag = "v${project.version}"
released = new SimpleDateFormat('yyyy-MM-dd\'T\'HH:mm:ss.SSSZZ').format(new Date())
gpg {
sign = true
passphrase = project.property("signing.password")
}
mavenCentralSync {
sync = true
user = project.property("sonatypeUsername")
password = project.property("sonatypePassword")
close = 1
}
}
}
nexusPublishing {
repositories {
sonatype() //sonatypeUsername and sonatypePassword properties are used automatically
}
connectTimeout = Duration.ofMinutes(3)
clientTimeout = Duration.ofMinutes(3)
}
jacocoTestReport {
@@ -291,7 +309,7 @@ task stopItestContainer(type: DockerStopContainer) {
task forkedUploadRelease(type: GradleBuild) {
buildFile = project.buildFile
tasks = ["bintrayUpload"]
tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
}
project.tasks.integrationTest.dependsOn(startItestContainer)

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
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.x9.X9ECParameters;
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.LoggerFactory;
@@ -45,7 +45,7 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
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"))));
break;
case RSA:
BigInteger n = keyBuffer.readMPInt(); // Modulus
keyBuffer.readMPInt(); // Public Exponent
BigInteger d = keyBuffer.readMPInt(); // Private Exponent
keyBuffer.readMPInt(); // iqmp (q^-1 mod p)
keyBuffer.readMPInt(); // p (Prime 1)
keyBuffer.readMPInt(); // q (Prime 2)
kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(new RSAPrivateKeySpec(n, d)));
final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
final PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(rsaPrivateCrtKeySpec);
kp = new KeyPair(publicKey, privateKey);
break;
case ECDSA256:
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());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
}
/**
* Read RSA Private CRT Key Spec according to OpenSSH sshkey_private_deserialize in sshkey.c
*
* @param buffer Buffer
* @return RSA Private CRT Key Specification
* @throws Buffer.BufferException Thrown on failure to read from buffer
*/
private RSAPrivateCrtKeySpec readRsaPrivateKeySpec(final PlainBuffer buffer) throws Buffer.BufferException {
final BigInteger modulus = buffer.readMPInt();
final BigInteger publicExponent = buffer.readMPInt();
final BigInteger privateExponent = buffer.readMPInt();
final BigInteger crtCoefficient = buffer.readMPInt(); // iqmp (q^-1 mod p)
final BigInteger primeP = buffer.readMPInt();
final BigInteger primeQ = buffer.readMPInt();
// Calculate Prime Exponent P and Prime Exponent Q according to RFC 8017 Section 3.2
final BigInteger primeExponentP = privateExponent.remainder(primeP.subtract(BigInteger.ONE));
final BigInteger primeExponentQ = privateExponent.remainder(primeQ.subtract(BigInteger.ONE));
return new RSAPrivateCrtKeySpec(
modulus,
publicExponent,
privateExponent,
primeP,
primeQ,
primeExponentP,
primeExponentQ,
crtCoefficient
);
}
}

View File

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

View File

@@ -140,7 +140,7 @@ public class SSHClient
super(DEFAULT_PORT);
loggerFactory = config.getLoggerFactory();
log = loggerFactory.getLogger(getClass());
this.trans = new TransportImpl(config, this);
this.trans = new TransportImpl(config);
this.auth = new UserAuthImpl(trans);
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.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
@@ -36,7 +37,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
private final DataBuffer buffer = new DataBuffer();
private final byte[] b = new byte[1];
private boolean closed;
private AtomicBoolean closed;
private SSHException error;
private final class DataBuffer {
@@ -122,6 +123,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
this.chan = chan;
this.trans = trans;
this.win = win;
this.closed = new AtomicBoolean(false);
}
@Override
@@ -151,24 +153,21 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
private void checkClose() throws SSHException {
// Check whether either the Stream is closed, or the underlying channel is closed
if (closed || !chan.isOpen()) {
if (error != null)
if (closed.get() || !chan.isOpen()) {
if (error != null) {
throw error;
else
} else {
throw new ConnectionException("Stream closed");
}
}
}
@Override
public synchronized void close() throws IOException {
// Not closed yet, and underlying channel is open to flush the data to.
if (!closed && chan.isOpen()) {
try {
if (!closed.getAndSet(true) && chan.isOpen()) {
buffer.flush(false);
// trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
} finally {
closed = true;
}
trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
}
}

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 {
engine.rename(oldpath, newpath);
engine.rename(oldpath, newpath, renameFlags);
}
public void rm(String filename)

View File

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

View File

@@ -117,9 +117,9 @@ public class StatefulSFTPClient
}
@Override
public void rename(String oldpath, String newpath)
public void rename(String oldpath, String newpath, Set<RenameFlags> renameFlags)
throws IOException {
super.rename(cwdify(oldpath), cwdify(newpath));
super.rename(cwdify(oldpath), cwdify(newpath), renameFlags);
}
@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;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* DSA {@link Signature}
*/
public class SignatureDSA
extends AbstractSignature {
extends AbstractSignatureDSA {
/**
* A named factory for DSA signature
@@ -90,32 +83,14 @@ public class SignatureDSA
public boolean verify(byte[] sig) {
try {
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) {
throw new SSHRuntimeException(e);
} catch (IOException e) {
throw new SSHRuntimeException(e);
}
}
/**
* Encodes the signature as a DER sequence (ASN.1 format).
*/
private byte[] asnEncode(byte[] sigBlob) throws IOException {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sigBlob, 0, 20));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sigBlob, 20, 40));
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new com.hierynomus.asn1.types.primitive.ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
com.hierynomus.asn1.ASN1OutputStream asn1OutputStream = new com.hierynomus.asn1.ASN1OutputStream(new DEREncoder(), baos);
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
return baos.toByteArray();
}
}

View File

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

View File

@@ -170,11 +170,22 @@ final class KeyExchanger
private void sendKexInit()
throws TransportException {
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());
kexInitSent.set();
}
private List<String> findKnownHostAlgs(String hostname, int port) {
for (HostKeyVerifier hkv : hostVerifiers) {
List<String> keyTypes = hkv.findExistingAlgorithms(hostname, port);
if (keyTypes != null && !keyTypes.isEmpty()) {
return keyTypes;
}
}
return Collections.emptyList();
}
private void sendNewKeys()
throws TransportException {
log.debug("Sending SSH_MSG_NEWKEYS");

View File

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

View File

@@ -85,22 +85,6 @@ public interface Transport
*/
void setTimeoutMs(int timeout);
/**
* @return the interval in seconds at which a heartbeat message is sent to the server
* @deprecated Moved to {@link net.schmizz.keepalive.KeepAlive#getKeepAliveInterval()}. This is accessible through the {@link net.schmizz.sshj.connection.Connection}.
* Scheduled to be removed in 0.12.0
*/
@Deprecated
int getHeartbeatInterval();
/**
* @param interval the interval in seconds, {@code 0} means no heartbeat
* @deprecated Moved to {@link net.schmizz.keepalive.KeepAlive#getKeepAliveInterval()}. This is accessible through the {@link net.schmizz.sshj.connection.Connection}.
* Scheduled to be removed in 0.12.0
*/
@Deprecated
void setHeartbeatInterval(int interval);
/** @return the hostname to which this transport is connected. */
String getRemoteHost();

View File

@@ -80,12 +80,6 @@ public final class TransportImpl
private final Reader reader;
/**
* @deprecated Moved to {@link net.schmizz.sshj.SSHClient}
*/
@Deprecated
private final SSHClient sshClient;
private final Encoder encoder;
private final Decoder decoder;
@@ -147,29 +141,6 @@ public final class TransportImpl
this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this);
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
this.sshClient = null;
}
/*
* Temporary constructor until we remove support for the set/get Heartbeat interval from transport.
* @deprecated To be removed in 0.12.0
*/
@Deprecated
public TransportImpl(Config config, SSHClient sshClient) {
this.config = config;
this.loggerFactory = config.getLoggerFactory();
this.serviceAccept = new Event<TransportException>("service accept", TransportException.chainer, loggerFactory);
this.close = new Event<TransportException>("transport close", TransportException.chainer, loggerFactory);
this.log = loggerFactory.getLogger(getClass());
this.nullService = new NullService(this);
this.service = nullService;
this.disconnectListener = this;
this.reader = new Reader(this);
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock, loggerFactory);
this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this);
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
this.sshClient = sshClient;
}
@Override
@@ -286,20 +257,6 @@ public final class TransportImpl
this.timeoutMs = timeoutMs;
}
@Override
@Deprecated
public int getHeartbeatInterval() {
log.warn("**Deprecated**: Please use: sshClient.getConnection().getKeepAlive().getKeepAliveInterval()");
return sshClient.getConnection().getKeepAlive().getKeepAliveInterval();
}
@Override
@Deprecated
public void setHeartbeatInterval(int interval) {
log.warn("**Deprecated**: Please use: sshClient.getConnection().getKeepAlive().setKeepAliveInterval()");
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(interval);
}
@Override
public String getRemoteHost() {
return connInfo.host;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,8 @@
*/
package net.schmizz.sshj.userauth.password;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/** 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
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* JUnit unit tests for BCrypt routines
* @author Damien Miller
* @version 0.2
*/
public class TestBCrypt extends TestCase {
String test_vectors[][] = {
public class BCryptTest {
String[][] test_vectors = {
{ "",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
@@ -87,17 +91,10 @@ public class TestBCrypt extends TestCase {
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
};
/**
* Entry point for unit tests
* @param args unused
*/
public static void main(String[] args) {
junit.textui.TestRunner.run(TestBCrypt.class);
}
/**
* Test method for 'BCrypt.hashpw(String, String)'
*/
@Test
public void testHashpw() {
System.out.print("BCrypt.hashpw(): ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -108,16 +105,16 @@ public class TestBCrypt extends TestCase {
assertEquals(hashed, expected);
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.gensalt(int)'
*/
@Test
public void testGensaltInt() {
System.out.print("BCrypt.gensalt(log_rounds):");
for (int i = 4; i <= 12; i++) {
System.out.print(" " + Integer.toString(i) + ":");
System.out.print(" " + i + ":");
for (int j = 0; j < test_vectors.length; j += 4) {
String plain = test_vectors[j][0];
String salt = BCrypt.gensalt(i);
@@ -127,12 +124,12 @@ public class TestBCrypt extends TestCase {
System.out.print(".");
}
}
System.out.println("");
}
/**
* Test method for 'BCrypt.gensalt()'
*/
@Test
public void testGensalt() {
System.out.print("BCrypt.gensalt(): ");
for (int i = 0; i < test_vectors.length; i += 4) {
@@ -143,13 +140,13 @@ public class TestBCrypt extends TestCase {
assertEquals(hashed1, hashed2);
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting success
*/
@Test
public void testCheckpw_success() {
System.out.print("BCrypt.checkpw w/ good passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -158,13 +155,13 @@ public class TestBCrypt extends TestCase {
assertTrue(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting failure
*/
@Test
public void testCheckpw_failure() {
System.out.print("BCrypt.checkpw w/ bad passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -174,12 +171,12 @@ public class TestBCrypt extends TestCase {
assertFalse(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
System.out.println("");
}
/**
* Test for correct hashing of non-US-ASCII passwords
*/
@Test
public void testInternationalChars() {
System.out.print("BCrypt.hashpw w/ international chars: ");
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
@@ -192,7 +189,6 @@ public class TestBCrypt extends TestCase {
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
assertFalse(BCrypt.checkpw(pw1, h2));
System.out.print(".");
System.out.println("");
}
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: ");
for (BCryptHashTV tv : bcrypt_hash_test_vectors) {
byte[] output = new byte[tv.out.length];
@@ -250,7 +247,6 @@ public class TestBCrypt extends TestCase {
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
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}),
};
public void testBCryptPbkdfTestVectors() throws Exception {
@Test
public void testBCryptPbkdfTestVectors() {
System.out.print("BCrypt.pbkdf w/ known vectors: ");
for (BCryptPbkdfTV tv : bcrypt_pbkdf_test_vectors) {
byte[] output = new byte[tv.out.length];
@@ -289,6 +286,5 @@ public class TestBCrypt extends TestCase {
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
}

View File

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

View File

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

View File

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

View File

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