mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3256f5336d | ||
|
|
ad87db9196 | ||
|
|
781f2dc632 | ||
|
|
b2115dea6f | ||
|
|
d6d6f0dd33 | ||
|
|
93de1ecf47 | ||
|
|
46ca5375d0 | ||
|
|
771ac0e346 | ||
|
|
eb09a16aef | ||
|
|
53d241e4e3 | ||
|
|
03dd1aaf49 | ||
|
|
7742d9b661 | ||
|
|
14bf93e677 | ||
|
|
753e3a50e5 | ||
|
|
2e1ef9dbcd | ||
|
|
6f9873712f | ||
|
|
8e8e04ff1f | ||
|
|
b47e6fa012 | ||
|
|
f38fcbe57e |
13
.github/workflows/gradle.yml
vendored
13
.github/workflows/gradle.yml
vendored
@@ -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
49
.github/workflows/release.yml
vendored
Normal 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 }}
|
||||
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -2,5 +2,6 @@
|
||||
"java.checkstyle.configuration": "${workspaceFolder}/gradle/config/checkstyle/checkstyle.xml",
|
||||
"files.watcherExclude": {
|
||||
"**/target": true
|
||||
}
|
||||
},
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
|
||||
18
README.adoc
18
README.adoc
@@ -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
|
||||
|
||||
178
build.gradle
178
build.gradle
@@ -6,18 +6,22 @@ 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 {
|
||||
@@ -33,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"
|
||||
|
||||
@@ -59,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'
|
||||
|
||||
@@ -71,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()) {
|
||||
@@ -85,7 +83,6 @@ if (JavaVersion.current().isJava8Compatible()) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compileJava {
|
||||
options.compilerArgs.addAll(['--release', '7'])
|
||||
}
|
||||
@@ -123,6 +120,12 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
sourcesJar {
|
||||
manifest {
|
||||
attributes(
|
||||
@@ -188,71 +191,82 @@ github {
|
||||
license 'Apache'
|
||||
}
|
||||
|
||||
pom {
|
||||
description "SSHv2 library for Java"
|
||||
url "https://github.com/hierynomus/sshj"
|
||||
inceptionYear "2009"
|
||||
developers {
|
||||
developer {
|
||||
id "hierynomus"
|
||||
name "Jeroen van Erp"
|
||||
email "jeroen@javadude.nl"
|
||||
roles {
|
||||
role "Lead developer"
|
||||
}
|
||||
}
|
||||
developer {
|
||||
id "shikhar"
|
||||
name "Shikhar Bhushan"
|
||||
email "shikhar@schmizz.net"
|
||||
url "http://schmizz.net"
|
||||
roles {
|
||||
role "Previous lead developer"
|
||||
}
|
||||
}
|
||||
developer {
|
||||
id "iterate"
|
||||
name "David Kocher"
|
||||
email "dkocher@iterate.ch"
|
||||
organization "iterage GmbH"
|
||||
organizationUrl "https://iterate.ch"
|
||||
roles {
|
||||
role "Developer"
|
||||
}
|
||||
}
|
||||
}
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from(components.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
project.signing {
|
||||
required { project.gradle.taskGraph.hasTask("release") }
|
||||
sign publishing.publications.maven
|
||||
|
||||
if (project.hasProperty("signingKeyId") || project.hasProperty("signingKey")) {
|
||||
def signingKeyId = project.findProperty("signingKeyId")
|
||||
def signingKey = project.findProperty("signingKey")
|
||||
def signingPassword = project.findProperty("signingPassword")
|
||||
if (signingKeyId) {
|
||||
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
|
||||
} else if (signingKey) {
|
||||
useInMemoryPgpKeys(signingKey, signingPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.plugins.withType(MavenPublishPlugin).all {
|
||||
PublishingExtension publishing = project.extensions.getByType(PublishingExtension)
|
||||
publishing.publications.withType(MavenPublication).all { mavenPublication ->
|
||||
mavenPublication.pom {
|
||||
name = "${project.name}"
|
||||
description = 'SSHv2 library for Java'
|
||||
inceptionYear = '2009'
|
||||
url = "https://github.com/hierynomus/${project.name}"
|
||||
licenses {
|
||||
license {
|
||||
name = "The Apache License, Version 2.0"
|
||||
url = "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "hierynomus"
|
||||
name = "Jeroen van Erp"
|
||||
email = "jeroen@hierynomus.com"
|
||||
}
|
||||
developer {
|
||||
id = "shikhar"
|
||||
name = "Shikhar Bhushan"
|
||||
email = "shikhar@schmizz.net"
|
||||
url = "http://schmizz.net"
|
||||
roles = ["Previous Lead developer"]
|
||||
}
|
||||
developer {
|
||||
id = "iterate"
|
||||
name = "David Kocher"
|
||||
email = "dkocher@iterate.ch"
|
||||
organization = "iterate GmbH"
|
||||
organizationUrl = "https://iterate.ch"
|
||||
roles = ["Developer"]
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/hierynomus/${project.name}"
|
||||
connection = "scm:git@github.com:hierynomus/${project.name}.git"
|
||||
developerConnection = "scm:git@github.com:hierynomus/${project.name}.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype() //sonatypeUsername and sonatypePassword properties are used automatically
|
||||
}
|
||||
|
||||
connectTimeout = Duration.ofMinutes(3)
|
||||
clientTimeout = Duration.ofMinutes(3)
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
@@ -295,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)
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
buffer.flush(false);
|
||||
// trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
|
||||
} finally {
|
||||
closed = true;
|
||||
}
|
||||
if (!closed.getAndSet(true) && chan.isOpen()) {
|
||||
buffer.flush(false);
|
||||
trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
src/main/java/net/schmizz/sshj/sftp/RenameFlags.java
Normal file
32
src/main/java/net/schmizz/sshj/sftp/RenameFlags.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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(),
|
||||
other.getClient2ServerCipherAlgorithms()),
|
||||
firstMatch("Server2ClientCipherAlgorithms",
|
||||
this.getServer2ClientCipherAlgorithms(),
|
||||
other.getServer2ClientCipherAlgorithms()),
|
||||
firstMatch("Client2ServerMACAlgorithms",
|
||||
this.getClient2ServerMACAlgorithms(),
|
||||
other.getClient2ServerMACAlgorithms()),
|
||||
firstMatch("Server2ClientMACAlgorithms",
|
||||
this.getServer2ClientMACAlgorithms(),
|
||||
other.getServer2ClientMACAlgorithms()),
|
||||
firstMatch("Client2ServerCompressionAlgorithms",
|
||||
this.getClient2ServerCompressionAlgorithms(),
|
||||
other.getClient2ServerCompressionAlgorithms()),
|
||||
firstMatch("Server2ClientCompressionAlgorithms",
|
||||
this.getServer2ClientCompressionAlgorithms(),
|
||||
other.getServer2ClientCompressionAlgorithms()),
|
||||
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS)
|
||||
);
|
||||
firstMatch("KeyExchangeAlgorithms", this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
|
||||
firstMatch("HostKeyAlgorithms", this.getHostKeyAlgorithms(), other.getHostKeyAlgorithms()),
|
||||
firstMatch("Client2ServerCipherAlgorithms", this.getClient2ServerCipherAlgorithms(),
|
||||
other.getClient2ServerCipherAlgorithms()),
|
||||
firstMatch("Server2ClientCipherAlgorithms", this.getServer2ClientCipherAlgorithms(),
|
||||
other.getServer2ClientCipherAlgorithms()),
|
||||
firstMatch("Client2ServerMACAlgorithms", this.getClient2ServerMACAlgorithms(),
|
||||
other.getClient2ServerMACAlgorithms()),
|
||||
firstMatch("Server2ClientMACAlgorithms", this.getServer2ClientMACAlgorithms(),
|
||||
other.getServer2ClientMACAlgorithms()),
|
||||
firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
|
||||
other.getClient2ServerCompressionAlgorithms()),
|
||||
firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
|
||||
other.getServer2ClientCompressionAlgorithms()),
|
||||
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS));
|
||||
}
|
||||
|
||||
private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
|
||||
if (knownHostKeyAlgorithms != null && !knownHostKeyAlgorithms.isEmpty()) {
|
||||
List<String> preferredAlgorithms = new ArrayList<String>();
|
||||
List<String> otherAlgorithms = new ArrayList<String>();
|
||||
|
||||
for (String configuredKeyAlgorithm : configuredKeyAlgorithms) {
|
||||
if (knownHostKeyAlgorithms.contains(configuredKeyAlgorithm)) {
|
||||
preferredAlgorithms.add(configuredKeyAlgorithm);
|
||||
} else {
|
||||
otherAlgorithms.add(configuredKeyAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
preferredAlgorithms.addAll(otherAlgorithms);
|
||||
|
||||
return preferredAlgorithms;
|
||||
} else {
|
||||
return configuredKeyAlgorithms;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String firstMatch(String ofWhat, List<String> a, List<String> b)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,8 +127,13 @@ 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 + "'}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
|
||||
if (KeyType.RSA.equals(this.getType())) {
|
||||
final KeyType keyType = this.getType();
|
||||
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
|
||||
if (KeyType.RSA.equals(keyType)) {
|
||||
// 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,77 +269,158 @@ 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 {
|
||||
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 {
|
||||
// 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());
|
||||
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
|
||||
digest.update(encodedPassphrase);
|
||||
Arrays.fill(encodedPassphrase, (byte) 0);
|
||||
}
|
||||
final byte[] key = digest.digest();
|
||||
|
||||
final Mac mac = Mac.getInstance("HmacSHA1");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
final DataOutputStream data = new DataOutputStream(out);
|
||||
// name of algorithm
|
||||
data.writeInt(this.getType().toString().length());
|
||||
data.writeBytes(this.getType().toString());
|
||||
|
||||
data.writeInt(headers.get("Encryption").length());
|
||||
data.writeBytes(headers.get("Encryption"));
|
||||
|
||||
data.writeInt(headers.get("Comment").length());
|
||||
data.writeBytes(headers.get("Comment"));
|
||||
|
||||
data.writeInt(publicKey.length);
|
||||
data.write(publicKey);
|
||||
|
||||
data.writeInt(privateKey.length);
|
||||
data.write(privateKey);
|
||||
|
||||
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
|
||||
final String reference = headers.get("Private-MAC");
|
||||
if (!encoded.equals(reference)) {
|
||||
throw new IOException("Invalid passphrase");
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -341,21 +440,63 @@ public class PuTTYKeyFileTest {
|
||||
assertEquals(key.getPublic(), referenceKey.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV3KeyArgon2id() throws Exception {
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new StringReader(v3_ecdsa));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRSAv3EncryptedKeyArgon2id() throws Exception {
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new StringReader(v3_rsa_argon2id), new UnitTestPasswordFinder("changeit"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
|
||||
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
|
||||
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
|
||||
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
|
||||
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
|
||||
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRSAv3EncryptedKeyArgon2d() throws Exception {
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new StringReader(v3_rsa_argon2d), new UnitTestPasswordFinder("changeit"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
|
||||
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
|
||||
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
|
||||
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
|
||||
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
|
||||
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRSAv3EncryptedKeyArgon2i() throws Exception {
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new StringReader(v3_rsa_argon2i), new UnitTestPasswordFinder("changeit"));
|
||||
assertNotNull(key.getPrivate());
|
||||
assertNotNull(key.getPublic());
|
||||
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
|
||||
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
|
||||
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
|
||||
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
|
||||
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
|
||||
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectPassphraseRsa() throws Exception {
|
||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() {
|
||||
@Override
|
||||
public char[] reqPassword(Resource<?> resource) {
|
||||
// correct passphrase
|
||||
return "123456".toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry(Resource<?> resource) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
30
src/test/resources/keyformats/pkcs8-rsa-2048-encrypted
Normal file
30
src/test/resources/keyformats/pkcs8-rsa-2048-encrypted
Normal 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-----
|
||||
30
src/test/resources/keytypes/test_rsa_putty_priv.openssh2
Normal file
30
src/test/resources/keytypes/test_rsa_putty_priv.openssh2
Normal 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-----
|
||||
Reference in New Issue
Block a user