mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-09 00:18:39 +03:00
Merge pull request #136 from dkocher/master
HMAC-SHA2, PuTTY key format, concurrent read for downloads and bug fixes
This commit is contained in:
86
pom.xml
86
pom.xml
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@@ -10,21 +11,20 @@
|
|||||||
|
|
||||||
<name>sshj</name>
|
<name>sshj</name>
|
||||||
<description>SSHv2 library for Java</description>
|
<description>SSHv2 library for Java</description>
|
||||||
<url>http://github.com/shikhar/sshj</url>
|
<url>http://github.com/dkocher/sshj</url>
|
||||||
|
|
||||||
<inceptionYear>2009</inceptionYear>
|
<inceptionYear>2009</inceptionYear>
|
||||||
|
|
||||||
<issueManagement>
|
<issueManagement>
|
||||||
<system>github</system>
|
<system>github</system>
|
||||||
<url>http://github.com/shikhar/sshj/issues</url>
|
<url>http://github.com/dkocher/sshj/issues</url>
|
||||||
</issueManagement>
|
</issueManagement>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:git://github.com/shikhar/sshj.git</connection>
|
<connection>scm:git:git://github.com/dkocher/sshj.git</connection>
|
||||||
<developerConnection>scm:git:git@github.com:shikhar/sshj.git</developerConnection>
|
<developerConnection>scm:git:git@github.com:dkocher/sshj.git</developerConnection>
|
||||||
<url>http://github.com/shikhar/sshj</url>
|
<url>http://github.com/dkocher/sshj</url>
|
||||||
<tag>HEAD</tag>
|
</scm>
|
||||||
</scm>
|
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
@@ -40,39 +40,65 @@
|
|||||||
<version>7</version>
|
<version>7</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<version>1.50</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
<version>1.50</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jzlib</artifactId>
|
||||||
|
<version>1.1.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-core</artifactId>
|
||||||
|
<version>1.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>1.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
<version>1.7.5</version>
|
<version>1.7.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcpkix-jdk15on</artifactId>
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
<version>1.49</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
<version>1.49</version>
|
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.jcraft</groupId>
|
<groupId>com.jcraft</groupId>
|
||||||
<artifactId>jzlib</artifactId>
|
<artifactId>jzlib</artifactId>
|
||||||
<version>1.1.2</version>
|
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.sshd</groupId>
|
<groupId>org.apache.sshd</groupId>
|
||||||
<artifactId>sshd-core</artifactId>
|
<artifactId>sshd-core</artifactId>
|
||||||
<version>0.8.0</version>
|
<version>0.11.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-core</artifactId>
|
<artifactId>logback-core</artifactId>
|
||||||
<version>1.0.13</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -84,7 +110,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.0.13</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -103,6 +128,13 @@
|
|||||||
<email>shikhar@schmizz.net</email>
|
<email>shikhar@schmizz.net</email>
|
||||||
<url>http://schmizz.net</url>
|
<url>http://schmizz.net</url>
|
||||||
</developer>
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>iterate</id>
|
||||||
|
<name>David Kocher</name>
|
||||||
|
<email>dkocher@iterate.ch</email>
|
||||||
|
<organization>iterate GmbH</organization>
|
||||||
|
<organizationUrl>https://iterate.ch</organizationUrl>
|
||||||
|
</developer>
|
||||||
</developers>
|
</developers>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -126,7 +158,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-release-plugin</artifactId>
|
<artifactId>maven-release-plugin</artifactId>
|
||||||
<version>2.4.1</version>
|
<version>2.5</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mavenExecutorId>forked-path</mavenExecutorId>
|
<mavenExecutorId>forked-path</mavenExecutorId>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -134,7 +166,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
<version>2.1.2</version>
|
<version>2.2.1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>attach-sources</id>
|
<id>attach-sources</id>
|
||||||
@@ -179,6 +211,11 @@
|
|||||||
</instructions>
|
</instructions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>clirr-maven-plugin</artifactId>
|
||||||
|
<version>2.6.1</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
@@ -202,22 +239,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
<version>1.49</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.jcraft</groupId>
|
<groupId>com.jcraft</groupId>
|
||||||
<artifactId>jzlib</artifactId>
|
<artifactId>jzlib</artifactId>
|
||||||
<version>1.0.7</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-core</artifactId>
|
<artifactId>logback-core</artifactId>
|
||||||
<version>1.0.13</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.0.13</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</profile>
|
</profile>
|
||||||
@@ -251,7 +284,14 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
</profiles>
|
</profiles>
|
||||||
|
<reporting>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>clirr-maven-plugin</artifactId>
|
||||||
|
<version>2.6.1</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</reporting>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ package net.schmizz.sshj;
|
|||||||
import net.schmizz.sshj.common.Factory;
|
import net.schmizz.sshj.common.Factory;
|
||||||
import net.schmizz.sshj.common.SecurityUtils;
|
import net.schmizz.sshj.common.SecurityUtils;
|
||||||
import net.schmizz.sshj.signature.SignatureDSA;
|
import net.schmizz.sshj.signature.SignatureDSA;
|
||||||
|
import net.schmizz.sshj.signature.SignatureECDSA;
|
||||||
import net.schmizz.sshj.signature.SignatureRSA;
|
import net.schmizz.sshj.signature.SignatureRSA;
|
||||||
import net.schmizz.sshj.transport.cipher.AES128CBC;
|
import net.schmizz.sshj.transport.cipher.AES128CBC;
|
||||||
import net.schmizz.sshj.transport.cipher.AES128CTR;
|
import net.schmizz.sshj.transport.cipher.AES128CTR;
|
||||||
@@ -56,11 +57,15 @@ import net.schmizz.sshj.transport.mac.HMACMD5;
|
|||||||
import net.schmizz.sshj.transport.mac.HMACMD596;
|
import net.schmizz.sshj.transport.mac.HMACMD596;
|
||||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||||
import net.schmizz.sshj.transport.mac.HMACSHA196;
|
import net.schmizz.sshj.transport.mac.HMACSHA196;
|
||||||
|
import net.schmizz.sshj.transport.mac.HMACSHA2256;
|
||||||
|
import net.schmizz.sshj.transport.mac.HMACSHA2512;
|
||||||
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
|
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
|
||||||
import net.schmizz.sshj.transport.random.JCERandom;
|
import net.schmizz.sshj.transport.random.JCERandom;
|
||||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||||
|
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -70,22 +75,22 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
|
* A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
|
||||||
* BouncyCastle is in the classpath.
|
* BouncyCastle is in the classpath.
|
||||||
* <p/>
|
* <p/>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link ConfigImpl#setKeyExchangeFactories Key exchange}: {@link DHG14}*, {@link DHG1}</li>
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setKeyExchangeFactories Key exchange}: {@link net.schmizz.sshj.transport.kex.DHG14}*, {@link net.schmizz.sshj.transport.kex.DHG1}</li>
|
||||||
* <li>{@link ConfigImpl#setCipherFactories Ciphers} [1]: {@link AES128CTR}, {@link AES192CTR}, {@link AES256CTR},
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setCipherFactories Ciphers} [1]: {@link net.schmizz.sshj.transport.cipher.AES128CTR}, {@link net.schmizz.sshj.transport.cipher.AES192CTR}, {@link net.schmizz.sshj.transport.cipher.AES256CTR},
|
||||||
* {@link
|
* {@link
|
||||||
* AES128CBC}, {@link AES192CBC}, {@link AES256CBC}, {@link AES192CBC}, {@link TripleDESCBC}, {@link BlowfishCBC}</li>
|
* net.schmizz.sshj.transport.cipher.AES128CBC}, {@link net.schmizz.sshj.transport.cipher.AES192CBC}, {@link net.schmizz.sshj.transport.cipher.AES256CBC}, {@link net.schmizz.sshj.transport.cipher.AES192CBC}, {@link net.schmizz.sshj.transport.cipher.TripleDESCBC}, {@link net.schmizz.sshj.transport.cipher.BlowfishCBC}</li>
|
||||||
* <li>{@link ConfigImpl#setMACFactories MAC}: {@link HMACSHA1}, {@link HMACSHA196}, {@link HMACMD5}, {@link
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setMACFactories MAC}: {@link net.schmizz.sshj.transport.mac.HMACSHA1}, {@link net.schmizz.sshj.transport.mac.HMACSHA196}, {@link net.schmizz.sshj.transport.mac.HMACMD5}, {@link
|
||||||
* HMACMD596}</li>
|
* net.schmizz.sshj.transport.mac.HMACMD596}</li>
|
||||||
* <li>{@link ConfigImpl#setCompressionFactories Compression}: {@link NoneCompression}</li>
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setCompressionFactories Compression}: {@link net.schmizz.sshj.transport.compression.NoneCompression}</li>
|
||||||
* <li>{@link ConfigImpl#setSignatureFactories Signature}: {@link SignatureRSA}, {@link SignatureDSA}</li>
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setSignatureFactories Signature}: {@link net.schmizz.sshj.signature.SignatureRSA}, {@link net.schmizz.sshj.signature.SignatureDSA}</li>
|
||||||
* <li>{@link ConfigImpl#setRandomFactory PRNG}: {@link BouncyCastleRandom}* or {@link JCERandom}</li>
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setRandomFactory PRNG}: {@link net.schmizz.sshj.transport.random.BouncyCastleRandom}* or {@link net.schmizz.sshj.transport.random.JCERandom}</li>
|
||||||
* <li>{@link ConfigImpl#setFileKeyProviderFactories Key file support}: {@link PKCS8KeyFile}*, {@link
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setFileKeyProviderFactories Key file support}: {@link net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile}*, {@link
|
||||||
* OpenSSHKeyFile}*</li>
|
* net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile}*</li>
|
||||||
* <li>{@link ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li>
|
* <li>{@link net.schmizz.sshj.ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p/>
|
* <p/>
|
||||||
* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This
|
* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This
|
||||||
@@ -96,7 +101,7 @@ public class DefaultConfig
|
|||||||
|
|
||||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
private static final String VERSION = "SSHJ_0_9_1";
|
private static final String VERSION = "SSHJ_0_9_2";
|
||||||
|
|
||||||
public DefaultConfig() {
|
public DefaultConfig() {
|
||||||
setVersion(VERSION);
|
setVersion(VERSION);
|
||||||
@@ -124,7 +129,7 @@ public class DefaultConfig
|
|||||||
|
|
||||||
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
|
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
|
||||||
if (bouncyCastleRegistered) {
|
if (bouncyCastleRegistered) {
|
||||||
setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory());
|
setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +167,12 @@ public class DefaultConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void initSignatureFactories() {
|
protected void initSignatureFactories() {
|
||||||
setSignatureFactories(new SignatureRSA.Factory(), new SignatureDSA.Factory());
|
setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initMACFactories() {
|
protected void initMACFactories() {
|
||||||
setMACFactories(new HMACSHA1.Factory(), new HMACSHA196.Factory(), new HMACMD5.Factory(),
|
setMACFactories(new HMACSHA1.Factory(), new HMACSHA196.Factory(), new HMACMD5.Factory(),
|
||||||
new HMACMD596.Factory());
|
new HMACMD596.Factory(), new HMACSHA2256.Factory(), new HMACSHA2512.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initCompressionFactories() {
|
protected void initCompressionFactories() {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import net.schmizz.sshj.userauth.UserAuth;
|
|||||||
import net.schmizz.sshj.userauth.UserAuthException;
|
import net.schmizz.sshj.userauth.UserAuthException;
|
||||||
import net.schmizz.sshj.userauth.UserAuthImpl;
|
import net.schmizz.sshj.userauth.UserAuthImpl;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||||
|
import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||||
@@ -485,7 +486,7 @@ public class SSHClient
|
|||||||
public KeyProvider loadKeys(String location, PasswordFinder passwordFinder)
|
public KeyProvider loadKeys(String location, PasswordFinder passwordFinder)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final File loc = new File(location);
|
final File loc = new File(location);
|
||||||
final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(loc);
|
final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(loc);
|
||||||
final FileKeyProvider fkp =
|
final FileKeyProvider fkp =
|
||||||
Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString());
|
Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString());
|
||||||
if (fkp == null)
|
if (fkp == null)
|
||||||
@@ -529,7 +530,7 @@ public class SSHClient
|
|||||||
*/
|
*/
|
||||||
public KeyProvider loadKeys(String privateKey, String publicKey, PasswordFinder passwordFinder)
|
public KeyProvider loadKeys(String privateKey, String publicKey, PasswordFinder passwordFinder)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null);
|
final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null);
|
||||||
final FileKeyProvider fkp =
|
final FileKeyProvider fkp =
|
||||||
Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString());
|
Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString());
|
||||||
if (fkp == null)
|
if (fkp == null)
|
||||||
|
|||||||
@@ -15,21 +15,25 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.common;
|
package net.schmizz.sshj.common;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||||
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
|
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||||
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.*;
|
||||||
import java.security.Key;
|
import java.security.interfaces.*;
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.interfaces.DSAPrivateKey;
|
|
||||||
import java.security.interfaces.DSAPublicKey;
|
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
|
||||||
import java.security.spec.DSAPublicKeySpec;
|
import java.security.spec.DSAPublicKeySpec;
|
||||||
import java.security.spec.RSAPublicKeySpec;
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/** Type of key e.g. rsa, dsa */
|
/** Type of key e.g. rsa, dsa */
|
||||||
public enum KeyType {
|
public enum KeyType {
|
||||||
|
|
||||||
|
|
||||||
/** SSH identifier for RSA keys */
|
/** SSH identifier for RSA keys */
|
||||||
RSA("ssh-rsa") {
|
RSA("ssh-rsa") {
|
||||||
@Override
|
@Override
|
||||||
@@ -96,6 +100,89 @@ public enum KeyType {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** SSH identifier for ECDSA keys */
|
||||||
|
ECDSA("ecdsa-sha2-nistp256") {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
try {
|
||||||
|
// final String algo = buf.readString(); it has been already read
|
||||||
|
final String curveName = buf.readString();
|
||||||
|
final int keyLen = buf.readUInt32AsInt();
|
||||||
|
final byte x04 = buf.readByte(); // it must be 0x04, but don't think we need that check
|
||||||
|
final byte[] x = new byte[(keyLen - 1) / 2];
|
||||||
|
final byte[] y = new byte[(keyLen - 1) / 2];
|
||||||
|
buf.readRawBytes(x);
|
||||||
|
buf.readRawBytes(y);
|
||||||
|
if(log.isDebugEnabled()) {
|
||||||
|
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
|
||||||
|
type,
|
||||||
|
curveName,
|
||||||
|
keyLen,
|
||||||
|
x04,
|
||||||
|
Arrays.toString(x),
|
||||||
|
Arrays.toString(y))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NISTP_CURVE.equals(curveName)) {
|
||||||
|
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger bigX = new BigInteger(1, x);
|
||||||
|
BigInteger bigY = new BigInteger(1, y);
|
||||||
|
|
||||||
|
X9ECParameters ecParams = NISTNamedCurves.getByName("p-256");
|
||||||
|
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY, false);
|
||||||
|
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(),
|
||||||
|
ecParams.getG(), ecParams.getN());
|
||||||
|
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
|
||||||
|
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
|
||||||
|
return keyFactory.generatePublic(publicSpec);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new GeneralSecurityException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||||
|
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
||||||
|
final java.security.spec.ECPoint point = ecdsa.getW();
|
||||||
|
final byte[] x = trimStartingZeros(point.getAffineX().toByteArray());
|
||||||
|
final byte[] y = trimStartingZeros(point.getAffineY().toByteArray());
|
||||||
|
|
||||||
|
buf.putString(sType)
|
||||||
|
.putString(NISTP_CURVE)
|
||||||
|
.putUInt32(1 + x.length + y.length)
|
||||||
|
.putRawBytes(new byte[] { (byte) 0x04 })
|
||||||
|
.putRawBytes(x)
|
||||||
|
.putRawBytes(y)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isMyType(Key key) {
|
||||||
|
return ("ECDSA".equals(key.getAlgorithm()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] trimStartingZeros(byte[] in) {
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (; i < in.length; i++) {
|
||||||
|
if (in[i] != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final byte[] out = new byte[in.length - i];
|
||||||
|
System.arraycopy(in, i, out, 0, out.length);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/** Unrecognized */
|
/** Unrecognized */
|
||||||
UNKNOWN("unknown") {
|
UNKNOWN("unknown") {
|
||||||
@Override
|
@Override
|
||||||
@@ -113,10 +200,11 @@ public enum KeyType {
|
|||||||
protected boolean isMyType(Key key) {
|
protected boolean isMyType(Key key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static final String NISTP_CURVE = "nistp256";
|
||||||
|
|
||||||
protected final String sType;
|
protected final String sType;
|
||||||
|
|
||||||
private KeyType(String type) {
|
private KeyType(String type) {
|
||||||
@@ -149,4 +237,4 @@ public enum KeyType {
|
|||||||
return sType;
|
return sType;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
package net.schmizz.sshj.sftp;
|
package net.schmizz.sshj.sftp;
|
||||||
|
|
||||||
import net.schmizz.concurrent.Promise;
|
import net.schmizz.concurrent.Promise;
|
||||||
|
import net.schmizz.sshj.common.SSHException;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ public class PacketReader
|
|||||||
| lenBuf[3] & 0x000000ffL);
|
| lenBuf[3] & 0x000000ffL);
|
||||||
|
|
||||||
if (len > SFTPPacket.MAX_SIZE) {
|
if (len > SFTPPacket.MAX_SIZE) {
|
||||||
throw new IllegalStateException("Invalid packet: indicated length "+len+" too large");
|
throw new SSHException(String.format("Indicated packet length %d too large", len));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) len;
|
return (int) len;
|
||||||
|
|||||||
@@ -15,9 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.sftp;
|
package net.schmizz.sshj.sftp;
|
||||||
|
|
||||||
import net.schmizz.concurrent.Promise;
|
|
||||||
import net.schmizz.sshj.sftp.Response.StatusCode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -25,6 +22,10 @@ import java.util.LinkedList;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.schmizz.concurrent.Promise;
|
||||||
|
import net.schmizz.sshj.common.Buffer;
|
||||||
|
import net.schmizz.sshj.sftp.Response.StatusCode;
|
||||||
|
|
||||||
public class RemoteFile
|
public class RemoteFile
|
||||||
extends RemoteResource {
|
extends RemoteResource {
|
||||||
|
|
||||||
@@ -52,10 +53,17 @@ public class RemoteFile
|
|||||||
|
|
||||||
public int read(long fileOffset, byte[] to, int offset, int len)
|
public int read(long fileOffset, byte[] to, int offset, int len)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final Response res = requester.request(
|
final Response res = this.asyncRead(fileOffset, len).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||||
newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len)
|
return this.checkReadResponse(res, to, offset);
|
||||||
).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
}
|
||||||
switch (res.getType()) {
|
|
||||||
|
protected Promise<Response, SFTPException> asyncRead(long fileOffset, int len)
|
||||||
|
throws IOException {
|
||||||
|
return requester.request(newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int checkReadResponse(Response res, byte[] to, int offset) throws Buffer.BufferException, SFTPException {
|
||||||
|
switch(res.getType()) {
|
||||||
case DATA:
|
case DATA:
|
||||||
int recvLen = res.readUInt32AsInt();
|
int recvLen = res.readUInt32AsInt();
|
||||||
System.arraycopy(res.array(), res.rpos(), to, offset, recvLen);
|
System.arraycopy(res.array(), res.rpos(), to, offset, recvLen);
|
||||||
@@ -72,19 +80,19 @@ public class RemoteFile
|
|||||||
|
|
||||||
public void write(long fileOffset, byte[] data, int off, int len)
|
public void write(long fileOffset, byte[] data, int off, int len)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
checkResponse(asyncWrite(fileOffset, data, off, len));
|
checkWriteResponse(asyncWrite(fileOffset, data, off, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Promise<Response, SFTPException> asyncWrite(long fileOffset, byte[] data, int off, int len)
|
protected Promise<Response, SFTPException> asyncWrite(long fileOffset, byte[] data, int off, int len)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return requester.request(newRequest(PacketType.WRITE)
|
return requester.request(newRequest(PacketType.WRITE)
|
||||||
.putUInt64(fileOffset)
|
.putUInt64(fileOffset)
|
||||||
.putUInt32(len - off)
|
.putUInt32(len - off)
|
||||||
.putRawBytes(data, off, len)
|
.putRawBytes(data, off, len)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkResponse(Promise<Response, SFTPException> responsePromise)
|
private void checkWriteResponse(Promise<Response, SFTPException> responsePromise)
|
||||||
throws SFTPException {
|
throws SFTPException {
|
||||||
responsePromise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
|
responsePromise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
|
||||||
}
|
}
|
||||||
@@ -139,8 +147,8 @@ public class RemoteFile
|
|||||||
@Override
|
@Override
|
||||||
public void write(byte[] buf, int off, int len)
|
public void write(byte[] buf, int off, int len)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (unconfirmedWrites.size() > maxUnconfirmedWrites) {
|
if(unconfirmedWrites.size() > maxUnconfirmedWrites) {
|
||||||
checkResponse(unconfirmedWrites.remove());
|
checkWriteResponse(unconfirmedWrites.remove());
|
||||||
}
|
}
|
||||||
unconfirmedWrites.add(RemoteFile.this.asyncWrite(fileOffset, buf, off, len));
|
unconfirmedWrites.add(RemoteFile.this.asyncWrite(fileOffset, buf, off, len));
|
||||||
fileOffset += len;
|
fileOffset += len;
|
||||||
@@ -149,8 +157,8 @@ public class RemoteFile
|
|||||||
@Override
|
@Override
|
||||||
public void flush()
|
public void flush()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
while (!unconfirmedWrites.isEmpty()) {
|
while(!unconfirmedWrites.isEmpty()) {
|
||||||
checkResponse(unconfirmedWrites.remove());
|
checkWriteResponse(unconfirmedWrites.remove());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +166,8 @@ public class RemoteFile
|
|||||||
public void close()
|
public void close()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
flush();
|
flush();
|
||||||
|
// Close handle
|
||||||
|
RemoteFile.this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -167,6 +177,11 @@ public class RemoteFile
|
|||||||
|
|
||||||
private final byte[] b = new byte[1];
|
private final byte[] b = new byte[1];
|
||||||
|
|
||||||
|
private final int maxUnconfirmedReads;
|
||||||
|
private final Queue<Promise<Response, SFTPException>> unconfirmedReads;
|
||||||
|
|
||||||
|
private boolean eof;
|
||||||
|
|
||||||
private long fileOffset;
|
private long fileOffset;
|
||||||
private long markPos;
|
private long markPos;
|
||||||
private long readLimit;
|
private long readLimit;
|
||||||
@@ -175,8 +190,14 @@ public class RemoteFile
|
|||||||
this(0);
|
this(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteFileInputStream(int fileOffset) {
|
public RemoteFileInputStream(long fileOffset) {
|
||||||
|
this(fileOffset, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteFileInputStream(long fileOffset, int maxUnconfirmedReads) {
|
||||||
this.fileOffset = fileOffset;
|
this.fileOffset = fileOffset;
|
||||||
|
this.maxUnconfirmedReads = maxUnconfirmedReads;
|
||||||
|
this.unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -187,7 +208,7 @@ public class RemoteFile
|
|||||||
@Override
|
@Override
|
||||||
public void mark(int readLimit) {
|
public void mark(int readLimit) {
|
||||||
this.readLimit = readLimit;
|
this.readLimit = readLimit;
|
||||||
markPos = fileOffset;
|
this.markPos = fileOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -211,14 +232,47 @@ public class RemoteFile
|
|||||||
@Override
|
@Override
|
||||||
public int read(byte[] into, int off, int len)
|
public int read(byte[] into, int off, int len)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int read = RemoteFile.this.read(fileOffset, into, off, len);
|
while(!eof && unconfirmedReads.size() <= maxUnconfirmedReads) {
|
||||||
if (read != -1) {
|
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
|
||||||
fileOffset += read;
|
unconfirmedReads.add(asyncRead(fileOffset, len));
|
||||||
if (markPos != 0 && read > readLimit) // Invalidate mark position
|
fileOffset += len;
|
||||||
markPos = 0;
|
|
||||||
}
|
}
|
||||||
return read;
|
if(unconfirmedReads.isEmpty()) {
|
||||||
|
// Attempted to read while status was already received
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Retrieve first in
|
||||||
|
final Response res = unconfirmedReads.remove().retrieve(
|
||||||
|
requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||||
|
final int recvLen = checkReadResponse(res, into, off);
|
||||||
|
if(markPos != 0 && recvLen > readLimit) // Invalidate mark position
|
||||||
|
{
|
||||||
|
markPos = 0;
|
||||||
|
}
|
||||||
|
if(-1 == recvLen) {
|
||||||
|
eof = true;
|
||||||
|
}
|
||||||
|
return recvLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
while(!unconfirmedReads.isEmpty()) {
|
||||||
|
final Response res = unconfirmedReads.remove().retrieve(
|
||||||
|
requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||||
|
switch(res.getType()) {
|
||||||
|
case STATUS:
|
||||||
|
res.ensureStatusIs(StatusCode.EOF);
|
||||||
|
break;
|
||||||
|
case DATA:
|
||||||
|
log.warn("Pending data packet from read response discarded");
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
throw new SFTPException("Unexpected packet: " + res.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Close handle
|
||||||
|
RemoteFile.this.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ public class SFTPEngine
|
|||||||
public void close()
|
public void close()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
sub.close();
|
sub.close();
|
||||||
|
reader.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected FileAttributes stat(PacketType pt, String path)
|
protected FileAttributes stat(PacketType pt, String path)
|
||||||
|
|||||||
@@ -81,6 +81,15 @@ public abstract class AbstractSignature
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] sign() {
|
||||||
|
try {
|
||||||
|
return signature.sign();
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new SSHRuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected byte[] extractSig(byte[] sig) {
|
protected byte[] extractSig(byte[] sig) {
|
||||||
if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0) {
|
if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|||||||
@@ -74,6 +74,13 @@ public interface Signature {
|
|||||||
*/
|
*/
|
||||||
byte[] sign();
|
byte[] sign();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the signature as blog
|
||||||
|
* @param signature the signature to encode
|
||||||
|
* @return Encoded signature
|
||||||
|
*/
|
||||||
|
byte[] encode(byte[] signature);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify against the given signature.
|
* Verify against the given signature.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.signature;
|
package net.schmizz.sshj.signature;
|
||||||
|
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
import net.schmizz.sshj.common.KeyType;
|
import net.schmizz.sshj.common.KeyType;
|
||||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||||
|
|
||||||
import java.security.SignatureException;
|
|
||||||
|
|
||||||
/** DSA {@link Signature} */
|
/** DSA {@link Signature} */
|
||||||
public class SignatureDSA
|
public class SignatureDSA
|
||||||
extends AbstractSignature {
|
extends AbstractSignature {
|
||||||
@@ -65,14 +65,7 @@ public class SignatureDSA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign() {
|
public byte[] encode(byte[] sig) {
|
||||||
byte[] sig;
|
|
||||||
try {
|
|
||||||
sig = signature.sign();
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
throw new SSHRuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sig is in ASN.1
|
// sig is in ASN.1
|
||||||
// SEQUENCE::={ r INTEGER, s INTEGER }
|
// SEQUENCE::={ r INTEGER, s INTEGER }
|
||||||
|
|
||||||
@@ -90,17 +83,11 @@ public class SignatureDSA
|
|||||||
|
|
||||||
// result must be 40 bytes, but length of r and s may not be 20 bytes
|
// result must be 40 bytes, but length of r and s may not be 20 bytes
|
||||||
|
|
||||||
System.arraycopy(r,
|
int r_copylen = (r.length < 20) ? r.length : 20;
|
||||||
r.length > 20 ? 1 : 0,
|
int s_copylen = (s.length < 20) ? s.length : 20;
|
||||||
result,
|
|
||||||
r.length > 20 ? 0 : 20 - r.length,
|
|
||||||
r.length > 20 ? 20 : r.length);
|
|
||||||
|
|
||||||
System.arraycopy(s,
|
System.arraycopy(r, r.length - r_copylen, result, 20 - r_copylen, r_copylen);
|
||||||
s.length > 20 ? 1 : 0,
|
System.arraycopy(s, s.length - s_copylen, result, 40 - s_copylen, s_copylen);
|
||||||
result,
|
|
||||||
s.length > 20 ? 20 : 40 - s.length,
|
|
||||||
s.length > 20 ? 20 : s.length);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
157
src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java
Normal file
157
src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2012 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.
|
||||||
|
*
|
||||||
|
* This file may incorporate work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.math.BigInteger;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.Buffer;
|
||||||
|
import net.schmizz.sshj.common.KeyType;
|
||||||
|
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||||
|
|
||||||
|
/** ECDSA {@link Signature} */
|
||||||
|
public class SignatureECDSA
|
||||||
|
extends AbstractSignature {
|
||||||
|
|
||||||
|
/** A named factory for ECDSA signature */
|
||||||
|
public static class Factory
|
||||||
|
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Signature create() {
|
||||||
|
return new SignatureECDSA();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return KeyType.ECDSA.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureECDSA() {
|
||||||
|
super("SHA256withECDSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encode(byte[] sig) {
|
||||||
|
int rIndex = 3;
|
||||||
|
int rLen = sig[rIndex++] & 0xff;
|
||||||
|
byte[] r = new byte[rLen];
|
||||||
|
System.arraycopy(sig, rIndex, r, 0, r.length);
|
||||||
|
|
||||||
|
int sIndex = rIndex + rLen + 1;
|
||||||
|
int sLen = sig[sIndex++] & 0xff;
|
||||||
|
byte[] s = new byte[sLen];
|
||||||
|
System.arraycopy(sig, sIndex, s, 0, s.length);
|
||||||
|
|
||||||
|
System.arraycopy(sig, 4, r, 0, rLen);
|
||||||
|
System.arraycopy(sig, 6 + rLen, s, 0, sLen);
|
||||||
|
|
||||||
|
Buffer buf = new Buffer.PlainBuffer();
|
||||||
|
buf.putMPInt(new BigInteger(r));
|
||||||
|
buf.putMPInt(new BigInteger(s));
|
||||||
|
|
||||||
|
return buf.getCompactData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(byte[] sig) {
|
||||||
|
byte[] r;
|
||||||
|
byte[] s;
|
||||||
|
try {
|
||||||
|
Buffer sigbuf = new Buffer.PlainBuffer(sig);
|
||||||
|
final String algo = new String(sigbuf.readBytes());
|
||||||
|
if (!"ecdsa-sha2-nistp256".equals(algo)) {
|
||||||
|
throw new SSHRuntimeException(String.format("Signature :: ecdsa-sha2-nistp256 expected, got %s", algo));
|
||||||
|
}
|
||||||
|
final int rsLen = sigbuf.readUInt32AsInt();
|
||||||
|
if (!(sigbuf.available() == rsLen)) {
|
||||||
|
throw new SSHRuntimeException("Invalid key length");
|
||||||
|
}
|
||||||
|
r = sigbuf.readBytes();
|
||||||
|
s = sigbuf.readBytes();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SSHRuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rLen = r.length;
|
||||||
|
int sLen = s.length;
|
||||||
|
|
||||||
|
/* We can't have the high bit set, so add an extra zero at the beginning if so. */
|
||||||
|
if ((r[0] & 0x80) != 0) {
|
||||||
|
rLen++;
|
||||||
|
}
|
||||||
|
if ((s[0] & 0x80) != 0) {
|
||||||
|
sLen++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate total output length */
|
||||||
|
int length = 6 + rLen + sLen;
|
||||||
|
byte[] asn1 = new byte[length];
|
||||||
|
|
||||||
|
/* ASN.1 SEQUENCE tag */
|
||||||
|
asn1[0] = (byte) 0x30;
|
||||||
|
|
||||||
|
/* Size of SEQUENCE */
|
||||||
|
asn1[1] = (byte) (4 + rLen + sLen);
|
||||||
|
|
||||||
|
/* ASN.1 INTEGER tag */
|
||||||
|
asn1[2] = (byte) 0x02;
|
||||||
|
|
||||||
|
/* "r" INTEGER length */
|
||||||
|
asn1[3] = (byte) rLen;
|
||||||
|
|
||||||
|
/* Copy in the "r" INTEGER */
|
||||||
|
System.arraycopy(r, 0, asn1, 4, rLen);
|
||||||
|
|
||||||
|
/* ASN.1 INTEGER tag */
|
||||||
|
asn1[rLen + 4] = (byte) 0x02;
|
||||||
|
|
||||||
|
/* "s" INTEGER length */
|
||||||
|
asn1[rLen + 5] = (byte) sLen;
|
||||||
|
|
||||||
|
/* Copy in the "s" INTEGER */
|
||||||
|
System.arraycopy(s, 0, asn1, (6 + rLen), sLen);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
return signature.verify(asn1);
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new SSHRuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,11 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.signature;
|
package net.schmizz.sshj.signature;
|
||||||
|
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
import net.schmizz.sshj.common.KeyType;
|
import net.schmizz.sshj.common.KeyType;
|
||||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||||
|
|
||||||
import java.security.SignatureException;
|
|
||||||
|
|
||||||
/** RSA {@link Signature} */
|
/** RSA {@link Signature} */
|
||||||
public class SignatureRSA
|
public class SignatureRSA
|
||||||
extends AbstractSignature {
|
extends AbstractSignature {
|
||||||
@@ -65,12 +65,8 @@ public class SignatureRSA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign() {
|
public byte[] encode(byte[] signature) {
|
||||||
try {
|
return signature;
|
||||||
return signature.sign();
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
throw new SSHRuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ import net.schmizz.sshj.common.DisconnectReason;
|
|||||||
|
|
||||||
public interface DisconnectListener {
|
public interface DisconnectListener {
|
||||||
|
|
||||||
void notifyDisconnect(DisconnectReason reason);
|
void notifyDisconnect(DisconnectReason reason, String message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
|
||||||
public final class Reader
|
public final class Reader
|
||||||
extends Thread {
|
extends Thread {
|
||||||
@@ -65,13 +66,21 @@ public final class Reader
|
|||||||
int needed = 1;
|
int needed = 1;
|
||||||
|
|
||||||
while (!isInterrupted()) {
|
while (!isInterrupted()) {
|
||||||
int read = inp.read(recvbuf, 0, needed);
|
int read;
|
||||||
|
try {
|
||||||
|
read = inp.read(recvbuf, 0, needed);
|
||||||
|
}
|
||||||
|
catch(SocketTimeoutException e) {
|
||||||
|
if (isInterrupted()) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (read == -1)
|
if (read == -1)
|
||||||
throw new TransportException("Broken transport; encountered EOF");
|
throw new TransportException("Broken transport; encountered EOF");
|
||||||
else
|
else
|
||||||
needed = decoder.received(recvbuf, read);
|
needed = decoder.received(recvbuf, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (isInterrupted()) {
|
if (isInterrupted()) {
|
||||||
// We are meant to shut up and draw to a close if interrupted
|
// We are meant to shut up and draw to a close if interrupted
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public final class TransportImpl
|
|||||||
|
|
||||||
private final DisconnectListener nullDisconnectListener = new DisconnectListener() {
|
private final DisconnectListener nullDisconnectListener = new DisconnectListener() {
|
||||||
@Override
|
@Override
|
||||||
public void notifyDisconnect(DisconnectReason reason) {
|
public void notifyDisconnect(DisconnectReason reason, String message) {
|
||||||
log.info("Disconnected - {}", reason);
|
log.info("Disconnected - {}", reason);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -139,7 +139,7 @@ public final class TransportImpl
|
|||||||
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock);
|
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock);
|
||||||
this.decoder = new Decoder(this);
|
this.decoder = new Decoder(this);
|
||||||
this.kexer = new KeyExchanger(this);
|
this.kexer = new KeyExchanger(this);
|
||||||
clientID = "SSH-2.0-" + config.getVersion();
|
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -383,7 +383,7 @@ public final class TransportImpl
|
|||||||
close.lock();
|
close.lock();
|
||||||
try {
|
try {
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
disconnectListener.notifyDisconnect(reason);
|
disconnectListener.notifyDisconnect(reason, message);
|
||||||
getService().notifyError(new TransportException(reason, "Disconnected"));
|
getService().notifyError(new TransportException(reason, "Disconnected"));
|
||||||
sendDisconnect(reason, message);
|
sendDisconnect(reason, message);
|
||||||
finishOff();
|
finishOff();
|
||||||
@@ -499,6 +499,10 @@ public final class TransportImpl
|
|||||||
gotServiceAccept();
|
gotServiceAccept();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case USERAUTH_BANNER: {
|
||||||
|
log.debug("Received USERAUTH_BANNER");
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
sendUnimplemented();
|
sendUnimplemented();
|
||||||
}
|
}
|
||||||
@@ -521,7 +525,7 @@ public final class TransportImpl
|
|||||||
final DisconnectReason code = DisconnectReason.fromInt(buf.readUInt32AsInt());
|
final DisconnectReason code = DisconnectReason.fromInt(buf.readUInt32AsInt());
|
||||||
final String message = buf.readString();
|
final String message = buf.readString();
|
||||||
log.info("Received SSH_MSG_DISCONNECT (reason={}, msg={})", code, message);
|
log.info("Received SSH_MSG_DISCONNECT (reason={}, msg={})", code, message);
|
||||||
throw new TransportException(code, "Disconnected; server said: " + message);
|
throw new TransportException(code, message);
|
||||||
} catch (Buffer.BufferException be) {
|
} catch (Buffer.BufferException be) {
|
||||||
throw new TransportException(be);
|
throw new TransportException(be);
|
||||||
}
|
}
|
||||||
@@ -572,7 +576,7 @@ public final class TransportImpl
|
|||||||
|
|
||||||
final SSHException causeOfDeath = SSHException.chainer.chain(ex);
|
final SSHException causeOfDeath = SSHException.chainer.chain(ex);
|
||||||
|
|
||||||
disconnectListener.notifyDisconnect(causeOfDeath.getDisconnectReason());
|
disconnectListener.notifyDisconnect(causeOfDeath.getDisconnectReason(), causeOfDeath.getMessage());
|
||||||
|
|
||||||
ErrorDeliveryUtil.alertEvents(causeOfDeath, close, serviceAccept);
|
ErrorDeliveryUtil.alertEvents(causeOfDeath, close, serviceAccept);
|
||||||
kexer.notifyError(causeOfDeath);
|
kexer.notifyError(causeOfDeath);
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2012 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.
|
||||||
|
*
|
||||||
|
* This file may incorporate work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.transport.mac;
|
||||||
|
|
||||||
|
/** HMAC-SHA1 <code>MAC</code> */
|
||||||
|
public class HMACSHA2256
|
||||||
|
extends BaseMAC {
|
||||||
|
|
||||||
|
/** Named factory for the HMAC-SHA1 <code>MAC</code> */
|
||||||
|
public static class Factory
|
||||||
|
implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MAC create() {
|
||||||
|
return new HMACSHA2256();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "hmac-sha2-256";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HMACSHA2256() {
|
||||||
|
super("HmacSHA256", 20, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2012 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.
|
||||||
|
*
|
||||||
|
* This file may incorporate work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.transport.mac;
|
||||||
|
|
||||||
|
/** HMAC-SHA1 <code>MAC</code> */
|
||||||
|
public class HMACSHA2512
|
||||||
|
extends BaseMAC {
|
||||||
|
|
||||||
|
/** Named factory for the HMAC-SHA1 <code>MAC</code> */
|
||||||
|
public static class Factory
|
||||||
|
implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MAC create() {
|
||||||
|
return new HMACSHA2512();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "hmac-sha2-512";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HMACSHA2512() {
|
||||||
|
super("HmacSHA512", 20, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,9 +28,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
@@ -129,6 +131,22 @@ public class OpenSSHKnownHosts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a single entry
|
||||||
|
*/
|
||||||
|
public void write(HostEntry entry)
|
||||||
|
throws IOException {
|
||||||
|
final BufferedWriter writer = new BufferedWriter(new FileWriter(khFile, true));
|
||||||
|
try {
|
||||||
|
writer.write(entry.getLine());
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
IOUtils.closeQuietly(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static File detectSSHDir() {
|
public static File detectSSHDir() {
|
||||||
final File sshDir = new File(System.getProperty("user.home"), ".ssh");
|
final File sshDir = new File(System.getProperty("user.home"), ".ssh");
|
||||||
return sshDir.exists() ? sshDir : null;
|
return sshDir.exists() ? sshDir : null;
|
||||||
@@ -182,7 +200,10 @@ public class OpenSSHKnownHosts
|
|||||||
if (marker != null) {
|
if (marker != null) {
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
if(split.length < 3) {
|
||||||
|
LOG.error("Error reading entry `{}`", line);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final String hostnames = split[i++];
|
final String hostnames = split[i++];
|
||||||
final String sType = split[i++];
|
final String sType = split[i++];
|
||||||
|
|
||||||
@@ -376,11 +397,6 @@ public class OpenSSHKnownHosts
|
|||||||
return saltyBytes;
|
return saltyBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLine() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getHostPart() {
|
protected String getHostPart() {
|
||||||
return hashedHost;
|
return hashedHost;
|
||||||
|
|||||||
@@ -18,21 +18,20 @@ package net.schmizz.sshj.userauth.keyprovider;
|
|||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
/** A file key provider is initialized with a location of */
|
/** A file key provider is initialized with a location of */
|
||||||
public interface FileKeyProvider
|
public interface FileKeyProvider
|
||||||
extends KeyProvider {
|
extends KeyProvider {
|
||||||
|
|
||||||
enum Format {
|
|
||||||
PKCS8,
|
|
||||||
OpenSSH,
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(File location);
|
void init(File location);
|
||||||
|
|
||||||
void init(File location, PasswordFinder pwdf);
|
void init(File location, PasswordFinder pwdf);
|
||||||
|
|
||||||
|
void init(Reader location);
|
||||||
|
|
||||||
|
void init(Reader location, PasswordFinder pwdf);
|
||||||
|
|
||||||
void init(String privateKey, String publicKey);
|
void init(String privateKey, String publicKey);
|
||||||
|
|
||||||
void init(String privateKey, String publicKey, PasswordFinder pwdf);
|
void init(String privateKey, String publicKey, PasswordFinder pwdf);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package net.schmizz.sshj.userauth.keyprovider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version $Id:$
|
||||||
|
*/
|
||||||
|
public enum KeyFormat {
|
||||||
|
PKCS8,
|
||||||
|
OpenSSH,
|
||||||
|
PuTTY,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
@@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.userauth.keyprovider;
|
package net.schmizz.sshj.userauth.keyprovider;
|
||||||
|
|
||||||
import net.schmizz.sshj.common.IOUtils;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
@@ -24,6 +22,8 @@ import java.io.IOException;
|
|||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.IOUtils;
|
||||||
|
|
||||||
public class KeyProviderUtil {
|
public class KeyProviderUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,15 +32,13 @@ public class KeyProviderUtil {
|
|||||||
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
||||||
*
|
*
|
||||||
* @param location
|
* @param location
|
||||||
*
|
|
||||||
* @return name of the key file format
|
* @return name of the key file format
|
||||||
*
|
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
*/
|
*/
|
||||||
public static FileKeyProvider.Format detectKeyFileFormat(File location)
|
public static KeyFormat detectKeyFileFormat(File location)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return detectKeyFileFormat(new FileReader(location),
|
return detectKeyFileFormat(new FileReader(location),
|
||||||
new File(location + ".pub").exists());
|
new File(location + ".pub").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,12 +48,10 @@ public class KeyProviderUtil {
|
|||||||
*
|
*
|
||||||
* @param privateKey Private key stored in a string
|
* @param privateKey Private key stored in a string
|
||||||
* @param separatePubKey Is the public key stored separately from the private key
|
* @param separatePubKey Is the public key stored separately from the private key
|
||||||
*
|
|
||||||
* @return name of the key file format
|
* @return name of the key file format
|
||||||
*
|
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
*/
|
*/
|
||||||
public static FileKeyProvider.Format detectKeyFileFormat(String privateKey,
|
public static KeyFormat detectKeyFileFormat(String privateKey,
|
||||||
boolean separatePubKey)
|
boolean separatePubKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return detectKeyFileFormat(new StringReader(privateKey), separatePubKey);
|
return detectKeyFileFormat(new StringReader(privateKey), separatePubKey);
|
||||||
@@ -68,29 +64,38 @@ public class KeyProviderUtil {
|
|||||||
*
|
*
|
||||||
* @param privateKey Private key accessible through a {@code Reader}
|
* @param privateKey Private key accessible through a {@code Reader}
|
||||||
* @param separatePubKey Is the public key stored separately from the private key
|
* @param separatePubKey Is the public key stored separately from the private key
|
||||||
*
|
|
||||||
* @return name of the key file format
|
* @return name of the key file format
|
||||||
*
|
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
*/
|
*/
|
||||||
private static FileKeyProvider.Format detectKeyFileFormat(Reader privateKey,
|
public static KeyFormat detectKeyFileFormat(Reader privateKey,
|
||||||
boolean separatePubKey)
|
boolean separatePubKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
BufferedReader br = new BufferedReader(privateKey);
|
BufferedReader br = new BufferedReader(privateKey);
|
||||||
String firstLine = br.readLine();
|
final String firstLine;
|
||||||
IOUtils.closeQuietly(br);
|
try {
|
||||||
if (firstLine == null)
|
firstLine = br.readLine();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
IOUtils.closeQuietly(br);
|
||||||
|
}
|
||||||
|
if(firstLine == null) {
|
||||||
throw new IOException("Empty file");
|
throw new IOException("Empty file");
|
||||||
if (firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----"))
|
}
|
||||||
if (separatePubKey)
|
if(firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----")) {
|
||||||
// Can delay asking for password since have unencrypted pubkey
|
if(separatePubKey)
|
||||||
return FileKeyProvider.Format.OpenSSH;
|
// Can delay asking for password since have unencrypted pubkey
|
||||||
|
{
|
||||||
|
return KeyFormat.OpenSSH;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
// More general
|
// More general
|
||||||
return FileKeyProvider.Format.PKCS8;
|
{
|
||||||
/*
|
return KeyFormat.PKCS8;
|
||||||
* TODO: Tectia, PuTTY (.ppk) ...
|
}
|
||||||
*/
|
}
|
||||||
return FileKeyProvider.Format.Unknown;
|
if(firstLine.startsWith("PuTTY-User-Key-File-")) {
|
||||||
|
return KeyFormat.PuTTY;
|
||||||
|
}
|
||||||
|
return KeyFormat.Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,24 +15,32 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.userauth.keyprovider;
|
package net.schmizz.sshj.userauth.keyprovider;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
import net.schmizz.sshj.common.IOUtils;
|
import net.schmizz.sshj.common.IOUtils;
|
||||||
import net.schmizz.sshj.common.KeyType;
|
import net.schmizz.sshj.common.KeyType;
|
||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||||
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
|
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
|
||||||
|
import net.schmizz.sshj.userauth.password.PrivateKeyReaderResource;
|
||||||
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
|
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.EncryptionException;
|
import org.bouncycastle.openssl.EncryptionException;
|
||||||
import org.bouncycastle.openssl.PEMReader;
|
import org.bouncycastle.openssl.PEMDecryptorProvider;
|
||||||
|
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.JcePEMDecryptorProviderBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
/** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */
|
/** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */
|
||||||
public class PKCS8KeyFile
|
public class PKCS8KeyFile
|
||||||
implements FileKeyProvider {
|
implements FileKeyProvider {
|
||||||
@@ -78,6 +86,18 @@ public class PKCS8KeyFile
|
|||||||
return type != null ? type : (type = KeyType.fromKey(getPublic()));
|
return type != null ? type : (type = KeyType.fromKey(getPublic()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Reader location) {
|
||||||
|
assert location != null;
|
||||||
|
resource = new PrivateKeyReaderResource(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Reader location, PasswordFinder pwdf) {
|
||||||
|
init(location);
|
||||||
|
this.pwdf = pwdf;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(File location) {
|
public void init(File location) {
|
||||||
assert location != null;
|
assert location != null;
|
||||||
@@ -119,14 +139,26 @@ public class PKCS8KeyFile
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
KeyPair kp = null;
|
KeyPair kp = null;
|
||||||
org.bouncycastle.openssl.PasswordFinder pFinder = makeBouncyPasswordFinder();
|
org.bouncycastle.openssl.PasswordFinder pFinder = makeBouncyPasswordFinder();
|
||||||
PEMReader r = null;
|
PEMParser r = null;
|
||||||
Object o = null;
|
Object o = null;
|
||||||
try {
|
try {
|
||||||
for (; ; ) {
|
for (; ; ) {
|
||||||
// while the PasswordFinder tells us we should retry
|
// while the PasswordFinder tells us we should retry
|
||||||
try {
|
try {
|
||||||
r = new PEMReader(resource.getReader(), pFinder);
|
r = new PEMParser(resource.getReader());
|
||||||
o = r.readObject();
|
o = r.readObject();
|
||||||
|
|
||||||
|
JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
|
||||||
|
pemConverter.setProvider("BC");
|
||||||
|
if (pFinder != null && o instanceof PEMEncryptedKeyPair) {
|
||||||
|
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
|
||||||
|
PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(pFinder.getPassword());
|
||||||
|
o = pemConverter.getKeyPair(((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor));
|
||||||
|
}
|
||||||
|
if (o instanceof PEMKeyPair) {
|
||||||
|
o = pemConverter.getKeyPair((PEMKeyPair) o);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (EncryptionException e) {
|
} catch (EncryptionException e) {
|
||||||
if (pwdf.shouldRetry(resource))
|
if (pwdf.shouldRetry(resource))
|
||||||
continue;
|
continue;
|
||||||
@@ -154,4 +186,4 @@ public class PKCS8KeyFile
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "PKCS8KeyFile{resource=" + resource + "}";
|
return "PKCS8KeyFile{resource=" + resource + "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
package net.schmizz.sshj.userauth.keyprovider;
|
||||||
|
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.DSAPrivateKeySpec;
|
||||||
|
import java.security.spec.DSAPublicKeySpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.RSAPrivateKeySpec;
|
||||||
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.Base64;
|
||||||
|
import net.schmizz.sshj.common.KeyType;
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||||
|
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
|
||||||
|
import net.schmizz.sshj.userauth.password.PrivateKeyReaderResource;
|
||||||
|
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
|
||||||
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>Sample PuTTY file format</h2>
|
||||||
|
* <pre>
|
||||||
|
* PuTTY-User-Key-File-2: ssh-rsa
|
||||||
|
* Encryption: none
|
||||||
|
* Comment: rsa-key-20080514
|
||||||
|
* Public-Lines: 4
|
||||||
|
* AAAAB3NzaC1yc2EAAAABJQAAAIEAiPVUpONjGeVrwgRPOqy3Ym6kF/f8bltnmjA2
|
||||||
|
* BMdAtaOpiD8A2ooqtLS5zWYuc0xkW0ogoKvORN+RF4JI+uNUlkxWxnzJM9JLpnvA
|
||||||
|
* HrMoVFaQ0cgDMIHtE1Ob1cGAhlNInPCRnGNJpBNcJ/OJye3yt7WqHP4SPCCLb6nL
|
||||||
|
* nmBUrLM=
|
||||||
|
* Private-Lines: 8
|
||||||
|
* AAAAgGtYgJzpktzyFjBIkSAmgeVdozVhgKmF6WsDMUID9HKwtU8cn83h6h7ug8qA
|
||||||
|
* hUWcvVxO201/vViTjWVz9ALph3uMnpJiuQaaNYIGztGJBRsBwmQW9738pUXcsUXZ
|
||||||
|
* 79KJP01oHn6Wkrgk26DIOsz04QOBI6C8RumBO4+F1WdfueM9AAAAQQDmA4hcK8Bx
|
||||||
|
* nVtEpcF310mKD3nsbJqARdw5NV9kCxPnEsmy7Sy1L4Ob/nTIrynbc3MA9HQVJkUz
|
||||||
|
* 7V0va5Pjm/T7AAAAQQCYbnG0UEekwk0LG1Hkxh1OrKMxCw2KWMN8ac3L0LVBg/Tk
|
||||||
|
* 8EnB2oT45GGeJaw7KzdoOMFZz0iXLsVLNUjNn2mpAAAAQQCN6SEfWqiNzyc/w5n/
|
||||||
|
* lFVDHExfVUJp0wXv+kzZzylnw4fs00lC3k4PZDSsb+jYCMesnfJjhDgkUA0XPyo8
|
||||||
|
* Emdk
|
||||||
|
* Private-MAC: 50c45751d18d74c00fca395deb7b7695e3ed6f77
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @version $Id:$
|
||||||
|
*/
|
||||||
|
public class PuTTYKeyFile implements FileKeyProvider {
|
||||||
|
|
||||||
|
public static class Factory
|
||||||
|
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileKeyProvider create() {
|
||||||
|
return new PuTTYKeyFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "PuTTY";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] privateKey;
|
||||||
|
private byte[] publicKey;
|
||||||
|
|
||||||
|
private KeyPair kp;
|
||||||
|
|
||||||
|
protected PasswordFinder pwdf;
|
||||||
|
|
||||||
|
protected Resource<?> resource;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Reader location) {
|
||||||
|
this.resource = new PrivateKeyReaderResource(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Reader location, PasswordFinder pwdf) {
|
||||||
|
this.init(location);
|
||||||
|
this.pwdf = pwdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(File location) {
|
||||||
|
resource = new PrivateKeyFileResource(location.getAbsoluteFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(File location, PasswordFinder pwdf) {
|
||||||
|
this.init(location);
|
||||||
|
this.pwdf = pwdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(String privateKey, String publicKey) {
|
||||||
|
resource = new PrivateKeyStringResource(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(String privateKey, String publicKey, PasswordFinder pwdf) {
|
||||||
|
init(privateKey, publicKey);
|
||||||
|
this.pwdf = pwdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivate()
|
||||||
|
throws IOException {
|
||||||
|
return kp != null ? kp.getPrivate() : (kp = this.readKeyPair()).getPrivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey getPublic()
|
||||||
|
throws IOException {
|
||||||
|
return kp != null ? kp.getPublic() : (kp = this.readKeyPair()).getPublic();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key type. Either "ssh-rsa" for RSA key, or "ssh-dss" for DSA key.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public KeyType getType() throws IOException {
|
||||||
|
return KeyType.fromString(headers.get("PuTTY-User-Key-File-2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEncrypted() {
|
||||||
|
// Currently the only supported encryption types are "aes256-cbc" and "none".
|
||||||
|
return "aes256-cbc".equals(headers.get("Encryption"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> payload
|
||||||
|
= new HashMap<String, String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>();
|
||||||
|
|
||||||
|
|
||||||
|
protected KeyPair readKeyPair() throws IOException {
|
||||||
|
this.parseKeyPair();
|
||||||
|
if(KeyType.RSA.equals(this.getType())) {
|
||||||
|
final KeyReader publicKeyReader = new KeyReader(publicKey);
|
||||||
|
publicKeyReader.skip(); // skip this
|
||||||
|
// public key exponent
|
||||||
|
BigInteger e = publicKeyReader.readInt();
|
||||||
|
// modulus
|
||||||
|
BigInteger n = publicKeyReader.readInt();
|
||||||
|
|
||||||
|
final KeyReader privateKeyReader = new KeyReader(privateKey);
|
||||||
|
// private key exponent
|
||||||
|
BigInteger d = privateKeyReader.readInt();
|
||||||
|
|
||||||
|
final KeyFactory factory;
|
||||||
|
try {
|
||||||
|
factory = KeyFactory.getInstance("RSA");
|
||||||
|
}
|
||||||
|
catch(NoSuchAlgorithmException s) {
|
||||||
|
throw new IOException(s.getMessage(), s);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
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())) {
|
||||||
|
final KeyReader publicKeyReader = new KeyReader(publicKey);
|
||||||
|
publicKeyReader.skip(); // skip this
|
||||||
|
BigInteger p = publicKeyReader.readInt();
|
||||||
|
BigInteger q = publicKeyReader.readInt();
|
||||||
|
BigInteger g = publicKeyReader.readInt();
|
||||||
|
BigInteger y = publicKeyReader.readInt();
|
||||||
|
|
||||||
|
final KeyReader privateKeyReader = new KeyReader(privateKey);
|
||||||
|
// Private exponent from the private key
|
||||||
|
BigInteger x = privateKeyReader.readInt();
|
||||||
|
|
||||||
|
final KeyFactory factory;
|
||||||
|
try {
|
||||||
|
factory = KeyFactory.getInstance("DSA");
|
||||||
|
}
|
||||||
|
catch(NoSuchAlgorithmException s) {
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch(InvalidKeySpecException e) {
|
||||||
|
throw new IOException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IOException(String.format("Unknown key type %s", this.getType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parseKeyPair() throws IOException {
|
||||||
|
BufferedReader r = new BufferedReader(resource.getReader());
|
||||||
|
// Parse the text into headers and payloads
|
||||||
|
try {
|
||||||
|
String headerName = null;
|
||||||
|
String line;
|
||||||
|
while((line = r.readLine()) != null) {
|
||||||
|
int idx = line.indexOf(": ");
|
||||||
|
if(idx > 0) {
|
||||||
|
headerName = line.substring(0, idx);
|
||||||
|
headers.put(headerName, line.substring(idx + 2));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String s = payload.get(headerName);
|
||||||
|
if(s == null) {
|
||||||
|
s = line;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Append to previous line
|
||||||
|
s += line;
|
||||||
|
}
|
||||||
|
// Save payload
|
||||||
|
payload.put(headerName, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
r.close();
|
||||||
|
}
|
||||||
|
// Retrieve keys from payload
|
||||||
|
publicKey = Base64.decode(payload.get("Public-Lines"));
|
||||||
|
if(this.isEncrypted()) {
|
||||||
|
final char[] passphrase;
|
||||||
|
if(pwdf != null) {
|
||||||
|
passphrase = pwdf.reqPassword(resource);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
passphrase = "".toCharArray();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase));
|
||||||
|
this.verify(new String(passphrase));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
PasswordUtils.blankOut(passphrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
privateKey = Base64.decode(payload.get("Private-Lines"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a passphrase into a key, by following the convention that PuTTY uses.
|
||||||
|
* <p/>
|
||||||
|
* <p/>
|
||||||
|
* This is used to decrypt the private key when it's encrypted.
|
||||||
|
*/
|
||||||
|
private byte[] toKey(final String passphrase) throws IOException {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
|
||||||
|
// The encryption key is derived from the passphrase by means of a succession of SHA-1 hashes.
|
||||||
|
|
||||||
|
// Sequence number 0
|
||||||
|
digest.update(new byte[]{0, 0, 0, 0});
|
||||||
|
digest.update(passphrase.getBytes());
|
||||||
|
byte[] key1 = digest.digest();
|
||||||
|
|
||||||
|
// Sequence number 1
|
||||||
|
digest.update(new byte[]{0, 0, 0, 1});
|
||||||
|
digest.update(passphrase.getBytes());
|
||||||
|
byte[] key2 = digest.digest();
|
||||||
|
|
||||||
|
byte[] r = new byte[32];
|
||||||
|
System.arraycopy(key1, 0, r, 0, 20);
|
||||||
|
System.arraycopy(key2, 0, r, 20, 12);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
catch(NoSuchAlgorithmException e) {
|
||||||
|
throw new IOException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the MAC.
|
||||||
|
*/
|
||||||
|
private void verify(final String passphrase) throws IOException {
|
||||||
|
try {
|
||||||
|
// The key to the MAC is itself a SHA-1 hash of:
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
digest.update("putty-private-key-file-mac-key".getBytes());
|
||||||
|
if(passphrase != null) {
|
||||||
|
digest.update(passphrase.getBytes());
|
||||||
|
}
|
||||||
|
final byte[] key = digest.digest();
|
||||||
|
|
||||||
|
final Mac mac = Mac.getInstance("HmacSHA1");
|
||||||
|
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
|
||||||
|
|
||||||
|
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
final DataOutputStream data = new DataOutputStream(out);
|
||||||
|
// name of algorithm
|
||||||
|
data.writeInt(this.getType().toString().length());
|
||||||
|
data.writeBytes(this.getType().toString());
|
||||||
|
|
||||||
|
data.writeInt(headers.get("Encryption").length());
|
||||||
|
data.writeBytes(headers.get("Encryption"));
|
||||||
|
|
||||||
|
data.writeInt(headers.get("Comment").length());
|
||||||
|
data.writeBytes(headers.get("Comment"));
|
||||||
|
|
||||||
|
data.writeInt(publicKey.length);
|
||||||
|
data.write(publicKey);
|
||||||
|
|
||||||
|
data.writeInt(privateKey.length);
|
||||||
|
data.write(privateKey);
|
||||||
|
|
||||||
|
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
|
||||||
|
final String reference = headers.get("Private-MAC");
|
||||||
|
if(!encoded.equals(reference)) {
|
||||||
|
throw new IOException("Invalid passphrase");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(GeneralSecurityException e) {
|
||||||
|
throw new IOException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt private key
|
||||||
|
*
|
||||||
|
* @param passphrase To decrypt
|
||||||
|
*/
|
||||||
|
private byte[] decrypt(final byte[] key, final String 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);
|
||||||
|
}
|
||||||
|
catch(GeneralSecurityException e) {
|
||||||
|
throw new IOException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the putty key bit vector, which is an encoded sequence
|
||||||
|
* of {@link java.math.BigInteger}s.
|
||||||
|
*/
|
||||||
|
private final static class KeyReader {
|
||||||
|
private final DataInput di;
|
||||||
|
|
||||||
|
public KeyReader(byte[] key) {
|
||||||
|
this.di = new DataInputStream(new ByteArrayInputStream(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips an integer without reading it.
|
||||||
|
*/
|
||||||
|
public void skip() throws IOException {
|
||||||
|
final int read = di.readInt();
|
||||||
|
if(read != di.skipBytes(read)) {
|
||||||
|
throw new IOException(String.format("Failed to skip %d bytes", read));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] read() throws IOException {
|
||||||
|
int len = di.readInt();
|
||||||
|
if(len <= 0 || len > 513) {
|
||||||
|
throw new IOException(String.format("Invalid length %d", len));
|
||||||
|
}
|
||||||
|
byte[] r = new byte[len];
|
||||||
|
di.readFully(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the next integer.
|
||||||
|
*/
|
||||||
|
public BigInteger readInt() throws IOException {
|
||||||
|
return new BigInteger(read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,16 +62,16 @@ public abstract class KeyedAuthMethod
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String kt = KeyType.fromKey(key).toString();
|
final String kt = KeyType.fromKey(key).toString();
|
||||||
Signature sigger = Factory.Named.Util.create(params.getTransport().getConfig().getSignatureFactories(), kt);
|
Signature signature = Factory.Named.Util.create(params.getTransport().getConfig().getSignatureFactories(), kt);
|
||||||
if (sigger == null)
|
if (signature == null)
|
||||||
throw new UserAuthException("Could not create signature instance for " + kt + " key");
|
throw new UserAuthException("Could not create signature instance for " + kt + " key");
|
||||||
|
|
||||||
sigger.init(null, key);
|
signature.init(null, key);
|
||||||
sigger.update(new Buffer.PlainBuffer()
|
signature.update(new Buffer.PlainBuffer()
|
||||||
.putString(params.getTransport().getSessionID())
|
.putString(params.getTransport().getSessionID())
|
||||||
.putBuffer(reqBuf) // & rest of the data for sig
|
.putBuffer(reqBuf) // & rest of the data for sig
|
||||||
.getCompactData());
|
.getCompactData());
|
||||||
reqBuf.putSignature(kt, sigger.sign());
|
reqBuf.putSignature(kt, signature.encode(signature.sign()));
|
||||||
return reqBuf;
|
return reqBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2012 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.userauth.password;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
public class PrivateKeyReaderResource
|
||||||
|
extends Resource<Reader> {
|
||||||
|
|
||||||
|
public PrivateKeyReaderResource(Reader privateKeyFile) {
|
||||||
|
super(privateKeyFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getReader()
|
||||||
|
throws IOException {
|
||||||
|
return getDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
221
src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java
Normal file
221
src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package net.schmizz.sshj.keyprovider;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
public class PuTTYKeyFileTest {
|
||||||
|
|
||||||
|
final static String ppk2048 = "PuTTY-User-Key-File-2: ssh-rsa\n" +
|
||||||
|
"Encryption: none\n" +
|
||||||
|
"Comment: \n" +
|
||||||
|
"Public-Lines: 6\n" +
|
||||||
|
"AAAAB3NzaC1yc2EAAAADAQABAAABAQC0ITaAE49ievGiREUNxjccle9zJEZkNdkE\n" +
|
||||||
|
"2Nnkl0zxlGVwShwRIjtarM0uKiUQAFD1OhkdSA/1FhZKRumIUWD+2Fkj23EEvox0\n" +
|
||||||
|
"bTZyPzvQJERchdpvZKJyfxZgnPL6ygY6UQj8oNBhxnuwm9lL11cSJWS+4qJigT7g\n" +
|
||||||
|
"59eEhDyBaZ/HoijtDGvfPmJlhdNcq0MlC6ALy7XXpOd4RS2oo8m/TRpLFvoSnAQ/\n" +
|
||||||
|
"sr1wv9u7sTZh5C5RAjPN8LWBu7GuodqZ8PhlyT3oT+qbjpA/2e18vQta1ELROBKk\n" +
|
||||||
|
"qsLnsN4fjH69eYSZ8h5R07tfvQxTKRwCWKziqMjP4dF9Lz+gVcy1\n" +
|
||||||
|
"Private-Lines: 14\n" +
|
||||||
|
"AAABAQCqLWasAc7JH5YB07XZmZafrxeWFINcUXNCnQzeZgMPiT98osd5eHnS5MbE\n" +
|
||||||
|
"ApUZVPMne0gW3eoVhlRwwCYJ37hfjE5LDhrsfIl9xWBW916u+lSLhPolm1HOEjs1\n" +
|
||||||
|
"85GrVgokNkLjSZsVhMt+wv68JCnivuk7XipEHg8ltGNskvIG4AjW97uBqewyvyeG\n" +
|
||||||
|
"wsPYyBtiifRxJQ1th5hlLPh+jOBsyz91uC+ZeEW2pil2ftI7XrbKVbA95SRh4W8R\n" +
|
||||||
|
"tNBjqUpI+M2mJQ7nwh2gxd/GdNxKyvTUyCAfJo+DzAG+XRGW1Px7ibFw38jiT+CP\n" +
|
||||||
|
"tKTjCZRFMPUvmoH3MR1hzjqjqpuBAAAAgQDj4/2h35V/aAEYvQfkwF4k6rWOY15+\n" +
|
||||||
|
"gEV+gbfjWlYGxkH6U0AvMQv3c6EAvJNQsKip3/fqOHgdd37CcGVW+NQTucHlxz8K\n" +
|
||||||
|
"e4cYs0Dy8g4gcNhy2M99MOy9TuMsC0/mrTQUP0Vewwo7FASWF23sbhZsBM//BC8W\n" +
|
||||||
|
"m3LM843RwCbvXQAAAIEAylkY1TU721y22mVA2C+o6ADs55ZtMGJqjI0DjOiWCgxt\n" +
|
||||||
|
"j6pXRmJQ05hFZy4pO4AOYMZ5IW7MdqmCu2+GVytA6PxA6C+OGYF0Eh1YJbIh/Qrv\n" +
|
||||||
|
"07NMrYQVwQY2+FAJpLAwWJAjlrRRlANgGBHkbppf8RuQFB/euToHCZ6R6goJdTkA\n" +
|
||||||
|
"AACAOn0n+B0Lums/2FFmBRak2niTRONt6GMWhtK4e42MnKN3VxMGshAB4SdDAQOY\n" +
|
||||||
|
"4Qk66RYmniuhaC3sLwxtXsEKoVnMp9EXVoTPEd+BQCVBOJzZDVtAaejO9bqrvRL8\n" +
|
||||||
|
"kmknsX54RBXxrOVvNTofHLiRojncZnRSrM3BR+Xjo0b0+mE=\n" +
|
||||||
|
"Private-MAC: c9c10df8a1e3546eedcc08608efd5338de5df723\n";
|
||||||
|
|
||||||
|
final static String ppk4096 = "PuTTY-User-Key-File-2: ssh-rsa\n" +
|
||||||
|
"Encryption: none\n" +
|
||||||
|
"Comment: \n" +
|
||||||
|
"Public-Lines: 12\n" +
|
||||||
|
"AAAAB3NzaC1yc2EAAAADAQABAAACAQDMlwE5YNobWP8R47Ms41hnQnATKfJblTxW\n" +
|
||||||
|
"k/6nf+5IOknCNFBMQUOnToCmvcVRPzepr3nRFGm/gvo5SjsKdE4b0b9eT7xOGAYM\n" +
|
||||||
|
"9y18qO3flt6hARasK8NoivbT8Cm1f0Zj02eLBaiFpFYZOuBZdpluKiYH0wHuSPeq\n" +
|
||||||
|
"K3Q3/arsnQj1C0X+h5f4Nm0IYIHRkNsnvZrJf+MlHtcwS+BPXpAK9tkICcP1MJ2x\n" +
|
||||||
|
"UvTKh+TgWQJQ8EUq0OUkTBUBdmG+J6O+sdB0V6r06IpcXZUNed02F+bzP/DVUE1b\n" +
|
||||||
|
"mJZTx0ynZhKyP6NeXlwuZ3fUZhiwwqMRvCQuq1p8/itG9Vz+eY652KIIrCoVsyH1\n" +
|
||||||
|
"gIIRert3ADX5UySMdPcgBoDWYlfyj/fS+dR2o1lIwQXLcl9uL8ZELteSq/sLmapA\n" +
|
||||||
|
"YJbIis3r9aJnNSVaQSKn8p20tCWNnazAcSK0RTN2h5r0/r7WfvXafFIMt6VZUxPn\n" +
|
||||||
|
"dpFCJtxhgCrszosy87eL92NCoZvdQMOddpV3op04CDccZy0LEAt7o9dXoNNaeYlx\n" +
|
||||||
|
"czaUTV/JcuAnk9G0u3xUpTh0AOQauuxn8Dv6yyVLvXNJANp89zAhUukEwdhqOvXD\n" +
|
||||||
|
"U5qLLgY0Kf3v+ySj6HUWNBoms6ijF1txT3RDmJdCiVfuZ1nic9tsyp0A77S1oEEQ\n" +
|
||||||
|
"QD7Rgmi4rQ==\n" +
|
||||||
|
"Private-Lines: 28\n" +
|
||||||
|
"AAACABi+/xfonhkGt7t7NjXsvcmnoJTA0x6+u1ChkADEmZbE7hz+ZOQEVOGMvkTs\n" +
|
||||||
|
"2UwNgHcW0X43oN7YQdniH6gRD02QHjyTGmy7vSeeUjMs37DWt9Dzp8FlfbpMbLSP\n" +
|
||||||
|
"7QuV/HagoHqRUaPwj7V3iKFplf9cO8Ngg3BGBSbhIKqRFTaPfADfvzSdRAVy19dW\n" +
|
||||||
|
"jP1DLy7sYSeUP25C/7ZIxzXycyvQVcoCHGCw47IKHa/NpiJ4wa32kfcu0ziDt1q4\n" +
|
||||||
|
"7fOpKcYsDdG0tOnwoqOvchLyNY6Qb4/moQO8Nc8pcq1pgt0QnJxQ1Dra4P1/6F+Z\n" +
|
||||||
|
"hc0DjePcROgcM9LAj42Cqh/hpiCfCiLJiDts+HhQppgA4fOMy5d/wrG0nuqKfhIv\n" +
|
||||||
|
"BsX9nJDj4eHU4eNBAoraUfNLDIq0GHYDcm+jlhqO1SHxymjIhDqS6Cz/FWf07L1Q\n" +
|
||||||
|
"5DQ/+xHysVHCcavQk4jA7JwbZrRWo5qyKrdLoWRPFUX5w5ocLnmj0Zx8VOl6a+8M\n" +
|
||||||
|
"Q+ehLSZXFoCbao3nES/oEkKH0RFNQsDMJb0uiKQv4b/+6bywtYIFc0eqvEqd1GSF\n" +
|
||||||
|
"x3exCdHNhLRycaCgGSh+IdPCRrMj0N7/9pGZmbjfcZ7uKlFwqETVmy1H67NTXUCW\n" +
|
||||||
|
"NukVfsqTRewpqjFFeaxW5GEYwEeA34MbIChfdw4/KRr5XDhFAAABAQD3c9w0rWAQ\n" +
|
||||||
|
"rjVF1WTeD89Mf+Fnf7NvRaHAaD1EJxfimgqCD5juCIa5WSplzEBpPSG/rpl3HYVz\n" +
|
||||||
|
"CZ98rdJSS/bmJieojefvjlz1nuuPlApg2ctCfEZYOFnNP6yt0w88GLp3aMTfIsTf\n" +
|
||||||
|
"Z893GZnMFzMMLItLcZBTSmQLiqpyU6lWE+Tr4QOcQeCF8XqHGrLWbwACuKocmCW/\n" +
|
||||||
|
"4nI+6gZ4SfucLXKwFgcuhSaXo0XM6HiSgZHb5wEyjS2Boad6vX8t4YdjZbCcnGVm\n" +
|
||||||
|
"9TEm0/ow41Cl44SJUU6pLlo4UnSmR7aLmTK4iEG3fIMdEmmy4VX3MJ8fqXuVJwLE\n" +
|
||||||
|
"RLzqEjCgIcQzAAABAQDTqB/A3CyJfeHYFO7Et6edAOklejxqRW4UuuOu55v7FOj5\n" +
|
||||||
|
"X/yW72rWbndcci+mDXQvDL6P9EG3vF1twPS0konHqVxqj6Jlp1AtUWND2FzVTypY\n" +
|
||||||
|
"0X7z4Mif5V0p5bS5Qx6/pBg37XXbisSANSDxFVdH0/OSTYXi4EKmh0LjU5Ls0zIw\n" +
|
||||||
|
"MB6TYetuR1hEcCxuVESnOMUgjXMsoIwGR/jeKynle45UwTqUv/oWRQvFeIi5wlwn\n" +
|
||||||
|
"82GtUzLxhAo/BbXc3ODWjIGfKSxBJdsn0ZEXtPAk4CTqxM3VF4s3aOFAhHBDSyOv\n" +
|
||||||
|
"nHvWXwVRwmhtyXKEkTfAO6K4ptcS57LTNT8ta6+fAAABAQC9dPiPexqC35vWtWQd\n" +
|
||||||
|
"Zvm8DVCVscd7IPDn952FUsf2svoQ9MWodpD1craKGadSRsFCTVeYyHzS3Sg8HwKC\n" +
|
||||||
|
"NNoanAxpY4IqEPfuaLZZuKQsj3PsVj5rXdSEbmwCR7EhI9oDUDNcSLufR5A5DMpz\n" +
|
||||||
|
"wY4EJmg8uC2nO/O9Rzr516pIfDGsNwsdSKGWLlhgRzJxWl7M+cJjJfRlf6XruhLI\n" +
|
||||||
|
"WDDIq/jMHb5cLNjXdWTt3jyRQkm3HI6r5C3vc4mdInBm3tNUE+KKBtChegpgDgqg\n" +
|
||||||
|
"hZ41/hnd1e+3on3tvrE7arM3t4IHt7grwS/i1vdukV8ilYkTYHMG/Ls+6pUr+Swy\n" +
|
||||||
|
"z15x\n" +
|
||||||
|
"Private-MAC: a11331fa8b59cfb2be1c8e9f67ead34ac848d514\n";
|
||||||
|
|
||||||
|
final static String ppk1024_passphrase = "PuTTY-User-Key-File-2: ssh-rsa\n" +
|
||||||
|
"Encryption: aes256-cbc\n" +
|
||||||
|
"Comment: rsa-key-20121215\n" +
|
||||||
|
"Public-Lines: 4\n" +
|
||||||
|
"AAAAB3NzaC1yc2EAAAABJQAAAIB7KdUyuvGb2ne9G9YDAjaYvX/Mq6Q6ppGjbEQo\n" +
|
||||||
|
"bac66VUazxVpZsnAWikcdYAU7odkyt3jg7Nn1NgQS1a5mpXk/j77Ss5C9W4rymrU\n" +
|
||||||
|
"p32cmbgB/KIV80DnOyZyOtDWDPM0M0RRXqQvAO6TsnmsNSnBa8puMLHqCtrhvvJD\n" +
|
||||||
|
"KU+XEw==\n" +
|
||||||
|
"Private-Lines: 8\n" +
|
||||||
|
"4YMkPgLQJ9hOI1L1HsdOUnYi57tDy5h9DoPTHD55fhEYsn53h4WaHpxuZH8dTpbC\n" +
|
||||||
|
"5TcV3vYTfhh+aFBY0p/FI8L1hKfABLRxhkqkkc7xMmOGlA6HejAc8oTA3VArgSeG\n" +
|
||||||
|
"tRBuQRmBAC1Edtek/U+s8HzI2whzTw8tZoUUnT6844oc4tyCpWJUy5T8l+O3/03s\n" +
|
||||||
|
"SceJ98DN2k+L358VY8AXgPxP6NJvHvIlwmIo+PtcMWsyZegfSHEnoXN2GN4N0ul6\n" +
|
||||||
|
"298RzA9R+I3GSKKxsxUvWfOVibLq0dDM3+CTwcbmo4qvyM2xrRRLhObB2rVW07gL\n" +
|
||||||
|
"7+FZpHxf44QoQQ8mVkDJNaT1faF+h/8tCp2j1Cj5yEPHMOHGTVMyaz7gqhoMw5RX\n" +
|
||||||
|
"sfSP4ZaCGinLbouPrZN9Ue3ytwdEpmqU2MelmcZdcH6kWbLCqpWBswsxPfuhFdNt\n" +
|
||||||
|
"oYhmT2+0DKBuBVCAM4qRdA==\n" +
|
||||||
|
"Private-MAC: 40ccc8b9a7291ec64e5be0c99badbc8a012bf220\n";
|
||||||
|
|
||||||
|
final static String ppkdsa_passphrase = "PuTTY-User-Key-File-2: ssh-dss\n" +
|
||||||
|
"Encryption: aes256-cbc\n" +
|
||||||
|
"Comment: dsa-key-20140507\n" +
|
||||||
|
"Public-Lines: 10\n" +
|
||||||
|
"AAAAB3NzaC1kc3MAAACBAN6eo/Yh8ih26sKRAHAta/UqKesrXRS83GN7YqAxQzsP\n" +
|
||||||
|
"2tJ00UzOqZCdBoHIXLXC07QRJ9SkXOMnILw/KuaZ3paJ6ym92FzKi3BRfpzujIdo\n" +
|
||||||
|
"qBAEGSGOWbz2oYPDDSi0bsL84P4O8WD7ZxKhgTb4JAxlVJiW20vPfZA8Ft6xKJyd\n" +
|
||||||
|
"AAAAFQD1pnKWpSyHzi6RcVPn16FwmGIgmwAAAIEAiFPw87HVijatNOBeuxoU5PHH\n" +
|
||||||
|
"80kMl0TtxoI7rhB8fKO9bu7wLcT79h6xYS4Np6nHv9ajWwwVSLh8NjKgMbCXCz2j\n" +
|
||||||
|
"qD4ajvnusS7yz7TbTumeaGqFXEEzqzG4Xe6KXkv7kd7Yg+Dnw29zucgeAvPfuJFW\n" +
|
||||||
|
"Gtr4CWPoHSBgpTeyemEAAACBAJYvGi5gIMJQQUhIErKbtZ64V2L0zZtYkzlms03R\n" +
|
||||||
|
"cTBFN9++xV8zUvTPAAM8imsoxZ/5JNtNjJCAD+Ghrzyav24gxYG9v/YXtd2WsYa5\n" +
|
||||||
|
"0E/5wxcPor82SAqU2fd3IEQ5y9KHamXBuX/5KFDOTMC6cnGsutFkeo5rXQ0fI55C\n" +
|
||||||
|
"VSTq\n" +
|
||||||
|
"Private-Lines: 1\n" +
|
||||||
|
"nLEIBzB8WEaMMgDz5qwlqq7eBxLPIIi9uHyjMf7NOsQ=\n" +
|
||||||
|
"Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\n";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2048() throws Exception {
|
||||||
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
|
key.init(new StringReader(ppk2048));
|
||||||
|
assertNotNull(key.getPrivate());
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test4096() throws Exception {
|
||||||
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
|
key.init(new StringReader(ppk4096));
|
||||||
|
assertNotNull(key.getPrivate());
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
||||||
|
assertNotNull(key.getPrivate());
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IOException.class)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
assertNull(key.getPrivate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
||||||
|
assertNotNull(key.getPrivate());
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IOException.class)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
assertNull(key.getPrivate());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import java.io.PipedInputStream;
|
|||||||
import java.io.PipedOutputStream;
|
import java.io.PipedOutputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.common.SSHException;
|
||||||
import net.schmizz.sshj.connection.channel.direct.Session.Subsystem;
|
import net.schmizz.sshj.connection.channel.direct.Session.Subsystem;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -57,7 +58,7 @@ public class PacketReaderTest {
|
|||||||
try {
|
try {
|
||||||
reader.readPacket();
|
reader.readPacket();
|
||||||
fail("Should have failed to read packet of size " + Integer.MAX_VALUE);
|
fail("Should have failed to read packet of size " + Integer.MAX_VALUE);
|
||||||
} catch (IllegalStateException e) {
|
} catch (SSHException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
// success; indicated packet size was too large
|
// success; indicated packet size was too large
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class Disconnection {
|
|||||||
|
|
||||||
fixture.getClient().getTransport().setDisconnectListener(new DisconnectListener() {
|
fixture.getClient().getTransport().setDisconnectListener(new DisconnectListener() {
|
||||||
@Override
|
@Override
|
||||||
public void notifyDisconnect(DisconnectReason reason) {
|
public void notifyDisconnect(DisconnectReason reason, String message) {
|
||||||
notified = true;
|
notified = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,4 +80,9 @@ public class OpenSSHKnownHostsTest {
|
|||||||
assertTrue(kh.verify("69.163.155.180", 22, key));
|
assertTrue(kh.verify("69.163.155.180", 22, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerifyIndexError() throws Exception {
|
||||||
|
final OpenSSHKnownHosts v = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts.invalid"));
|
||||||
|
assertTrue(v.entries().isEmpty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/test/resources/known_hosts.invalid
Normal file
1
src/test/resources/known_hosts.invalid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
M36Lo+Ik5ukNugvvoNFlpnyiHMmtKxt3FpyEfYuryXjNqMNWHn/ARVnpUIl5jRLTB7WBzyLYMG7X5nuoFL9zYqKGtHxChbDunxMVbspw5WXI9VN+qxcLwmITmpEvI9ApyS/Ox2ZyN7zw==
|
||||||
Reference in New Issue
Block a user