mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d456612d25 | ||
|
|
6feed72251 | ||
|
|
67e44241d0 | ||
|
|
a2a5923767 | ||
|
|
bdf9ab7452 | ||
|
|
afdfa91eb7 | ||
|
|
29a6cf6f79 | ||
|
|
eece80cf48 | ||
|
|
7973cb1ff6 | ||
|
|
75c0ae9a83 | ||
|
|
f2314e74ed | ||
|
|
e041e3e1e3 | ||
|
|
47df71c836 | ||
|
|
e24ed6ee7b | ||
|
|
10f8645ecd | ||
|
|
d520585a09 | ||
|
|
28a11b0b45 | ||
|
|
a335185827 | ||
|
|
74a4012023 | ||
|
|
c98ad22a7a | ||
|
|
1c749da957 | ||
|
|
5d81e87bce | ||
|
|
d18e9d9961 | ||
|
|
84990ada08 | ||
|
|
9c424f9431 | ||
|
|
dec00efcaa | ||
|
|
742553912c | ||
|
|
e81fdb8d8b | ||
|
|
782ff9b83e | ||
|
|
84d15f4cf5 | ||
|
|
1ebcbb07ba | ||
|
|
9982e5c30e | ||
|
|
3f340d6927 | ||
|
|
b8eec64a37 | ||
|
|
314d9d01cf | ||
|
|
c526f8e3de | ||
|
|
9529c30105 | ||
|
|
6a476858d1 | ||
|
|
6bfb268c11 | ||
|
|
e334525da5 | ||
|
|
8776500fa0 | ||
|
|
a747db88ed | ||
|
|
97065264de | ||
|
|
7c26ac669a | ||
|
|
1c5b462206 | ||
|
|
4cb9610cdd | ||
|
|
b9d0a03cb3 | ||
|
|
4adc83b9df | ||
|
|
14edb33fa9 | ||
|
|
8e74330b0b | ||
|
|
5217d34198 | ||
|
|
d3d019c1c2 | ||
|
|
49185b044d |
@@ -1 +1,2 @@
|
||||
language: java
|
||||
language: java
|
||||
sudo: false
|
||||
|
||||
21
README.adoc
21
README.adoc
@@ -1,7 +1,7 @@
|
||||
= sshj - SSHv2 library for Java
|
||||
Jeroen van Erp
|
||||
:sshj_groupid: com.hierynomus
|
||||
:sshj_version: 0.11.0
|
||||
:sshj_version: 0.14.0
|
||||
:source-highlighter: pygments
|
||||
|
||||
image::https://travis-ci.org/hierynomus/sshj.svg?branch=master[]
|
||||
@@ -62,10 +62,11 @@ ciphers::
|
||||
`aes{128,192,256}-{cbc,ctr}`, `blowfish-cbc`, `3des-cbc`
|
||||
|
||||
key exchange::
|
||||
`diffie-hellman-group1-sha1`, `diffie-hellman-group14-sha1`
|
||||
`diffie-hellman-group1-sha1`, `diffie-hellman-group14-sha1`, `diffie-hellman-group-exhange-sha1`, `diffie-hellman-group-exchange-sha256`,
|
||||
`ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `curve25519-sha256@libssh.org`
|
||||
|
||||
signatures::
|
||||
`ssh-rsa`, `ssh-dss`
|
||||
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`
|
||||
|
||||
mac::
|
||||
`hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`
|
||||
@@ -92,7 +93,19 @@ Google Group: http://groups.google.com/group/sshj-users
|
||||
Fork away!
|
||||
|
||||
== Release history
|
||||
|
||||
SSHJ 0.14.0 (2015-11-04)::
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/171[#171]: Added support for `curve25519-sha256@libssh.org` key exchange algorithm
|
||||
* Added support for `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384` and `ecdh-sha2-nistp521` key exchange algorithms
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/167[#167]: Added support for `diffie-hellman-group-exhange-sha1` and `diffie-hellman-group-exhange-sha256` key exchange methods
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/212[#212]: Configure path escaping to enable shell expansion to work correctly
|
||||
* Merged https://github.com/hierynomus/sshj/issues/210[#210]: RemoteFileInputStream.skip returns wrong value (Fixes https://github.com/hierynomus/sshj/issues/209[#209])
|
||||
* Merged https://github.com/hierynomus/sshj/issues/208[#208]: Added SCP bandwidth limitation support
|
||||
* Merged https://github.com/hierynomus/sshj/issues/211[#211]: Made keyfile format detection more robust
|
||||
SSHJ 0.13.0 (2015-08-18)::
|
||||
* Merged https://github.com/hierynomus/sshj/issues/199[#199]: Fix for IndexOutOfBoundsException in ReadAheadRemoteFileInputStream, fixes https://github.com/hierynomus/sshj/issues/183[#183]
|
||||
* Merged https://github.com/hierynomus/sshj/issues/195[#195]: New authentication supported: `gssapi-with-mic`
|
||||
* Merged https://github.com/hierynomus/sshj/issues/201[#201]: New option to verify negotiated key exchange algorithms
|
||||
* Merged https://github.com/hierynomus/sshj/issues/196[#196]: Fix for looking up complete hostname in known hosts file
|
||||
SSHJ 0.12.0 (2015-04-14)::
|
||||
* Added support for HTTP proxies when running JDK6 or JDK7, fixes: https://github.com/hierynomus/sshj/issues/170[#170]
|
||||
* Merged https://github.com/hierynomus/sshj/issues/186[#186]: Fix for detecting end-of-stream
|
||||
|
||||
16
build.gradle
16
build.gradle
@@ -4,7 +4,7 @@ apply plugin: "signing"
|
||||
apply plugin: "osgi"
|
||||
|
||||
group = "com.hierynomus"
|
||||
version = "0.12.0"
|
||||
version = "0.14.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -21,7 +21,17 @@ configurations {
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
include "**/*Test.*"
|
||||
if (!project.hasProperty("allTests")) {
|
||||
useJUnit {
|
||||
excludeCategories 'com.hierynomus.sshj.test.SlowTests'
|
||||
excludeCategories 'com.hierynomus.sshj.test.KnownFailingTests'
|
||||
}
|
||||
}
|
||||
|
||||
afterSuite { descriptor, result ->
|
||||
if (descriptor.className != null) {
|
||||
def indicator = "\u001B[32m✓\u001b[0m"
|
||||
@@ -41,9 +51,11 @@ dependencies {
|
||||
compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
|
||||
compile "com.jcraft:jzlib:1.1.3"
|
||||
|
||||
compile "net.vrallev.ecc:ecc-25519-java:1.0.1"
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
testCompile "org.apache.sshd:sshd-core:0.11.0"
|
||||
testCompile "org.apache.sshd:sshd-core:1.0.0"
|
||||
testRuntime "ch.qos.logback:logback-classic:1.1.2"
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>net.schmizz</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<groupId>com.hierynomus</groupId>
|
||||
<artifactId>sshj-examples</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.10.0</version>
|
||||
<version>0.14.0</version>
|
||||
|
||||
<name>sshj-examples</name>
|
||||
<description>Examples for SSHv2 library for Java</description>
|
||||
@@ -53,9 +53,9 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.schmizz</groupId>
|
||||
<groupId>com.hierynomus</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<version>0.10.0</version>
|
||||
<version>0.13.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,4 +1,4 @@
|
||||
#Wed Jan 21 09:17:25 CET 2015
|
||||
#Wed Nov 04 09:47:51 CET 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
344
pom.xml
344
pom.xml
@@ -1,344 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright 2009 sshj contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<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>
|
||||
|
||||
<groupId>net.schmizz</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<packaging>bundle</packaging>
|
||||
<version>0.10.1-SNAPSHOT</version>
|
||||
|
||||
<name>sshj</name>
|
||||
<description>SSHv2 library for Java</description>
|
||||
<url>http://github.com/hierynomus/sshj</url>
|
||||
|
||||
<inceptionYear>2009</inceptionYear>
|
||||
|
||||
<issueManagement>
|
||||
<system>github</system>
|
||||
<url>http://github.com/hierynomus/sshj/issues</url>
|
||||
</issueManagement>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/hierynomus/sshj.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:hierynomus/sshj.git</developerConnection>
|
||||
<url>http://github.com/hierynomus/sshj</url>
|
||||
</scm>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
<artifactId>oss-parent</artifactId>
|
||||
<version>7</version>
|
||||
</parent>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.51</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<version>1.51</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>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jzlib</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-core</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>hierynomus</id>
|
||||
<name>Jeroen van Erp</name>
|
||||
<email>jeroen@hierynomus.com</email>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>shikhar</id>
|
||||
<name>Shikhar Bhushan</name>
|
||||
<email>shikhar@schmizz.net</email>
|
||||
<url>http://schmizz.net</url>
|
||||
</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>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<version>2.4.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Import-Package>
|
||||
!net.schmizz.*,
|
||||
javax.crypto*,
|
||||
com.jcraft.jzlib*;version="[1.1,2)";resolution:=optional,
|
||||
org.slf4j*;version="[1.7,5)",
|
||||
org.bouncycastle*,
|
||||
*
|
||||
</Import-Package>
|
||||
<Export-Package>net.schmizz.*</Export-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>clirr-maven-plugin</artifactId>
|
||||
<version>2.6.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<header>src/etc/license-header</header>
|
||||
<properties>
|
||||
<owner>sshj contributors</owner>
|
||||
<email>sshj-users@googlegroups.com</email>
|
||||
</properties>
|
||||
<excludes>
|
||||
<exclude>**/README</exclude>
|
||||
<exclude>src/test/resources/**</exclude>
|
||||
<exclude>src/main/resources/**</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- <plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<serverId>ossrh</serverId>
|
||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
|
||||
<autoReleaseAfterClose>true</autoReleaseAfterClose>
|
||||
</configuration>
|
||||
</plugin>
|
||||
--> </plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>sonatype-nexus-staging</id>
|
||||
<name>Nexus Release Repository</name>
|
||||
<url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>full-deps</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jzlib</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>doclint-java8-disable</id>
|
||||
<activation>
|
||||
<jdk>[1.8,)</jdk>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>clirr-maven-plugin</artifactId>
|
||||
<version>2.6.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
</project>
|
||||
@@ -1,39 +0,0 @@
|
||||
package nl.javadude.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class ChannelCloseEofTest {
|
||||
|
||||
private SSHClient sshClient;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
sshClient = new SSHClient();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException {
|
||||
sshClient.disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCorrectlyHandleSessionChannelEof() throws IOException, InterruptedException {
|
||||
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
|
||||
sshClient.connect("172.16.37.129");
|
||||
sshClient.authPassword("jeroen", "jeroen");
|
||||
Session session = sshClient.startSession();
|
||||
session.allocateDefaultPTY();
|
||||
session.close();
|
||||
Thread.sleep(1000);
|
||||
assertThat("Should still be connected", sshClient.isConnected());
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public class Sockets {
|
||||
/**
|
||||
* Java 7 and up have Socket implemented as Closeable, whereas Java6 did not have this inheritance.
|
||||
* @param socket The socket to wrap as Closeable
|
||||
* @return
|
||||
* @return The (potentially wrapped) Socket as a Closeable.
|
||||
*/
|
||||
public static Closeable asCloseable(final Socket socket) {
|
||||
if (Closeable.class.isAssignableFrom(socket.getClass())) {
|
||||
|
||||
@@ -31,8 +31,7 @@ import net.schmizz.sshj.transport.cipher.BlowfishCBC;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.cipher.TripleDESCBC;
|
||||
import net.schmizz.sshj.transport.compression.NoneCompression;
|
||||
import net.schmizz.sshj.transport.kex.DHG1;
|
||||
import net.schmizz.sshj.transport.kex.DHG14;
|
||||
import net.schmizz.sshj.transport.kex.*;
|
||||
import net.schmizz.sshj.transport.mac.HMACMD5;
|
||||
import net.schmizz.sshj.transport.mac.HMACMD596;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||
@@ -81,7 +80,7 @@ public class DefaultConfig
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private static final String VERSION = "SSHJ_0_9_2";
|
||||
private static final String VERSION = "SSHJ_0_14_0";
|
||||
|
||||
public DefaultConfig() {
|
||||
setVersion(VERSION);
|
||||
@@ -98,9 +97,16 @@ public class DefaultConfig
|
||||
|
||||
protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) {
|
||||
if (bouncyCastleRegistered)
|
||||
setKeyExchangeFactories(new DHG14.Factory(), new DHG1.Factory());
|
||||
setKeyExchangeFactories(new Curve25519SHA256.Factory(),
|
||||
new DHGexSHA256.Factory(),
|
||||
new ECDHNistP.Factory521(),
|
||||
new ECDHNistP.Factory384(),
|
||||
new ECDHNistP.Factory256(),
|
||||
new DHGexSHA1.Factory(),
|
||||
new DHG14.Factory(),
|
||||
new DHG1.Factory());
|
||||
else
|
||||
setKeyExchangeFactories(new DHG1.Factory());
|
||||
setKeyExchangeFactories(new DHG1.Factory(), new DHGexSHA1.Factory());
|
||||
}
|
||||
|
||||
protected void initRandomFactory(boolean bouncyCastleRegistered) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import net.schmizz.sshj.transport.TransportImpl;
|
||||
import net.schmizz.sshj.transport.compression.DelayedZlibCompression;
|
||||
import net.schmizz.sshj.transport.compression.NoneCompression;
|
||||
import net.schmizz.sshj.transport.compression.ZlibCompression;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
|
||||
import net.schmizz.sshj.userauth.UserAuth;
|
||||
@@ -49,6 +50,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
|
||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||
import net.schmizz.sshj.userauth.method.AuthMethod;
|
||||
import net.schmizz.sshj.userauth.method.AuthPassword;
|
||||
@@ -58,15 +60,19 @@ import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
|
||||
import org.ietf.jgss.Oid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.security.auth.login.LoginContext;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
@@ -153,10 +159,19 @@ public class SSHClient
|
||||
* Add a {@link HostKeyVerifier} which will be invoked for verifying host key during connection establishment and
|
||||
* future key exchanges.
|
||||
*
|
||||
* @param hostKeyVerifier {@link HostKeyVerifier} instance
|
||||
* @param verifier {@link HostKeyVerifier} instance
|
||||
*/
|
||||
public void addHostKeyVerifier(HostKeyVerifier hostKeyVerifier) {
|
||||
trans.addHostKeyVerifier(hostKeyVerifier);
|
||||
public void addHostKeyVerifier(HostKeyVerifier verifier) {
|
||||
trans.addHostKeyVerifier(verifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link AlgorithmsVerifier} which will be invoked for verifying negotiated algorithms.
|
||||
*
|
||||
* @param verifier {@link AlgorithmsVerifier} instance
|
||||
*/
|
||||
public void addAlgorithmsVerifier(AlgorithmsVerifier verifier) {
|
||||
trans.addAlgorithmsVerifier(verifier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,6 +380,30 @@ public class SSHClient
|
||||
authPublickey(username, keyProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "gssapi-with-mic"} authentication method, given a login context
|
||||
* for the peer GSS machine and a list of supported OIDs.
|
||||
* <p/>
|
||||
* Supported OIDs should be ordered by preference as the SSH server will choose the first OID that it also
|
||||
* supports. At least one OID is required
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param context {@code LoginContext} for the peer GSS machine
|
||||
* @param supportedOid first supported OID
|
||||
* @param supportedOids other supported OIDs
|
||||
*
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authGssApiWithMic(String username, LoginContext context, Oid supportedOid, Oid... supportedOids)
|
||||
throws UserAuthException, TransportException {
|
||||
// insert supportedOid to the front of the list since ordering matters
|
||||
List<Oid> oids = new ArrayList<Oid>(Arrays.asList(supportedOids));
|
||||
oids.add(0, supportedOid);
|
||||
|
||||
auth(username, new AuthGssApiWithMic(context, oids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the connected SSH server. {@code SSHClient} objects are not reusable therefore it is incorrect
|
||||
* to attempt connection after this method has been called.
|
||||
|
||||
@@ -61,7 +61,7 @@ public abstract class SocketClient {
|
||||
* @param port The port to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
|
||||
@@ -86,7 +86,7 @@ public abstract class SocketClient {
|
||||
* @param port The port to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(String hostname, int port, Proxy proxy) throws IOException {
|
||||
@@ -120,7 +120,7 @@ public abstract class SocketClient {
|
||||
* @param host The host address to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(InetAddress host, Proxy proxy) throws IOException {
|
||||
@@ -132,7 +132,7 @@ public abstract class SocketClient {
|
||||
* @param hostname The host name to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(String hostname, Proxy proxy) throws IOException {
|
||||
|
||||
@@ -135,7 +135,7 @@ public enum KeyType {
|
||||
BigInteger bigY = new BigInteger(1, y);
|
||||
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName("p-256");
|
||||
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY, false);
|
||||
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY);
|
||||
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(),
|
||||
ecParams.getG(), ecParams.getN());
|
||||
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
|
||||
|
||||
@@ -30,7 +30,7 @@ public enum Message {
|
||||
|
||||
KEXDH_INIT(30),
|
||||
|
||||
/** { KEXDH_REPLY, KEXDH_GEX_GROUP } */
|
||||
/** { KEXDH_REPLY, KEXDH_GEX_GROUP, SSH_MSG_KEX_ECDH_REPLY } */
|
||||
KEXDH_31(31),
|
||||
|
||||
KEX_DH_GEX_INIT(32),
|
||||
@@ -46,6 +46,9 @@ public enum Message {
|
||||
USERAUTH_60(60),
|
||||
USERAUTH_INFO_RESPONSE(61),
|
||||
|
||||
USERAUTH_GSSAPI_EXCHANGE_COMPLETE(63),
|
||||
USERAUTH_GSSAPI_MIC(66),
|
||||
|
||||
GLOBAL_REQUEST(80),
|
||||
REQUEST_SUCCESS(81),
|
||||
REQUEST_FAILURE(82),
|
||||
|
||||
@@ -66,7 +66,7 @@ public class ConnectionImpl
|
||||
*
|
||||
* @param config the ssh config
|
||||
* @param trans transport layer
|
||||
* @param keepAlive
|
||||
* @param keepAlive the keep alive provider
|
||||
*/
|
||||
public ConnectionImpl(Transport trans, KeepAliveProvider keepAlive) {
|
||||
super("ssh-connection", trans);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Copyright 2009 sshj contributors
|
||||
*
|
||||
* <p/>
|
||||
* 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
|
||||
*
|
||||
* <p/>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p/>
|
||||
* 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.
|
||||
@@ -19,6 +19,7 @@ import net.schmizz.concurrent.Promise;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.sftp.Response.StatusCode;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -33,37 +34,31 @@ public class RemoteFile
|
||||
super(requester, path, handle);
|
||||
}
|
||||
|
||||
public FileAttributes fetchAttributes()
|
||||
throws IOException {
|
||||
public FileAttributes fetchAttributes() throws IOException {
|
||||
return requester.request(newRequest(PacketType.FSTAT))
|
||||
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS)
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
public long length()
|
||||
throws IOException {
|
||||
public long length() throws IOException {
|
||||
return fetchAttributes().getSize();
|
||||
}
|
||||
|
||||
public void setLength(long len)
|
||||
throws IOException {
|
||||
public void setLength(long len) throws IOException {
|
||||
setAttributes(new FileAttributes.Builder().withSize(len).build());
|
||||
}
|
||||
|
||||
public int read(long fileOffset, byte[] to, int offset, int len)
|
||||
throws IOException {
|
||||
public int read(long fileOffset, byte[] to, int offset, int len) throws IOException {
|
||||
final Response res = asyncRead(fileOffset, len).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||
return checkReadResponse(res, to, offset);
|
||||
}
|
||||
|
||||
protected Promise<Response, SFTPException> asyncRead(long fileOffset, int len)
|
||||
throws IOException {
|
||||
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 {
|
||||
protected int checkReadResponse(Response res, byte[] to, int offset) throws Buffer.BufferException, SFTPException {
|
||||
switch (res.getType()) {
|
||||
case DATA:
|
||||
int recvLen = res.readUInt32AsInt();
|
||||
@@ -79,28 +74,25 @@ public class RemoteFile
|
||||
}
|
||||
}
|
||||
|
||||
public void write(long fileOffset, byte[] data, int off, int len)
|
||||
throws IOException {
|
||||
public void write(long fileOffset, byte[] data, int off, int len) throws IOException {
|
||||
checkWriteResponse(asyncWrite(fileOffset, data, off, len));
|
||||
}
|
||||
|
||||
protected Promise<Response, SFTPException> asyncWrite(long fileOffset, byte[] data, int off, int len)
|
||||
throws IOException {
|
||||
return requester.request(newRequest(PacketType.WRITE)
|
||||
.putUInt64(fileOffset)
|
||||
// TODO The SFTP spec claims this field is unneeded...? See #187
|
||||
.putUInt32(len)
|
||||
.putRawBytes(data, off, len)
|
||||
.putUInt64(fileOffset)
|
||||
// TODO The SFTP spec claims this field is unneeded...? See #187
|
||||
.putUInt32(len)
|
||||
.putRawBytes(data, off, len)
|
||||
);
|
||||
}
|
||||
|
||||
private void checkWriteResponse(Promise<Response, SFTPException> responsePromise)
|
||||
throws SFTPException {
|
||||
private void checkWriteResponse(Promise<Response, SFTPException> responsePromise) throws SFTPException {
|
||||
responsePromise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void setAttributes(FileAttributes attrs)
|
||||
throws IOException {
|
||||
public void setAttributes(FileAttributes attrs) throws IOException {
|
||||
requester.request(newRequest(PacketType.FSETSTAT).putFileAttributes(attrs))
|
||||
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
|
||||
}
|
||||
@@ -140,15 +132,13 @@ public class RemoteFile
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int w)
|
||||
throws IOException {
|
||||
public void write(int w) throws IOException {
|
||||
b[0] = (byte) w;
|
||||
write(b, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buf, int off, int len)
|
||||
throws IOException {
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
if (unconfirmedWrites.size() > maxUnconfirmedWrites) {
|
||||
checkWriteResponse(unconfirmedWrites.remove());
|
||||
}
|
||||
@@ -157,23 +147,20 @@ public class RemoteFile
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush()
|
||||
throws IOException {
|
||||
public void flush() throws IOException {
|
||||
while (!unconfirmedWrites.isEmpty()) {
|
||||
checkWriteResponse(unconfirmedWrites.remove());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
public void close() throws IOException {
|
||||
flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class RemoteFileInputStream
|
||||
extends InputStream {
|
||||
public class RemoteFileInputStream extends InputStream {
|
||||
|
||||
private final byte[] b = new byte[1];
|
||||
|
||||
@@ -201,31 +188,32 @@ public class RemoteFile
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
throws IOException {
|
||||
public void reset() throws IOException {
|
||||
fileOffset = markPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n)
|
||||
throws IOException {
|
||||
return (this.fileOffset = Math.min(fileOffset + n, length()));
|
||||
public long skip(long n) throws IOException {
|
||||
final long fileLength = length();
|
||||
final Long previousFileOffset = fileOffset;
|
||||
fileOffset = Math.min(fileOffset + n, fileLength);
|
||||
return fileOffset - previousFileOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read()
|
||||
throws IOException {
|
||||
public int read() throws IOException {
|
||||
return read(b, 0, 1) == -1 ? -1 : b[0] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] into, int off, int len)
|
||||
throws IOException {
|
||||
public int read(byte[] into, int off, int len) throws IOException {
|
||||
int read = RemoteFile.this.read(fileOffset, into, off, len);
|
||||
if (read != -1) {
|
||||
fileOffset += read;
|
||||
if (markPos != 0 && read > readLimit) // Invalidate mark position
|
||||
if (markPos != 0 && read > readLimit) {
|
||||
// Invalidate mark position
|
||||
markPos = 0;
|
||||
}
|
||||
}
|
||||
return read;
|
||||
}
|
||||
@@ -238,27 +226,56 @@ public class RemoteFile
|
||||
private final byte[] b = new byte[1];
|
||||
|
||||
private final int maxUnconfirmedReads;
|
||||
private final Queue<Promise<Response, SFTPException>> unconfirmedReads;
|
||||
private final Queue<Promise<Response, SFTPException>> unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
|
||||
private final Queue<Long> unconfirmedReadOffsets = new LinkedList<Long>();
|
||||
|
||||
private long fileOffset;
|
||||
private long requestOffset;
|
||||
private long responseOffset;
|
||||
private boolean eof;
|
||||
|
||||
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads) {
|
||||
assert 0 <= maxUnconfirmedReads;
|
||||
|
||||
this.maxUnconfirmedReads = maxUnconfirmedReads;
|
||||
this.unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
|
||||
this.fileOffset = 0;
|
||||
}
|
||||
|
||||
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset) {
|
||||
assert 0 <= maxUnconfirmedReads;
|
||||
assert 0 <= fileOffset;
|
||||
|
||||
this.maxUnconfirmedReads = maxUnconfirmedReads;
|
||||
this.unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
|
||||
this.fileOffset = fileOffset;
|
||||
this.requestOffset = this.responseOffset = fileOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n)
|
||||
throws IOException {
|
||||
throw new IOException("skip is not supported by ReadAheadFileInputStream, use RemoteFileInputStream instead");
|
||||
private ByteArrayInputStream pending = new ByteArrayInputStream(new byte[0]);
|
||||
|
||||
private boolean retrieveUnconfirmedRead(boolean blocking) throws IOException {
|
||||
if (unconfirmedReads.size() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!blocking && !unconfirmedReads.peek().isDelivered()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unconfirmedReadOffsets.remove();
|
||||
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||
switch (res.getType()) {
|
||||
case DATA:
|
||||
int recvLen = res.readUInt32AsInt();
|
||||
responseOffset += recvLen;
|
||||
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
|
||||
break;
|
||||
|
||||
case STATUS:
|
||||
res.ensureStatusIs(Response.StatusCode.EOF);
|
||||
eof = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new SFTPException("Unexpected packet: " + res.getType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -268,26 +285,66 @@ public class RemoteFile
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] into, int off, int len)
|
||||
throws IOException {
|
||||
while (!eof && unconfirmedReads.size() <= maxUnconfirmedReads) {
|
||||
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
|
||||
unconfirmedReads.add(asyncRead(fileOffset, len));
|
||||
fileOffset += len;
|
||||
public int read(byte[] into, int off, int len) throws IOException {
|
||||
|
||||
while (!eof && pending.available() <= 0) {
|
||||
|
||||
// we also need to go here for len <= 0, because pending may be at
|
||||
// EOF in which case it would return -1 instead of 0
|
||||
|
||||
while (unconfirmedReads.size() <= maxUnconfirmedReads) {
|
||||
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
|
||||
int reqLen = Math.max(1024, len); // don't be shy!
|
||||
unconfirmedReads.add(RemoteFile.this.asyncRead(requestOffset, reqLen));
|
||||
unconfirmedReadOffsets.add(requestOffset);
|
||||
requestOffset += reqLen;
|
||||
}
|
||||
|
||||
long nextOffset = unconfirmedReadOffsets.peek();
|
||||
if (responseOffset != nextOffset) {
|
||||
|
||||
// the server could not give us all the data we needed, so
|
||||
// we try to fill the gap synchronously
|
||||
|
||||
assert responseOffset < nextOffset;
|
||||
assert 0 < (nextOffset - responseOffset);
|
||||
assert (nextOffset - responseOffset) <= Integer.MAX_VALUE;
|
||||
|
||||
byte[] buf = new byte[(int) (nextOffset - responseOffset)];
|
||||
int recvLen = RemoteFile.this.read(responseOffset, buf, 0, buf.length);
|
||||
|
||||
if (recvLen < 0) {
|
||||
eof = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 == recvLen) {
|
||||
// avoid infinite loops
|
||||
throw new SFTPException("Unexpected response size (0), bailing out");
|
||||
}
|
||||
|
||||
responseOffset += recvLen;
|
||||
pending = new ByteArrayInputStream(buf, 0, recvLen);
|
||||
} else if (!retrieveUnconfirmedRead(true /*blocking*/)) {
|
||||
|
||||
// this may happen if we change prefetch strategy
|
||||
// currently, we should never get here...
|
||||
|
||||
throw new IllegalStateException("Could not retrieve data for pending read request");
|
||||
}
|
||||
}
|
||||
if (unconfirmedReads.isEmpty()) {
|
||||
assert eof;
|
||||
return -1;
|
||||
}
|
||||
// Retrieve first in
|
||||
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||
final int recvLen = checkReadResponse(res, into, off);
|
||||
if (recvLen == -1) {
|
||||
eof = true;
|
||||
}
|
||||
return recvLen;
|
||||
|
||||
return pending.read(into, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
boolean lastRead = true;
|
||||
while (!eof && (pending.available() <= 0) && lastRead) {
|
||||
lastRead = retrieveUnconfirmedRead(false /*blocking*/);
|
||||
}
|
||||
return pending.available();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import net.schmizz.sshj.transport.digest.Digest;
|
||||
import net.schmizz.sshj.transport.kex.KeyExchange;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -68,6 +70,8 @@ final class KeyExchanger
|
||||
*/
|
||||
private final Queue<HostKeyVerifier> hostVerifiers = new LinkedList<HostKeyVerifier>();
|
||||
|
||||
private final Queue<AlgorithmsVerifier> algorithmVerifiers = new LinkedList<AlgorithmsVerifier>();
|
||||
|
||||
private final AtomicBoolean kexOngoing = new AtomicBoolean();
|
||||
|
||||
/** What we are expecting from the next packet */
|
||||
@@ -108,6 +112,10 @@ final class KeyExchanger
|
||||
hostVerifiers.add(hkv);
|
||||
}
|
||||
|
||||
synchronized void addAlgorithmsVerifier(AlgorithmsVerifier verifier) {
|
||||
algorithmVerifiers.add(verifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session identifier computed during key exchange.
|
||||
*
|
||||
@@ -218,6 +226,13 @@ final class KeyExchanger
|
||||
final Proposal serverProposal = new Proposal(buf);
|
||||
negotiatedAlgs = clientProposal.negotiate(serverProposal);
|
||||
log.debug("Negotiated algorithms: {}", negotiatedAlgs);
|
||||
for(AlgorithmsVerifier v: algorithmVerifiers) {
|
||||
log.debug("Trying to verify algorithms with {}", v);
|
||||
if(!v.verify(negotiatedAlgs)) {
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED,
|
||||
"Failed to verify negotiated algorithms `" + negotiatedAlgs + "`");
|
||||
}
|
||||
}
|
||||
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(),
|
||||
negotiatedAlgs.getKeyExchangeAlgorithm());
|
||||
try {
|
||||
|
||||
@@ -48,23 +48,25 @@ public final class Reader
|
||||
int read;
|
||||
try {
|
||||
read = inp.read(recvbuf, 0, needed);
|
||||
}
|
||||
catch(SocketTimeoutException e) {
|
||||
} catch(SocketTimeoutException e) {
|
||||
if (isInterrupted()) {
|
||||
throw e;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (read == -1)
|
||||
if (read == -1) {
|
||||
throw new TransportException("Broken transport; encountered EOF");
|
||||
else
|
||||
} else {
|
||||
needed = decoder.received(recvbuf, read);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (isInterrupted()) {
|
||||
// We are meant to shut up and draw to a close if interrupted
|
||||
} else
|
||||
} else {
|
||||
trans.die(e);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Stopping");
|
||||
|
||||
@@ -20,6 +20,7 @@ import net.schmizz.sshj.Service;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.SSHPacketHandler;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -51,6 +52,13 @@ public interface Transport
|
||||
*/
|
||||
void addHostKeyVerifier(HostKeyVerifier hkv);
|
||||
|
||||
/**
|
||||
* Adds the specified verifier.
|
||||
*
|
||||
* @param verifier The verifier to call with negotiated algorithms
|
||||
*/
|
||||
void addAlgorithmsVerifier(AlgorithmsVerifier verifier);
|
||||
|
||||
/**
|
||||
* Do key exchange and algorithm negotiation. This can be the initial one or for algorithm renegotiation.
|
||||
*
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -235,6 +236,11 @@ public final class TransportImpl
|
||||
kexer.addHostKeyVerifier(hkv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAlgorithmsVerifier(AlgorithmsVerifier verifier) {
|
||||
kexer.addAlgorithmsVerifier(verifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doKex()
|
||||
throws TransportException {
|
||||
|
||||
26
src/main/java/net/schmizz/sshj/transport/digest/SHA256.java
Normal file
26
src/main/java/net/schmizz/sshj/transport/digest/SHA256.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package net.schmizz.sshj.transport.digest;
|
||||
|
||||
/** SHA256 Digest. */
|
||||
public class SHA256 extends BaseDigest {
|
||||
|
||||
/** Named factory for SHA256 digest */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Digest> {
|
||||
|
||||
@Override
|
||||
public Digest create() {
|
||||
return new SHA256();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "sha256";
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a new instance of a SHA256 digest */
|
||||
public SHA256() {
|
||||
super("SHA-256", 32);
|
||||
}
|
||||
|
||||
}
|
||||
23
src/main/java/net/schmizz/sshj/transport/digest/SHA384.java
Normal file
23
src/main/java/net/schmizz/sshj/transport/digest/SHA384.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package net.schmizz.sshj.transport.digest;
|
||||
|
||||
public class SHA384 extends BaseDigest {
|
||||
/** Named factory for SHA384 digest */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Digest> {
|
||||
|
||||
@Override
|
||||
public Digest create() {
|
||||
return new SHA384();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "sha384";
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a new instance of a SHA384 digest */
|
||||
public SHA384() {
|
||||
super("SHA-384", 48);
|
||||
}
|
||||
}
|
||||
23
src/main/java/net/schmizz/sshj/transport/digest/SHA512.java
Normal file
23
src/main/java/net/schmizz/sshj/transport/digest/SHA512.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package net.schmizz.sshj.transport.digest;
|
||||
|
||||
public class SHA512 extends BaseDigest {
|
||||
/** Named factory for SHA384 digest */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Digest> {
|
||||
|
||||
@Override
|
||||
public Digest create() {
|
||||
return new SHA512();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "sha512";
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a new instance of a SHA384 digest */
|
||||
public SHA512() {
|
||||
super("SHA-512", 64);
|
||||
}
|
||||
}
|
||||
21
src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java
Normal file
21
src/main/java/net/schmizz/sshj/transport/kex/AbstractDH.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class AbstractDH extends KeyExchangeBase {
|
||||
protected final DHBase dh;
|
||||
|
||||
public AbstractDH(DHBase dh, Digest digest) {
|
||||
super(digest);
|
||||
this.dh = dh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getK() {
|
||||
return dh.getK();
|
||||
}
|
||||
}
|
||||
@@ -38,57 +38,24 @@ import java.util.Arrays;
|
||||
* Base class for DHG key exchange algorithms. Implementations will only have to configure the required data on the
|
||||
* {@link DH} class in the
|
||||
*/
|
||||
public abstract class AbstractDHG
|
||||
public abstract class AbstractDHG extends AbstractDH
|
||||
implements KeyExchange {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private Transport trans;
|
||||
|
||||
private final Digest sha1 = new SHA1();
|
||||
private final DH dh = new DH();
|
||||
|
||||
private String V_S;
|
||||
private String V_C;
|
||||
private byte[] I_S;
|
||||
private byte[] I_C;
|
||||
|
||||
private byte[] H;
|
||||
private PublicKey hostKey;
|
||||
|
||||
@Override
|
||||
public byte[] getH() {
|
||||
return Arrays.copyOf(H, H.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getK() {
|
||||
return dh.getK();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Digest getHash() {
|
||||
return sha1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getHostKey() {
|
||||
return hostKey;
|
||||
public AbstractDHG(DHBase dhBase, Digest digest) {
|
||||
super(dhBase, digest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C)
|
||||
throws GeneralSecurityException, TransportException {
|
||||
this.trans = trans;
|
||||
this.V_S = V_S;
|
||||
this.V_C = V_C;
|
||||
this.I_S = Arrays.copyOf(I_S, I_S.length);
|
||||
this.I_C = Arrays.copyOf(I_C, I_C.length);
|
||||
sha1.init();
|
||||
super.init(trans, V_S, V_C, I_S, I_C);
|
||||
digest.init();
|
||||
initDH(dh);
|
||||
|
||||
log.debug("Sending SSH_MSG_KEXDH_INIT");
|
||||
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(dh.getE()));
|
||||
trans.write(new SSHPacket(Message.KEXDH_INIT).putBytes(dh.getE()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,11 +66,11 @@ public abstract class AbstractDHG
|
||||
|
||||
log.debug("Received SSH_MSG_KEXDH_REPLY");
|
||||
final byte[] K_S;
|
||||
final BigInteger f;
|
||||
final byte[] f;
|
||||
final byte[] sig; // signature sent by server
|
||||
try {
|
||||
K_S = packet.readBytes();
|
||||
f = packet.readMPInt();
|
||||
f = packet.readBytes();
|
||||
sig = packet.readBytes();
|
||||
hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
} catch (Buffer.BufferException be) {
|
||||
@@ -112,17 +79,13 @@ public abstract class AbstractDHG
|
||||
|
||||
dh.computeK(f);
|
||||
|
||||
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer()
|
||||
.putString(V_C)
|
||||
.putString(V_S)
|
||||
.putString(I_C)
|
||||
.putString(I_S)
|
||||
final Buffer.PlainBuffer buf = initializedBuffer()
|
||||
.putString(K_S)
|
||||
.putMPInt(dh.getE())
|
||||
.putMPInt(f)
|
||||
.putBytes(dh.getE())
|
||||
.putBytes(f)
|
||||
.putMPInt(dh.getK());
|
||||
sha1.update(buf.array(), buf.rpos(), buf.available());
|
||||
H = sha1.digest();
|
||||
digest.update(buf.array(), buf.rpos(), buf.available());
|
||||
H = digest.digest();
|
||||
|
||||
Signature signature = Factory.Named.Util.create(trans.getConfig().getSignatureFactories(),
|
||||
KeyType.fromKey(hostKey).toString());
|
||||
@@ -134,7 +97,7 @@ public abstract class AbstractDHG
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void initDH(DH dh)
|
||||
protected abstract void initDH(DHBase dh)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.signature.Signature;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public abstract class AbstractDHGex extends AbstractDH {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private int minBits = 1024;
|
||||
private int maxBits = 8192;
|
||||
private int preferredBits = 2048;
|
||||
|
||||
public AbstractDHGex(Digest digest) {
|
||||
super(new DH(), digest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C) throws GeneralSecurityException, TransportException {
|
||||
super.init(trans, V_S, V_C, I_S, I_C);
|
||||
digest.init();
|
||||
|
||||
log.debug("Sending {}", Message.KEX_DH_GEX_REQUEST);
|
||||
trans.write(new SSHPacket(Message.KEX_DH_GEX_REQUEST).putUInt32(minBits).putUInt32(preferredBits).putUInt32(maxBits));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean next(Message msg, SSHPacket buffer) throws GeneralSecurityException, TransportException {
|
||||
log.debug("Got message {}", msg);
|
||||
try {
|
||||
switch (msg) {
|
||||
case KEXDH_31:
|
||||
return parseGexGroup(buffer);
|
||||
case KEX_DH_GEX_REPLY:
|
||||
return parseGexReply(buffer);
|
||||
}
|
||||
} catch (Buffer.BufferException be) {
|
||||
throw new TransportException(be);
|
||||
}
|
||||
throw new TransportException("Unexpected message " + msg);
|
||||
}
|
||||
|
||||
private boolean parseGexReply(SSHPacket buffer) throws Buffer.BufferException, GeneralSecurityException, TransportException {
|
||||
byte[] K_S = buffer.readBytes();
|
||||
byte[] f = buffer.readBytes();
|
||||
byte[] sig = buffer.readBytes();
|
||||
hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
|
||||
dh.computeK(f);
|
||||
BigInteger k = dh.getK();
|
||||
|
||||
final Buffer.PlainBuffer buf = initializedBuffer()
|
||||
.putString(K_S)
|
||||
.putUInt32(minBits)
|
||||
.putUInt32(preferredBits)
|
||||
.putUInt32(maxBits)
|
||||
.putMPInt(((DH) dh).getP())
|
||||
.putMPInt(((DH) dh).getG())
|
||||
.putBytes(dh.getE())
|
||||
.putBytes(f)
|
||||
.putMPInt(k);
|
||||
digest.update(buf.array(), buf.rpos(), buf.available());
|
||||
H = digest.digest();
|
||||
Signature signature = Factory.Named.Util.create(trans.getConfig().getSignatureFactories(),
|
||||
KeyType.fromKey(hostKey).toString());
|
||||
signature.init(hostKey, null);
|
||||
signature.update(H, 0, H.length);
|
||||
if (!signature.verify(sig))
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED,
|
||||
"KeyExchange signature verification failed");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private boolean parseGexGroup(SSHPacket buffer) throws Buffer.BufferException, GeneralSecurityException, TransportException {
|
||||
BigInteger p = buffer.readMPInt();
|
||||
BigInteger g = buffer.readMPInt();
|
||||
int bitLength = p.bitLength();
|
||||
if (bitLength < minBits || bitLength > maxBits) {
|
||||
throw new GeneralSecurityException("Server generated gex p is out of range (" + bitLength + " bits)");
|
||||
}
|
||||
log.debug("Received server p bitlength {}", bitLength);
|
||||
dh.init(new DHParameterSpec(p, g));
|
||||
log.debug("Sending {}", Message.KEX_DH_GEX_INIT);
|
||||
trans.write(new SSHPacket(Message.KEX_DH_GEX_INIT).putBytes(dh.getE()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.math.ec.ECFieldElement;
|
||||
import org.bouncycastle.math.ec.custom.djb.Curve25519;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
||||
public class Curve25519DH extends DHBase {
|
||||
|
||||
|
||||
private byte[] secretKey;
|
||||
|
||||
public Curve25519DH() {
|
||||
super("ECDSA", "ECDH");
|
||||
}
|
||||
|
||||
@Override
|
||||
void computeK(byte[] f) throws GeneralSecurityException {
|
||||
byte[] k = new byte[32];
|
||||
djb.Curve25519.curve(k, secretKey, f);
|
||||
setK(new BigInteger(1, k));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(AlgorithmParameterSpec params) throws GeneralSecurityException {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] secretBytes = new byte[32];
|
||||
secureRandom.nextBytes(secretBytes);
|
||||
byte[] publicBytes = new byte[32];
|
||||
djb.Curve25519.keygen(publicBytes, null, secretBytes);
|
||||
this.secretKey = Arrays.copyOf(secretBytes, secretBytes.length);
|
||||
setE(publicBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO want to figure out why BouncyCastle does not work.
|
||||
* @return
|
||||
*/
|
||||
public static AlgorithmParameterSpec getCurve25519Params() {
|
||||
X9ECParameters ecP = CustomNamedCurves.getByName("curve25519");
|
||||
return new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.signature.Signature;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.transport.digest.SHA256;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class Curve25519SHA256 extends AbstractDHG {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Curve25519SHA256.class);
|
||||
|
||||
/** Named factory for Curve25519SHA256 key exchange */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new Curve25519SHA256();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "curve25519-sha256@libssh.org";
|
||||
}
|
||||
}
|
||||
|
||||
public Curve25519SHA256() {
|
||||
super(new Curve25519DH(), new SHA256());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DHBase dh) throws GeneralSecurityException {
|
||||
dh.init(Curve25519DH.getCurve25519Params());
|
||||
}
|
||||
}
|
||||
@@ -18,59 +18,51 @@ package net.schmizz.sshj.transport.kex;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/** Diffie-Hellman key generator. */
|
||||
public class DH {
|
||||
public class DH extends DHBase {
|
||||
|
||||
private BigInteger p;
|
||||
private BigInteger g;
|
||||
private BigInteger e; // my public key
|
||||
private BigInteger K; // shared secret key
|
||||
private final KeyPairGenerator generator;
|
||||
private final KeyAgreement agreement;
|
||||
|
||||
public DH() {
|
||||
try {
|
||||
generator = SecurityUtils.getKeyPairGenerator("DH");
|
||||
agreement = SecurityUtils.getKeyAgreement("DH");
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
super("DH", "DH");
|
||||
}
|
||||
|
||||
public void init(BigInteger p, BigInteger g)
|
||||
throws GeneralSecurityException {
|
||||
this.p = p;
|
||||
this.g = g;
|
||||
generator.initialize(new DHParameterSpec(p, g));
|
||||
@Override
|
||||
protected void init(AlgorithmParameterSpec params) throws GeneralSecurityException {
|
||||
if (!(params instanceof DHParameterSpec)) {
|
||||
throw new SSHRuntimeException("Wrong algorithm parameters for Diffie Hellman");
|
||||
}
|
||||
this.p = ((DHParameterSpec) params).getP();
|
||||
this.g = ((DHParameterSpec) params).getG();
|
||||
generator.initialize(params);
|
||||
final KeyPair kp = generator.generateKeyPair();
|
||||
agreement.init(kp.getPrivate());
|
||||
e = ((javax.crypto.interfaces.DHPublicKey) kp.getPublic()).getY();
|
||||
setE(((javax.crypto.interfaces.DHPublicKey) kp.getPublic()).getY().toByteArray());
|
||||
}
|
||||
|
||||
public void computeK(BigInteger f)
|
||||
throws GeneralSecurityException {
|
||||
@Override
|
||||
void computeK(byte[] f) throws GeneralSecurityException {
|
||||
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("DH");
|
||||
final PublicKey yourPubKey = keyFactory.generatePublic(new DHPublicKeySpec(f, p, g));
|
||||
final PublicKey yourPubKey = keyFactory.generatePublic(new DHPublicKeySpec(new BigInteger(f), p, g));
|
||||
agreement.doPhase(yourPubKey, true);
|
||||
K = new BigInteger(1, agreement.generateSecret());
|
||||
setK(new BigInteger(1, agreement.generateSecret()));
|
||||
}
|
||||
|
||||
public BigInteger getE() {
|
||||
return e;
|
||||
public BigInteger getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
public BigInteger getK() {
|
||||
return K;
|
||||
public BigInteger getG() {
|
||||
return g;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
47
src/main/java/net/schmizz/sshj/transport/kex/DHBase.java
Normal file
47
src/main/java/net/schmizz/sshj/transport/kex/DHBase.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
abstract class DHBase {
|
||||
protected final KeyPairGenerator generator;
|
||||
protected final KeyAgreement agreement;
|
||||
|
||||
private byte[] e; // my public key
|
||||
private BigInteger K; // shared secret key
|
||||
|
||||
public DHBase(String generator, String agreement) {
|
||||
try {
|
||||
this.generator = SecurityUtils.getKeyPairGenerator(generator);
|
||||
this.agreement = SecurityUtils.getKeyAgreement(agreement);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void computeK(byte[] f) throws GeneralSecurityException;
|
||||
|
||||
protected abstract void init(AlgorithmParameterSpec params) throws GeneralSecurityException;
|
||||
|
||||
void setE(byte[] e) {
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
void setK(BigInteger k) {
|
||||
K = k;
|
||||
}
|
||||
|
||||
public byte[] getE() {
|
||||
return e;
|
||||
}
|
||||
|
||||
public BigInteger getK() {
|
||||
return K;
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,17 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.transport.digest.SHA1;
|
||||
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Diffie-Hellman key exchange with SHA-1 and Oakley Group 2 [RFC2409] (1024-bit MODP Group).
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc4253.txt">RFC 4253</a>
|
||||
*
|
||||
* TODO refactor away the (unneeded) class
|
||||
*/
|
||||
public class DHG1
|
||||
extends AbstractDHG {
|
||||
@@ -38,13 +43,14 @@ public class DHG1
|
||||
public String getName() {
|
||||
return "diffie-hellman-group1-sha1";
|
||||
}
|
||||
}
|
||||
|
||||
public DHG1() {
|
||||
super(new DH(), new SHA1());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DH dh)
|
||||
throws GeneralSecurityException {
|
||||
dh.init(DHGroupData.P1, DHGroupData.G);
|
||||
protected void initDH(DHBase dh) throws GeneralSecurityException {
|
||||
dh.init(new DHParameterSpec(DHGroupData.P1, DHGroupData.G));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.transport.digest.SHA1;
|
||||
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
@@ -42,10 +45,12 @@ public class DHG14
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DH dh)
|
||||
throws GeneralSecurityException {
|
||||
dh.init(DHGroupData.P14, DHGroupData.G);
|
||||
public DHG14() {
|
||||
super(new DH(), new SHA1());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DHBase dh) throws GeneralSecurityException {
|
||||
dh.init(new DHParameterSpec(DHGroupData.P14, DHGroupData.G));
|
||||
}
|
||||
}
|
||||
|
||||
25
src/main/java/net/schmizz/sshj/transport/kex/DHGexSHA1.java
Normal file
25
src/main/java/net/schmizz/sshj/transport/kex/DHGexSHA1.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.transport.digest.SHA1;
|
||||
|
||||
public class DHGexSHA1 extends AbstractDHGex {
|
||||
|
||||
/** Named factory for DHGexSHA1 key exchange */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new DHGexSHA1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "diffie-hellman-group-exchange-sha1";
|
||||
}
|
||||
}
|
||||
|
||||
public DHGexSHA1() {
|
||||
super(new SHA1());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.transport.digest.SHA256;
|
||||
|
||||
public class DHGexSHA256 extends AbstractDHGex {
|
||||
|
||||
/** Named factory for DHGexSHA256 key exchange */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new DHGexSHA256();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "diffie-hellman-group-exchange-sha256";
|
||||
}
|
||||
}
|
||||
|
||||
public DHGexSHA256() {
|
||||
super(new SHA256());
|
||||
}
|
||||
}
|
||||
46
src/main/java/net/schmizz/sshj/transport/kex/ECDH.java
Normal file
46
src/main/java/net/schmizz/sshj/transport/kex/ECDH.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static net.schmizz.sshj.transport.kex.SecgUtils.getDecoded;
|
||||
import static net.schmizz.sshj.transport.kex.SecgUtils.getEncoded;
|
||||
|
||||
public class ECDH extends DHBase {
|
||||
|
||||
private ECParameterSpec ecParameterSpec;
|
||||
|
||||
public ECDH() {
|
||||
super("EC", "ECDH");
|
||||
}
|
||||
|
||||
protected void init(AlgorithmParameterSpec params) throws GeneralSecurityException {
|
||||
generator.initialize(params);
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
agreement.init(keyPair.getPrivate());
|
||||
ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();
|
||||
this.ecParameterSpec = ecPublicKey.getParams();
|
||||
ECPoint w = ecPublicKey.getW();
|
||||
byte[] encoded = getEncoded(w, ecParameterSpec.getCurve());
|
||||
setE(encoded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeK(byte[] f) throws GeneralSecurityException {
|
||||
KeyFactory keyFactory = SecurityUtils.getKeyFactory("EC");
|
||||
ECPublicKeySpec keySpec = new ECPublicKeySpec(getDecoded(f, ecParameterSpec.getCurve()), ecParameterSpec);
|
||||
PublicKey yourPubKey = keyFactory.generatePublic(keySpec);
|
||||
agreement.doPhase(yourPubKey, true);
|
||||
setK(new BigInteger(1, agreement.generateSecret()));
|
||||
}
|
||||
|
||||
}
|
||||
70
src/main/java/net/schmizz/sshj/transport/kex/ECDHNistP.java
Normal file
70
src/main/java/net/schmizz/sshj/transport/kex/ECDHNistP.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
import net.schmizz.sshj.transport.digest.SHA256;
|
||||
import net.schmizz.sshj.transport.digest.SHA384;
|
||||
import net.schmizz.sshj.transport.digest.SHA512;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class ECDHNistP extends AbstractDHG {
|
||||
|
||||
private String curve;
|
||||
|
||||
/** Named factory for ECDHNistP key exchange */
|
||||
public static class Factory521
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new ECDHNistP("P-521", new SHA512());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ecdh-sha2-nistp521";
|
||||
}
|
||||
}
|
||||
|
||||
/** Named factory for ECDHNistP key exchange */
|
||||
public static class Factory384
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new ECDHNistP("P-384", new SHA384());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ecdh-sha2-nistp384";
|
||||
}
|
||||
}
|
||||
|
||||
/** Named factory for ECDHNistP key exchange */
|
||||
public static class Factory256
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new ECDHNistP("P-256", new SHA256());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ecdh-sha2-nistp256";
|
||||
}
|
||||
}
|
||||
|
||||
public ECDHNistP(String curve, Digest digest) {
|
||||
super(new ECDH(), digest);
|
||||
this.curve = curve;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DHBase dh) throws GeneralSecurityException {
|
||||
dh.init(new ECNamedCurveGenParameterSpec(curve));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class KeyExchangeBase implements KeyExchange {
|
||||
protected Transport trans;
|
||||
|
||||
protected final Digest digest;
|
||||
protected byte[] H;
|
||||
protected PublicKey hostKey;
|
||||
|
||||
private String V_S;
|
||||
private String V_C;
|
||||
private byte[] I_S;
|
||||
private byte[] I_C;
|
||||
|
||||
public KeyExchangeBase(Digest digest) {
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C) throws GeneralSecurityException, TransportException {
|
||||
this.trans = trans;
|
||||
this.V_S = V_S;
|
||||
this.V_C = V_C;
|
||||
this.I_S = Arrays.copyOf(I_S, I_S.length);
|
||||
this.I_C = Arrays.copyOf(I_C, I_C.length);
|
||||
}
|
||||
|
||||
protected Buffer.PlainBuffer initializedBuffer() {
|
||||
return new Buffer.PlainBuffer()
|
||||
.putString(V_C)
|
||||
.putString(V_S)
|
||||
.putString(I_C)
|
||||
.putString(I_S);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getH() {
|
||||
return Arrays.copyOf(H, H.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Digest getHash() {
|
||||
return digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getHostKey() {
|
||||
return hostKey;
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/net/schmizz/sshj/transport/kex/SecgUtils.java
Normal file
55
src/main/java/net/schmizz/sshj/transport/kex/SecgUtils.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.EllipticCurve;
|
||||
import java.util.Arrays;
|
||||
|
||||
class SecgUtils {
|
||||
/**
|
||||
* SECG 2.3.4 Octet String to ECPoint
|
||||
*/
|
||||
static ECPoint getDecoded(byte[] M, EllipticCurve curve) {
|
||||
int elementSize = getElementSize(curve);
|
||||
if (M.length != 2 * elementSize + 1 || M[0] != 0x04) {
|
||||
throw new SSHRuntimeException("Invalid 'f' for Elliptic Curve " + curve.toString());
|
||||
}
|
||||
byte[] xBytes = new byte[elementSize];
|
||||
byte[] yBytes = new byte[elementSize];
|
||||
System.arraycopy(M, 1, xBytes, 0, elementSize);
|
||||
System.arraycopy(M, 1 + elementSize, yBytes, 0, elementSize);
|
||||
return new ECPoint(new BigInteger(1, xBytes), new BigInteger(1, yBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* SECG 2.3.3 ECPoint to Octet String
|
||||
*/
|
||||
static byte[] getEncoded(ECPoint point, EllipticCurve curve) {
|
||||
int elementSize = getElementSize(curve);
|
||||
byte[] M = new byte[2 * elementSize + 1];
|
||||
M[0] = 0x04;
|
||||
|
||||
byte[] xBytes = stripLeadingZeroes(point.getAffineX().toByteArray());
|
||||
byte[] yBytes = stripLeadingZeroes(point.getAffineY().toByteArray());
|
||||
System.arraycopy(xBytes, 0, M, 1 + elementSize - xBytes.length, xBytes.length);
|
||||
System.arraycopy(yBytes, 0, M, 1 + 2 * elementSize - yBytes.length, yBytes.length);
|
||||
return M;
|
||||
}
|
||||
|
||||
private static byte[] stripLeadingZeroes(byte[] bytes) {
|
||||
int start = 0;
|
||||
while (bytes[start] == 0x0) {
|
||||
start++;
|
||||
}
|
||||
|
||||
return Arrays.copyOfRange(bytes, start, bytes.length);
|
||||
}
|
||||
|
||||
private static int getElementSize(EllipticCurve curve) {
|
||||
int fieldSize = curve.getField().getFieldSize();
|
||||
return (fieldSize + 7) / 8;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2009 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.transport.verification;
|
||||
|
||||
import net.schmizz.sshj.transport.NegotiatedAlgorithms;
|
||||
|
||||
public interface AlgorithmsVerifier {
|
||||
|
||||
/**
|
||||
* Callback is invoked when algorithms have been negotiated between client and server.
|
||||
* @return False to interrupt the connection
|
||||
*/
|
||||
boolean verify(NegotiatedAlgorithms algorithms);
|
||||
}
|
||||
@@ -352,7 +352,7 @@ public class OpenSSHKnownHosts
|
||||
@Override
|
||||
public boolean appliesTo(KeyType type, String host)
|
||||
throws IOException {
|
||||
return type == this.type && hostnames.contains(host);
|
||||
return type == this.type && hosts.contains(host);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@ public class KeyProviderUtil {
|
||||
* @return name of the key file format
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static KeyFormat detectKeyFileFormat(String privateKey,
|
||||
boolean separatePubKey)
|
||||
public static KeyFormat detectKeyFileFormat(String privateKey, boolean separatePubKey)
|
||||
throws IOException {
|
||||
return detectKeyFileFormat(new StringReader(privateKey), separatePubKey);
|
||||
}
|
||||
@@ -67,35 +66,44 @@ public class KeyProviderUtil {
|
||||
* @return name of the key file format
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static KeyFormat detectKeyFileFormat(Reader privateKey,
|
||||
boolean separatePubKey)
|
||||
public static KeyFormat detectKeyFileFormat(Reader privateKey, boolean separatePubKey)
|
||||
throws IOException {
|
||||
BufferedReader br = new BufferedReader(privateKey);
|
||||
final String firstLine;
|
||||
try {
|
||||
firstLine = br.readLine();
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(br);
|
||||
}
|
||||
if(firstLine == null) {
|
||||
String header = readHeader(privateKey);
|
||||
if (header == null) {
|
||||
throw new IOException("Empty file");
|
||||
}
|
||||
if(firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----")) {
|
||||
if(separatePubKey)
|
||||
// Can delay asking for password since have unencrypted pubkey
|
||||
{
|
||||
return KeyFormat.OpenSSH;
|
||||
return keyFormatFromHeader(header, separatePubKey);
|
||||
}
|
||||
|
||||
private static String readHeader(Reader privateKey) throws IOException {
|
||||
BufferedReader br = new BufferedReader(privateKey);
|
||||
try {
|
||||
String header;
|
||||
while ((header = br.readLine()) != null) {
|
||||
header = header.trim();
|
||||
if (!header.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
// More general
|
||||
{
|
||||
return header;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(br);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyFormat keyFormatFromHeader(String header, boolean separatePubKey) {
|
||||
if (header.startsWith("-----BEGIN") && header.endsWith("PRIVATE KEY-----")) {
|
||||
if (separatePubKey) {
|
||||
// Can delay asking for password since have unencrypted pubkey
|
||||
return KeyFormat.OpenSSH;
|
||||
} else {
|
||||
// More general
|
||||
return KeyFormat.PKCS8;
|
||||
}
|
||||
}
|
||||
if(firstLine.startsWith("PuTTY-User-Key-File-")) {
|
||||
} else if (header.startsWith("PuTTY-User-Key-File-")) {
|
||||
return KeyFormat.PuTTY;
|
||||
} else {
|
||||
return KeyFormat.Unknown;
|
||||
}
|
||||
return KeyFormat.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package net.schmizz.sshj.userauth.method;
|
||||
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.List;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||
import net.schmizz.sshj.common.Buffer.PlainBuffer;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.userauth.UserAuthException;
|
||||
|
||||
/** Implements authentication by GSS-API. */
|
||||
public class AuthGssApiWithMic
|
||||
extends AbstractAuthMethod {
|
||||
|
||||
private final LoginContext loginContext;
|
||||
private final List<Oid> mechanismOids;
|
||||
private final GSSManager manager;
|
||||
|
||||
private GSSContext secContext;
|
||||
|
||||
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids) {
|
||||
this(loginContext, mechanismOids, GSSManager.getInstance());
|
||||
}
|
||||
|
||||
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids, GSSManager manager) {
|
||||
super("gssapi-with-mic");
|
||||
this.loginContext = loginContext;
|
||||
this.mechanismOids = mechanismOids;
|
||||
this.manager = manager;
|
||||
|
||||
secContext = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSHPacket buildReq()
|
||||
throws UserAuthException {
|
||||
SSHPacket packet = super.buildReq() // the generic stuff
|
||||
.putUInt32(mechanismOids.size()); // number of OIDs we support
|
||||
for (Oid oid : mechanismOids) {
|
||||
try {
|
||||
packet.putString(oid.getDER());
|
||||
} catch (GSSException e) {
|
||||
throw new UserAuthException("Mechanism OID could not be encoded: " + oid.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* PrivilegedExceptionAction to be executed within the given LoginContext for
|
||||
* initializing the GSSContext.
|
||||
*
|
||||
* @author Ben Hamme
|
||||
*/
|
||||
private class InitializeContextAction implements PrivilegedExceptionAction<GSSContext> {
|
||||
|
||||
private final Oid selectedOid;
|
||||
|
||||
public InitializeContextAction(Oid selectedOid) {
|
||||
this.selectedOid = selectedOid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSContext run() throws GSSException {
|
||||
GSSName clientName = manager.createName(params.getUsername(), GSSName.NT_USER_NAME);
|
||||
GSSCredential clientCreds = manager.createCredential(clientName, GSSContext.DEFAULT_LIFETIME, selectedOid, GSSCredential.INITIATE_ONLY);
|
||||
GSSName peerName = manager.createName("host@" + params.getTransport().getRemoteHost(), GSSName.NT_HOSTBASED_SERVICE);
|
||||
|
||||
GSSContext context = manager.createContext(peerName, selectedOid, clientCreds, GSSContext.DEFAULT_LIFETIME);
|
||||
context.requestMutualAuth(true);
|
||||
context.requestInteg(true);
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToken(byte[] token) throws TransportException {
|
||||
SSHPacket packet = new SSHPacket(Message.USERAUTH_INFO_RESPONSE).putString(token);
|
||||
params.getTransport().write(packet);
|
||||
}
|
||||
|
||||
private void handleContextInitialization(SSHPacket buf)
|
||||
throws UserAuthException, TransportException {
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = buf.readBytes();
|
||||
} catch (BufferException e) {
|
||||
throw new UserAuthException("Failed to read byte array from message buffer", e);
|
||||
}
|
||||
|
||||
Oid selectedOid;
|
||||
try {
|
||||
selectedOid = new Oid(bytes);
|
||||
} catch (GSSException e) {
|
||||
throw new UserAuthException("Exception constructing OID from server response", e);
|
||||
}
|
||||
|
||||
log.debug("Server selected OID: {}", selectedOid.toString());
|
||||
log.debug("Initializing GSSAPI context");
|
||||
|
||||
Subject subject = loginContext.getSubject();
|
||||
|
||||
try {
|
||||
secContext = Subject.doAs(subject, new InitializeContextAction(selectedOid));
|
||||
} catch (PrivilegedActionException e) {
|
||||
throw new UserAuthException("Exception during context initialization", e);
|
||||
}
|
||||
|
||||
log.debug("Sending initial token");
|
||||
byte[] inToken = new byte[0];
|
||||
try {
|
||||
byte[] outToken = secContext.initSecContext(inToken, 0, inToken.length);
|
||||
sendToken(outToken);
|
||||
} catch (GSSException e) {
|
||||
throw new UserAuthException("Exception sending initial token", e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] handleTokenFromServer(SSHPacket buf) throws UserAuthException {
|
||||
byte[] token;
|
||||
|
||||
try {
|
||||
token = buf.readStringAsBytes();
|
||||
} catch (BufferException e) {
|
||||
throw new UserAuthException("Failed to read string from message buffer", e);
|
||||
}
|
||||
|
||||
try {
|
||||
return secContext.initSecContext(token, 0, token.length);
|
||||
} catch (GSSException e) {
|
||||
throw new UserAuthException("Exception during token exchange", e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] generateMIC() throws UserAuthException {
|
||||
byte[] msg = new PlainBuffer().putString(params.getTransport().getSessionID())
|
||||
.putByte(Message.USERAUTH_REQUEST.toByte())
|
||||
.putString(params.getUsername())
|
||||
.putString(params.getNextServiceName())
|
||||
.putString(getName())
|
||||
.getCompactData();
|
||||
|
||||
try {
|
||||
return secContext.getMIC(msg, 0, msg.length, null);
|
||||
} catch (GSSException e) {
|
||||
throw new UserAuthException("Exception getting message integrity code", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Message cmd, SSHPacket buf)
|
||||
throws UserAuthException, TransportException {
|
||||
if (cmd == Message.USERAUTH_60) {
|
||||
handleContextInitialization(buf);
|
||||
} else if (cmd == Message.USERAUTH_INFO_RESPONSE) {
|
||||
byte[] token = handleTokenFromServer(buf);
|
||||
|
||||
if (!secContext.isEstablished()) {
|
||||
log.debug("Sending token");
|
||||
sendToken(token);
|
||||
} else {
|
||||
if (secContext.getIntegState()) {
|
||||
log.debug("Per-message integrity protection available: finalizing authentication with message integrity code");
|
||||
params.getTransport().write(new SSHPacket(Message.USERAUTH_GSSAPI_MIC).putString(generateMIC()));
|
||||
} else {
|
||||
log.debug("Per-message integrity protection unavailable: finalizing authentication");
|
||||
params.getTransport().write(new SSHPacket(Message.USERAUTH_GSSAPI_EXCHANGE_COMPLETE));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.handle(cmd, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
abstract class AbstractSCPClient {
|
||||
|
||||
protected final SCPEngine engine;
|
||||
protected int bandwidthLimit;
|
||||
|
||||
AbstractSCPClient(SCPEngine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
AbstractSCPClient(SCPEngine engine, int bandwidthLimit) {
|
||||
this.engine = engine;
|
||||
this.bandwidthLimit = bandwidthLimit;
|
||||
}
|
||||
}
|
||||
@@ -18,32 +18,36 @@ package net.schmizz.sshj.xfer.scp;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.xfer.LocalDestFile;
|
||||
import net.schmizz.sshj.xfer.TransferListener;
|
||||
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/** Support for uploading files over a connected link using SCP. */
|
||||
public final class SCPDownloadClient {
|
||||
public final class SCPDownloadClient extends AbstractSCPClient {
|
||||
|
||||
private boolean recursiveMode = true;
|
||||
|
||||
private final SCPEngine engine;
|
||||
|
||||
SCPDownloadClient(SCPEngine engine) {
|
||||
this.engine = engine;
|
||||
super(engine);
|
||||
}
|
||||
|
||||
SCPDownloadClient(SCPEngine engine, int bandwidthLimit) {
|
||||
super(engine, bandwidthLimit);
|
||||
}
|
||||
|
||||
/** Download a file from {@code sourcePath} on the connected host to {@code targetPath} locally. */
|
||||
public synchronized int copy(String sourcePath, LocalDestFile targetFile)
|
||||
public synchronized int copy(String sourcePath, LocalDestFile targetFile) throws IOException {
|
||||
return copy(sourcePath, targetFile, ScpCommandLine.EscapeMode.NoEscape);
|
||||
}
|
||||
|
||||
public synchronized int copy(String sourcePath, LocalDestFile targetFile, ScpCommandLine.EscapeMode escapeMode)
|
||||
throws IOException {
|
||||
engine.cleanSlate();
|
||||
try {
|
||||
startCopy(sourcePath, targetFile);
|
||||
startCopy(sourcePath, targetFile, escapeMode);
|
||||
} finally {
|
||||
engine.exit();
|
||||
}
|
||||
@@ -58,15 +62,15 @@ public final class SCPDownloadClient {
|
||||
this.recursiveMode = recursive;
|
||||
}
|
||||
|
||||
void startCopy(String sourcePath, LocalDestFile targetFile)
|
||||
private void startCopy(String sourcePath, LocalDestFile targetFile, ScpCommandLine.EscapeMode escapeMode)
|
||||
throws IOException {
|
||||
List<Arg> args = new LinkedList<Arg>();
|
||||
args.add(Arg.SOURCE);
|
||||
args.add(Arg.QUIET);
|
||||
args.add(Arg.PRESERVE_TIMES);
|
||||
if (recursiveMode)
|
||||
args.add(Arg.RECURSIVE);
|
||||
engine.execSCPWith(args, sourcePath);
|
||||
ScpCommandLine commandLine = ScpCommandLine.with(ScpCommandLine.Arg.SOURCE)
|
||||
.and(ScpCommandLine.Arg.QUIET)
|
||||
.and(ScpCommandLine.Arg.PRESERVE_TIMES)
|
||||
.and(ScpCommandLine.Arg.RECURSIVE, recursiveMode)
|
||||
.and(ScpCommandLine.Arg.LIMIT, String.valueOf(bandwidthLimit), (bandwidthLimit > 0));
|
||||
commandLine.withPath(sourcePath, escapeMode);
|
||||
engine.execSCPWith(commandLine);
|
||||
|
||||
engine.signal("Start status OK");
|
||||
|
||||
@@ -119,7 +123,7 @@ public final class SCPDownloadClient {
|
||||
|
||||
case (char) 1:
|
||||
case (char) 2:
|
||||
throw new SCPException("Remote SCP command returned error: " + msg.substring(1));
|
||||
throw new SCPRemoteException("Remote SCP command returned error: " + msg.substring(1), msg.substring(1));
|
||||
|
||||
default:
|
||||
final String err = "Unrecognized message: `" + msg + "`";
|
||||
@@ -198,4 +202,4 @@ public final class SCPDownloadClient {
|
||||
return parts;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,32 +28,14 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/** @see <a href="http://blogs.sun.com/janp/entry/how_the_scp_protocol_works">SCP Protocol</a> */
|
||||
class SCPEngine {
|
||||
|
||||
enum Arg {
|
||||
SOURCE('f'),
|
||||
SINK('t'),
|
||||
RECURSIVE('r'),
|
||||
VERBOSE('v'),
|
||||
PRESERVE_TIMES('p'),
|
||||
QUIET('q');
|
||||
|
||||
private final char a;
|
||||
|
||||
private Arg(char a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "-" + a;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String SCP_COMMAND = "scp";
|
||||
private static final char LF = '\n';
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
@@ -87,7 +69,8 @@ class SCPEngine {
|
||||
return;
|
||||
case 1: // Warning? not
|
||||
case 2:
|
||||
throw new SCPException("Remote SCP command had error: " + readMessage());
|
||||
final String remoteMessage = readMessage();
|
||||
throw new SCPRemoteException("Remote SCP command had error: " + remoteMessage, remoteMessage);
|
||||
default:
|
||||
throw new SCPException("Received unknown response code");
|
||||
}
|
||||
@@ -97,19 +80,9 @@ class SCPEngine {
|
||||
exitStatus = -1;
|
||||
}
|
||||
|
||||
void execSCPWith(List<Arg> args, String path)
|
||||
void execSCPWith(ScpCommandLine commandLine)
|
||||
throws SSHException {
|
||||
final StringBuilder cmd = new StringBuilder(SCP_COMMAND);
|
||||
for (Arg arg : args) {
|
||||
cmd.append(" ").append(arg);
|
||||
}
|
||||
cmd.append(" ");
|
||||
if (path == null || path.isEmpty()) {
|
||||
cmd.append(".");
|
||||
} else {
|
||||
cmd.append("'").append(path.replaceAll("'", "\\'")).append("'");
|
||||
}
|
||||
scp = host.startSession().exec(cmd.toString());
|
||||
scp = host.startSession().exec(commandLine.toCommandLine());
|
||||
}
|
||||
|
||||
void exit() {
|
||||
@@ -185,5 +158,4 @@ class SCPEngine {
|
||||
TransferListener getTransferListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,18 +28,23 @@ public class SCPFileTransfer
|
||||
extends AbstractFileTransfer
|
||||
implements FileTransfer {
|
||||
|
||||
/** Default bandwidth limit for SCP transfer in kilobit/s (-1 means unlimited) */
|
||||
private static final int DEFAULT_BANDWIDTH_LIMIT = -1;
|
||||
|
||||
private final SessionFactory sessionFactory;
|
||||
private int bandwidthLimit;
|
||||
|
||||
public SCPFileTransfer(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.bandwidthLimit = DEFAULT_BANDWIDTH_LIMIT;
|
||||
}
|
||||
|
||||
public SCPDownloadClient newSCPDownloadClient() {
|
||||
return new SCPDownloadClient(newSCPEngine());
|
||||
return new SCPDownloadClient(newSCPEngine(), bandwidthLimit);
|
||||
}
|
||||
|
||||
public SCPUploadClient newSCPUploadClient() {
|
||||
return new SCPUploadClient(newSCPEngine());
|
||||
return new SCPUploadClient(newSCPEngine(), bandwidthLimit);
|
||||
}
|
||||
|
||||
private SCPEngine newSCPEngine() {
|
||||
@@ -70,4 +75,10 @@ public class SCPFileTransfer
|
||||
newSCPUploadClient().copy(localFile, remotePath);
|
||||
}
|
||||
|
||||
public SCPFileTransfer bandwidthLimit(int limit) {
|
||||
if (limit > 0) {
|
||||
this.bandwidthLimit = limit;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
public class SCPRemoteException extends SCPException
|
||||
{
|
||||
private final String remoteMessage;
|
||||
|
||||
public SCPRemoteException(String message, String remoteMessage) {
|
||||
super(message);
|
||||
this.remoteMessage = remoteMessage;
|
||||
}
|
||||
|
||||
public String getRemoteMessage() {
|
||||
return remoteMessage;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Copyright 2009 sshj contributors
|
||||
*
|
||||
* <p/>
|
||||
* 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
|
||||
*
|
||||
* <p/>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p/>
|
||||
* 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.
|
||||
@@ -20,29 +20,34 @@ import net.schmizz.sshj.common.StreamCopier;
|
||||
import net.schmizz.sshj.xfer.LocalFileFilter;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
import net.schmizz.sshj.xfer.TransferListener;
|
||||
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/** Support for uploading files over a connected link using SCP. */
|
||||
public final class SCPUploadClient {
|
||||
public final class SCPUploadClient extends AbstractSCPClient {
|
||||
|
||||
private final SCPEngine engine;
|
||||
private LocalFileFilter uploadFilter;
|
||||
|
||||
SCPUploadClient(SCPEngine engine) {
|
||||
this.engine = engine;
|
||||
super(engine);
|
||||
}
|
||||
|
||||
SCPUploadClient(SCPEngine engine, int bandwidthLimit) {
|
||||
super(engine, bandwidthLimit);
|
||||
}
|
||||
|
||||
/** Upload a local file from {@code localFile} to {@code targetPath} on the remote host. */
|
||||
public synchronized int copy(LocalSourceFile sourceFile, String remotePath)
|
||||
throws IOException {
|
||||
return copy(sourceFile, remotePath, ScpCommandLine.EscapeMode.SingleQuote);
|
||||
}
|
||||
|
||||
public synchronized int copy(LocalSourceFile sourceFile, String remotePath, ScpCommandLine.EscapeMode escapeMode)
|
||||
throws IOException {
|
||||
engine.cleanSlate();
|
||||
try {
|
||||
startCopy(sourceFile, remotePath);
|
||||
startCopy(sourceFile, remotePath, escapeMode);
|
||||
} finally {
|
||||
engine.exit();
|
||||
}
|
||||
@@ -53,14 +58,14 @@ public final class SCPUploadClient {
|
||||
this.uploadFilter = uploadFilter;
|
||||
}
|
||||
|
||||
private synchronized void startCopy(LocalSourceFile sourceFile, String targetPath)
|
||||
private void startCopy(LocalSourceFile sourceFile, String targetPath, ScpCommandLine.EscapeMode escapeMode)
|
||||
throws IOException {
|
||||
List<Arg> args = new LinkedList<Arg>();
|
||||
args.add(Arg.SINK);
|
||||
args.add(Arg.RECURSIVE);
|
||||
if (sourceFile.providesAtimeMtime())
|
||||
args.add(Arg.PRESERVE_TIMES);
|
||||
engine.execSCPWith(args, targetPath);
|
||||
ScpCommandLine commandLine = ScpCommandLine.with(ScpCommandLine.Arg.SINK)
|
||||
.and(ScpCommandLine.Arg.RECURSIVE)
|
||||
.and(ScpCommandLine.Arg.PRESERVE_TIMES, sourceFile.providesAtimeMtime())
|
||||
.and(ScpCommandLine.Arg.LIMIT, String.valueOf(bandwidthLimit), (bandwidthLimit > 0));
|
||||
commandLine.withPath(targetPath, escapeMode);
|
||||
engine.execSCPWith(commandLine);
|
||||
engine.check("Start status OK");
|
||||
process(engine.getTransferListener(), sourceFile);
|
||||
}
|
||||
|
||||
132
src/main/java/net/schmizz/sshj/xfer/scp/ScpCommandLine.java
Normal file
132
src/main/java/net/schmizz/sshj/xfer/scp/ScpCommandLine.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Command line to be sent to the remote SSH process to setup an SCP process in the correct mode.
|
||||
*/
|
||||
public class ScpCommandLine {
|
||||
private static final String SCP_COMMAND = "scp";
|
||||
private EscapeMode mode;
|
||||
|
||||
enum Arg {
|
||||
SOURCE('f'),
|
||||
SINK('t'),
|
||||
RECURSIVE('r'),
|
||||
VERBOSE('v'),
|
||||
PRESERVE_TIMES('p'),
|
||||
QUIET('q'),
|
||||
LIMIT('l');
|
||||
|
||||
private final char a;
|
||||
|
||||
private Arg(char a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "-" + a;
|
||||
}
|
||||
}
|
||||
|
||||
public enum EscapeMode {
|
||||
NoEscape,
|
||||
Space {
|
||||
@Override
|
||||
String escapedPath(String path) {
|
||||
return path.replace(" ", "\\ ");
|
||||
}
|
||||
},
|
||||
DoubleQuote {
|
||||
@Override
|
||||
String escapedPath(String path) {
|
||||
return "\"" + path.replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
},
|
||||
SingleQuote {
|
||||
@Override
|
||||
String escapedPath(String path) {
|
||||
return "\'" + path.replace("'", "\\'") + "'";
|
||||
}
|
||||
};
|
||||
|
||||
String escapedPath(String path) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<Arg, String> arguments = new LinkedHashMap<Arg, String>();
|
||||
private String path;
|
||||
|
||||
ScpCommandLine() {
|
||||
}
|
||||
|
||||
static ScpCommandLine with(Arg name) {
|
||||
return with(name, null, true);
|
||||
}
|
||||
|
||||
static ScpCommandLine with(Arg name, String value) {
|
||||
return with(name, value, true);
|
||||
}
|
||||
|
||||
static ScpCommandLine with(Arg name, boolean accept) {
|
||||
return with(name, null, accept);
|
||||
}
|
||||
|
||||
static ScpCommandLine with(Arg name, String value, boolean accept) {
|
||||
ScpCommandLine commandLine = new ScpCommandLine();
|
||||
commandLine.addArgument(name, value, accept);
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
private void addArgument(Arg name, String value, boolean accept) {
|
||||
if (accept) {
|
||||
arguments.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
ScpCommandLine and(Arg name) {
|
||||
addArgument(name, null, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
ScpCommandLine and(Arg name, String value) {
|
||||
addArgument(name, value, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
ScpCommandLine and(Arg name, boolean accept) {
|
||||
addArgument(name, null, accept);
|
||||
return this;
|
||||
}
|
||||
|
||||
ScpCommandLine and(Arg name, String value, boolean accept) {
|
||||
addArgument(name, value, accept);
|
||||
return this;
|
||||
}
|
||||
|
||||
ScpCommandLine withPath(String path, EscapeMode mode) {
|
||||
this.path = path;
|
||||
this.mode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
String toCommandLine() {
|
||||
final StringBuilder cmd = new StringBuilder(SCP_COMMAND);
|
||||
for (Arg arg : arguments.keySet()) {
|
||||
cmd.append(" ").append(arg);
|
||||
String s = arguments.get(arg);
|
||||
if (s != null && !s.trim().isEmpty()) {
|
||||
cmd.append(s);
|
||||
}
|
||||
}
|
||||
cmd.append(" ");
|
||||
if (path == null || path.trim().isEmpty()) {
|
||||
cmd.append(".");
|
||||
} else {
|
||||
cmd.append(mode.escapedPath(path));
|
||||
}
|
||||
return cmd.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.hierynomus.sshj.connection.channel;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class ChannelCloseEofTest {
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Test
|
||||
public void shouldCorrectlyHandleSessionChannelEof() throws IOException, InterruptedException {
|
||||
fixture.setupConnectedDefaultClient().authPassword("jeroen", "jeroen");
|
||||
Session session = fixture.getClient().startSession();
|
||||
session.allocateDefaultPTY();
|
||||
session.close();
|
||||
Thread.sleep(1000);
|
||||
assertThat("Should still be connected", fixture.getClient().isConnected());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.hierynomus.sshj.connection.channel.direct;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class CommandTest {
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void shouldExecuteBackgroundCommand() throws IOException {
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||
sshClient.authPassword("jeroen", "jeroen");
|
||||
File file = new File(temp.getRoot(), "testdir");
|
||||
assertThat("File should not exist", !file.exists());
|
||||
// TODO figure out why this does not really execute in the background.
|
||||
Session.Command exec = sshClient.startSession().exec("mkdir " + file.getPath() + " &");
|
||||
exec.join();
|
||||
assertThat("File should exist", file.exists());
|
||||
assertThat("File should be directory", file.isDirectory());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.hierynomus.sshj.keepalive;
|
||||
|
||||
import com.hierynomus.sshj.test.KnownFailingTests;
|
||||
import com.hierynomus.sshj.test.SlowTests;
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.DefaultConfig;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.userauth.UserAuthException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class KeepAliveThreadTerminationTest {
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Test
|
||||
@Category({SlowTests.class, KnownFailingTests.class})
|
||||
public void shouldCorrectlyTerminateThreadOnDisconnect() throws IOException, InterruptedException {
|
||||
DefaultConfig defaultConfig = new DefaultConfig();
|
||||
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
SSHClient sshClient = fixture.setupClient(defaultConfig);
|
||||
fixture.connectClient(sshClient);
|
||||
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(1);
|
||||
try {
|
||||
sshClient.authPassword("bad", "credentials");
|
||||
fail("Should not auth.");
|
||||
} catch (UserAuthException e) {
|
||||
// OK
|
||||
}
|
||||
fixture.stopClient();
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
|
||||
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
||||
for (long l : threadMXBean.getAllThreadIds()) {
|
||||
ThreadInfo threadInfo = threadMXBean.getThreadInfo(l);
|
||||
if (threadInfo.getThreadName().equals("keep-alive") && threadInfo.getThreadState() != Thread.State.TERMINATED) {
|
||||
System.err.println("Found thread in state " + threadInfo.getThreadState());
|
||||
throw new RuntimeException("Found alive keep-alive thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/test/java/com/hierynomus/sshj/sftp/RemoteFileTest.java
Normal file
74
src/test/java/com/hierynomus/sshj/sftp/RemoteFileTest.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.hierynomus.sshj.sftp;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.sftp.OpenMode;
|
||||
import net.schmizz.sshj.sftp.RemoteFile;
|
||||
import net.schmizz.sshj.sftp.SFTPEngine;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class RemoteFileTest {
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void shouldNotGoOutOfBoundsInReadAheadInputStream() throws IOException {
|
||||
SSHClient ssh = fixture.setupConnectedDefaultClient();
|
||||
ssh.authPassword("test", "test");
|
||||
SFTPEngine sftp = new SFTPEngine(ssh).init();
|
||||
|
||||
RemoteFile rf;
|
||||
File file = temp.newFile("SftpReadAheadTest.bin");
|
||||
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
|
||||
byte[] data = new byte[8192];
|
||||
new Random(53).nextBytes(data);
|
||||
data[3072] = 1;
|
||||
rf.write(0, data, 0, data.length);
|
||||
rf.close();
|
||||
|
||||
assertThat("The file should exist", file.exists());
|
||||
|
||||
rf = sftp.open(file.getPath());
|
||||
InputStream rs = rf.new ReadAheadRemoteFileInputStream(16 /*maxUnconfirmedReads*/);
|
||||
|
||||
byte[] test = new byte[4097];
|
||||
int n = 0;
|
||||
|
||||
while (n < 2048) {
|
||||
n += rs.read(test, n, 2048 - n);
|
||||
}
|
||||
|
||||
while (n < 3072) {
|
||||
n += rs.read(test, n, 3072 - n);
|
||||
}
|
||||
|
||||
if (test[3072] != 0) {
|
||||
System.err.println("buffer overrun!");
|
||||
}
|
||||
|
||||
n += rs.read(test, n, test.length - n); // --> ArrayIndexOutOfBoundsException
|
||||
|
||||
byte[] test2 = new byte[data.length];
|
||||
System.arraycopy(test, 0, test2, 0, test.length);
|
||||
|
||||
while (n < data.length) {
|
||||
n += rs.read(test2, n, data.length - n);
|
||||
}
|
||||
|
||||
assertThat("The written and received data should match", data, equalTo(test2));
|
||||
}
|
||||
}
|
||||
41
src/test/java/com/hierynomus/sshj/sftp/SFTPClientTest.java
Normal file
41
src/test/java/com/hierynomus/sshj/sftp/SFTPClientTest.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.hierynomus.sshj.sftp;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import com.hierynomus.sshj.test.util.FileUtil;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.sftp.SFTPClient;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SFTPClientTest {
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void shouldNotThrowExceptionOnCloseBeforeDisconnect() throws IOException {
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||
sshClient.authPassword("test", "test");
|
||||
SFTPClient sftpClient = sshClient.newSFTPClient();
|
||||
// TODO workaround for bug in Mina 1.0.0 --> Should be fixed in 1.1.0
|
||||
sftpClient.getFileTransfer().setPreserveAttributes(false);
|
||||
File file = temp.newFile("source.txt");
|
||||
FileUtil.writeToFile(file, "This is the source");
|
||||
try {
|
||||
try {
|
||||
sftpClient.put(file.getPath(), temp.newFile("dest.txt").getPath());
|
||||
} finally {
|
||||
sftpClient.close();
|
||||
}
|
||||
} finally {
|
||||
sshClient.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.hierynomus.sshj.test;
|
||||
|
||||
/**
|
||||
* Marker interface for JUnit Categories.
|
||||
*
|
||||
* This denotes that the test is known to fail, and should be fixed at some time.
|
||||
*/
|
||||
public interface KnownFailingTests {
|
||||
}
|
||||
4
src/test/java/com/hierynomus/sshj/test/SlowTests.java
Normal file
4
src/test/java/com/hierynomus/sshj/test/SlowTests.java
Normal file
@@ -0,0 +1,4 @@
|
||||
package com.hierynomus.sshj.test;
|
||||
|
||||
public interface SlowTests {
|
||||
}
|
||||
163
src/test/java/com/hierynomus/sshj/test/SshFixture.java
Normal file
163
src/test/java/com/hierynomus/sshj/test/SshFixture.java
Normal file
@@ -0,0 +1,163 @@
|
||||
package com.hierynomus.sshj.test;
|
||||
|
||||
import net.schmizz.sshj.Config;
|
||||
import net.schmizz.sshj.DefaultConfig;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.keyprovider.AbstractClassLoadableResourceKeyPairProvider;
|
||||
import org.apache.sshd.common.util.OsUtils;
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.CommandFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.command.ScpCommandFactory;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.shell.ProcessShellFactory;
|
||||
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
|
||||
import org.junit.rules.ExternalResource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Can be used as a rule to ensure the server is teared down after each test.
|
||||
*/
|
||||
public class SshFixture extends ExternalResource {
|
||||
public static final String hostkey = "hostkey.pem";
|
||||
public static final String fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
|
||||
|
||||
private SshServer server = defaultSshServer();
|
||||
private SSHClient client = null;
|
||||
private AtomicBoolean started = new AtomicBoolean(false);
|
||||
private boolean autoStart = true;
|
||||
|
||||
public SshFixture(boolean autoStart) {
|
||||
this.autoStart = autoStart;
|
||||
}
|
||||
|
||||
public SshFixture() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
if (autoStart) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
stopClient();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
if (!started.getAndSet(true)) {
|
||||
server.start();
|
||||
}
|
||||
}
|
||||
|
||||
public SSHClient setupConnectedDefaultClient() throws IOException {
|
||||
return connectClient(setupDefaultClient());
|
||||
}
|
||||
|
||||
public SSHClient setupDefaultClient() {
|
||||
return setupClient(new DefaultConfig());
|
||||
}
|
||||
|
||||
public SSHClient setupClient(Config config) {
|
||||
if (client == null) {
|
||||
client = new SSHClient(config);
|
||||
client.addHostKeyVerifier(fingerprint);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
public SSHClient getClient() {
|
||||
if (client != null) {
|
||||
return client;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("First call one of the setup*Client methods");
|
||||
}
|
||||
|
||||
public SSHClient connectClient(SSHClient client) throws IOException {
|
||||
client.connect(server.getHost(), server.getPort());
|
||||
return client;
|
||||
}
|
||||
|
||||
private SshServer defaultSshServer() {
|
||||
SshServer sshServer = SshServer.setUpDefaultServer();
|
||||
sshServer.setPort(randomPort());
|
||||
AbstractClassLoadableResourceKeyPairProvider fileKeyPairProvider = SecurityUtils.createClassLoadableResourceKeyPairProvider();
|
||||
fileKeyPairProvider.setResources(Collections.singletonList(hostkey));
|
||||
sshServer.setKeyPairProvider(fileKeyPairProvider);
|
||||
sshServer.setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, ServerSession session) {
|
||||
return username.equals(password);
|
||||
}
|
||||
});
|
||||
sshServer.setGSSAuthenticator(new BogusGSSAuthenticator());
|
||||
sshServer.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
|
||||
ScpCommandFactory commandFactory = new ScpCommandFactory();
|
||||
commandFactory.setDelegateCommandFactory(new CommandFactory() {
|
||||
@Override
|
||||
public Command createCommand(String command) {
|
||||
return new ProcessShellFactory(command.split(" ")).create();
|
||||
}
|
||||
});
|
||||
sshServer.setCommandFactory(commandFactory);
|
||||
|
||||
return sshServer;
|
||||
}
|
||||
|
||||
private int randomPort() {
|
||||
try {
|
||||
ServerSocket s = null;
|
||||
try {
|
||||
s = new ServerSocket(0);
|
||||
return s.getLocalPort();
|
||||
} finally {
|
||||
if (s != null)
|
||||
s.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopClient() {
|
||||
if (client != null && client.isConnected()) {
|
||||
try {
|
||||
client.disconnect();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
client = null;
|
||||
}
|
||||
} else if (client != null) {
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
if (started.getAndSet(false)) {
|
||||
try {
|
||||
server.stop(true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SshServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
27
src/test/java/com/hierynomus/sshj/test/util/FileUtil.java
Normal file
27
src/test/java/com/hierynomus/sshj/test/util/FileUtil.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.hierynomus.sshj.test.util;
|
||||
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class FileUtil {
|
||||
|
||||
public static void writeToFile(File f, String content) throws IOException {
|
||||
FileWriter w = new FileWriter(f);
|
||||
try {
|
||||
w.write(content);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(w);
|
||||
}
|
||||
}
|
||||
|
||||
public static String readFromFile(File f) throws IOException {
|
||||
FileInputStream fileInputStream = new FileInputStream(f);
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = IOUtils.readFully(fileInputStream);
|
||||
return byteArrayOutputStream.toString("UTF-8");
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fileInputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,39 @@
|
||||
/**
|
||||
* Copyright 2009 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.transport;
|
||||
package com.hierynomus.sshj.transport;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.util.BasicFixture;
|
||||
import org.junit.After;
|
||||
import net.schmizz.sshj.transport.DisconnectListener;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class Disconnection {
|
||||
public class DisconnectionTest {
|
||||
private AtomicBoolean disconnected = null;
|
||||
|
||||
private final BasicFixture fixture = new BasicFixture();
|
||||
|
||||
private boolean notified;
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
throws IOException {
|
||||
fixture.init();
|
||||
|
||||
notified = false;
|
||||
|
||||
fixture.getClient().getTransport().setDisconnectListener(new DisconnectListener() {
|
||||
public void setupFlag() throws IOException {
|
||||
disconnected = new AtomicBoolean(false);
|
||||
// Initialize the client
|
||||
SSHClient defaultClient = fixture.setupDefaultClient();
|
||||
defaultClient.getTransport().setDisconnectListener(new DisconnectListener() {
|
||||
@Override
|
||||
public void notifyDisconnect(DisconnectReason reason, String message) {
|
||||
notified = true;
|
||||
disconnected.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
throws IOException, InterruptedException {
|
||||
fixture.done();
|
||||
fixture.connectClient(defaultClient);
|
||||
}
|
||||
|
||||
private boolean joinToClientTransport(int seconds) {
|
||||
@@ -67,8 +48,8 @@ public class Disconnection {
|
||||
@Test
|
||||
public void listenerNotifiedOnClientDisconnect()
|
||||
throws IOException {
|
||||
fixture.stopClient();
|
||||
assertTrue(notified);
|
||||
fixture.getClient().disconnect();
|
||||
assertTrue(disconnected.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,13 +57,13 @@ public class Disconnection {
|
||||
throws InterruptedException, IOException {
|
||||
fixture.stopServer();
|
||||
joinToClientTransport(2);
|
||||
assertTrue(notified);
|
||||
assertTrue(disconnected.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void joinNotifiedOnClientDisconnect()
|
||||
throws IOException {
|
||||
fixture.stopClient();
|
||||
fixture.getClient().disconnect();
|
||||
assertTrue(joinToClientTransport(2));
|
||||
}
|
||||
|
||||
@@ -93,4 +74,4 @@ public class Disconnection {
|
||||
assertFalse(joinToClientTransport(2));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.hierynomus.sshj.transport.kex;
|
||||
|
||||
import com.hierynomus.sshj.test.KnownFailingTests;
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.DefaultConfig;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.transport.kex.Curve25519SHA256;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
||||
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.kex.BuiltinDHFactories;
|
||||
import org.apache.sshd.server.kex.DHGEXServer;
|
||||
import org.apache.sshd.server.kex.DHGServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class KeyExchangeTest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(KeyExchangeTest.class);
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture(false);
|
||||
|
||||
@After
|
||||
public void stopServer() {
|
||||
fixture.stopServer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldKexWithDiffieHellmanGroupExchangeSha1() throws IOException {
|
||||
setupAndCheckKex(DHGEXServer.newFactory(BuiltinDHFactories.dhgex), new DHGexSHA1.Factory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldKexWithDiffieHellmanGroupExchangeSha256() throws IOException {
|
||||
setupAndCheckKex(DHGEXServer.newFactory(BuiltinDHFactories.dhgex256), new DHGexSHA256.Factory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldKexWithEllipticCurveDiffieHellmanNistP256() throws IOException {
|
||||
attemptKex(100, DHGServer.newFactory(BuiltinDHFactories.ecdhp256), new ECDHNistP.Factory256());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldKexWithEllipticCurveDiffieHellmanNistP384() throws IOException {
|
||||
attemptKex(100, DHGServer.newFactory(BuiltinDHFactories.ecdhp384), new ECDHNistP.Factory384());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldKexWithEllipticCurveDiffieHellmanNistP521() throws IOException {
|
||||
attemptKex(100, DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Apache SSHD does (not yet) have Curve25519 support")
|
||||
public void shouldKexWithCurve25519() throws IOException {
|
||||
attemptKex(100, null, new Curve25519SHA256.Factory());
|
||||
}
|
||||
|
||||
|
||||
private void attemptKex(int times, NamedFactory<org.apache.sshd.common.kex.KeyExchange> serverFactory,
|
||||
Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) throws IOException {
|
||||
for (int i = 0; i < times; i++) {
|
||||
logger.info("--> Attempt {}", i);
|
||||
setupAndCheckKex(serverFactory, clientFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupAndCheckKex(NamedFactory<org.apache.sshd.common.kex.KeyExchange> serverFactory,
|
||||
Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) throws IOException {
|
||||
fixture.getServer().setKeyExchangeFactories(Collections.singletonList(serverFactory));
|
||||
fixture.start();
|
||||
DefaultConfig config = new DefaultConfig();
|
||||
config.setKeyExchangeFactories(Collections.singletonList(clientFactory));
|
||||
SSHClient sshClient = fixture.connectClient(fixture.setupClient(config));
|
||||
assertThat("should be connected", sshClient.isConnected());
|
||||
sshClient.disconnect();
|
||||
// fixture.stopServer();
|
||||
fixture.stopClient();
|
||||
}
|
||||
}
|
||||
52
src/test/java/com/hierynomus/sshj/userauth/GssApiTest.java
Normal file
52
src/test/java/com/hierynomus/sshj/userauth/GssApiTest.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package com.hierynomus.sshj.userauth;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
|
||||
import net.schmizz.sshj.util.gss.BogusGSSManager;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GssApiTest {
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
private static final String LOGIN_CONTEXT_NAME = "TestLoginContext";
|
||||
|
||||
private static class TestAuthConfiguration extends Configuration {
|
||||
private AppConfigurationEntry entry = new AppConfigurationEntry(
|
||||
"testLoginModule",
|
||||
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
|
||||
Collections.<String, Object> emptyMap());
|
||||
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
||||
if (name.equals(LOGIN_CONTEXT_NAME)) {
|
||||
return new AppConfigurationEntry[] { entry };
|
||||
} else {
|
||||
return new AppConfigurationEntry[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticated() throws Exception {
|
||||
AuthGssApiWithMic authMethod = new AuthGssApiWithMic(
|
||||
new LoginContext(LOGIN_CONTEXT_NAME, null, null, new TestAuthConfiguration()),
|
||||
Collections.singletonList(BogusGSSManager.KRB5_MECH),
|
||||
new BogusGSSManager());
|
||||
|
||||
SSHClient defaultClient = fixture.setupConnectedDefaultClient();
|
||||
defaultClient.auth("user", authMethod);
|
||||
assertTrue(defaultClient.isAuthenticated());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.sshj.util.BasicFixture;
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -26,15 +26,17 @@ public class LoadsOfConnects {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final BasicFixture fixture = new BasicFixture();
|
||||
private final SshFixture fixture = new SshFixture();
|
||||
|
||||
@Test
|
||||
public void loadsOfConnects()
|
||||
throws IOException, InterruptedException {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
System.out.println("Try " + i);
|
||||
fixture.init(false);
|
||||
fixture.done();
|
||||
fixture.start();
|
||||
fixture.setupConnectedDefaultClient();
|
||||
fixture.stopClient();
|
||||
fixture.stopServer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.userauth.UserAuthException;
|
||||
import net.schmizz.sshj.util.BasicFixture;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -29,18 +29,20 @@ import static org.junit.Assert.assertTrue;
|
||||
/* Kinda basic right now */
|
||||
public class SmokeTest {
|
||||
|
||||
private final BasicFixture fixture = new BasicFixture();
|
||||
private final SshFixture fixture = new SshFixture();
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
throws IOException {
|
||||
fixture.init(false);
|
||||
fixture.start();
|
||||
fixture.setupConnectedDefaultClient();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
throws IOException, InterruptedException {
|
||||
fixture.done();
|
||||
fixture.stopClient();
|
||||
fixture.stopServer();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -52,7 +54,7 @@ public class SmokeTest {
|
||||
@Test
|
||||
public void authenticated()
|
||||
throws UserAuthException, TransportException {
|
||||
fixture.dummyAuth();
|
||||
fixture.getClient().authPassword("dummy", "dummy");
|
||||
assertTrue(fixture.getClient().isAuthenticated());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.schmizz.sshj.keyprovider;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||
|
||||
public class KeyProviderUtilTest {
|
||||
|
||||
private static final File ROOT = new File("src/test/resources/keyformats");
|
||||
|
||||
@Test
|
||||
public void testOpenSsh() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "openssh"));
|
||||
assertEquals(KeyFormat.OpenSSH, format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkcs8() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs8"));
|
||||
assertEquals(KeyFormat.PKCS8, format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutty() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "putty"));
|
||||
assertEquals(KeyFormat.PuTTY, format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsBlankLines() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs8-blanks"));
|
||||
assertEquals(KeyFormat.PKCS8, format);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import java.security.PublicKey;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -78,6 +79,7 @@ public class OpenSSHKnownHostsTest {
|
||||
|
||||
assertTrue(kh.verify("schmizz.net", 22, key));
|
||||
assertTrue(kh.verify("69.163.155.180", 22, key));
|
||||
assertFalse(kh.verify("69.163.155.18", 22, key));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Copyright 2009 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2010, 2011 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.util;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.userauth.UserAuthException;
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
||||
import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
|
||||
public class BasicFixture {
|
||||
|
||||
public static final String hostkey = "src/test/resources/hostkey.pem";
|
||||
public static final String fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
|
||||
|
||||
public static final String hostname = "localhost";
|
||||
public final int port = gimmeAPort();
|
||||
|
||||
private SSHClient client;
|
||||
private SshServer server;
|
||||
|
||||
private boolean clientRunning = false;
|
||||
private boolean serverRunning = false;
|
||||
|
||||
private static int gimmeAPort() {
|
||||
try {
|
||||
ServerSocket s = null;
|
||||
try {
|
||||
s = new ServerSocket(0);
|
||||
return s.getLocalPort();
|
||||
} finally {
|
||||
if (s != null)
|
||||
s.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void init()
|
||||
throws IOException {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean authenticate)
|
||||
throws IOException {
|
||||
startServer();
|
||||
startClient(authenticate);
|
||||
}
|
||||
|
||||
public void done()
|
||||
throws InterruptedException, IOException {
|
||||
stopClient();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
public void startServer()
|
||||
throws IOException {
|
||||
server = SshServer.setUpDefaultServer();
|
||||
server.setPort(port);
|
||||
server.setKeyPairProvider(new FileKeyPairProvider(new String[]{hostkey}));
|
||||
server.setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||
@Override
|
||||
public boolean authenticate(String u, String p, ServerSession s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
server.start();
|
||||
serverRunning = true;
|
||||
}
|
||||
|
||||
public void stopServer()
|
||||
throws InterruptedException {
|
||||
if (serverRunning) {
|
||||
server.stop();
|
||||
serverRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public SshServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void startClient(boolean authenticate)
|
||||
throws IOException {
|
||||
client = new SSHClient();
|
||||
client.addHostKeyVerifier(fingerprint);
|
||||
client.connect(hostname, port);
|
||||
if (authenticate)
|
||||
dummyAuth();
|
||||
clientRunning = true;
|
||||
}
|
||||
|
||||
public void stopClient()
|
||||
throws IOException {
|
||||
if (clientRunning) {
|
||||
client.disconnect();
|
||||
clientRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public SSHClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void dummyAuth()
|
||||
throws UserAuthException, TransportException {
|
||||
server.setPasswordAuthenticator(new BogusPasswordAuthenticator());
|
||||
client.authPassword("same", "same");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright 2009 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2010, 2011 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.util;
|
||||
|
||||
import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
|
||||
/** Successfully authenticates when username == password. */
|
||||
public class BogusPasswordAuthenticator
|
||||
implements PasswordAuthenticator {
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, ServerSession s) {
|
||||
return username.equals(password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.schmizz.sshj.util.gss;
|
||||
|
||||
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
|
||||
public class BogusGSSAuthenticator
|
||||
extends GSSAuthenticator {
|
||||
|
||||
private final GSSManager manager = new BogusGSSManager();
|
||||
|
||||
@Override
|
||||
public GSSManager getGSSManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSCredential getGSSCredential(GSSManager mgr) throws GSSException {
|
||||
return manager.createCredential(GSSCredential.ACCEPT_ONLY);
|
||||
}
|
||||
}
|
||||
243
src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java
Normal file
243
src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java
Normal file
@@ -0,0 +1,243 @@
|
||||
package net.schmizz.sshj.util.gss;
|
||||
|
||||
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.ietf.jgss.ChannelBinding;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.MessageProp;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
public class BogusGSSContext
|
||||
implements GSSContext {
|
||||
|
||||
private static final byte[] INIT_TOKEN = fromString("INIT");
|
||||
private static final byte[] ACCEPT_TOKEN = fromString("ACCEPT");
|
||||
private static final byte[] MIC = fromString("LGTM");
|
||||
|
||||
private static byte[] fromString(String s) {
|
||||
return s.getBytes(Charset.forName("UTF-8"));
|
||||
}
|
||||
|
||||
private boolean initialized = false;
|
||||
private boolean accepted = false;
|
||||
private boolean integState = false;
|
||||
private boolean mutualAuthState = false;
|
||||
|
||||
@Override
|
||||
public byte[] initSecContext(byte[] inputBuf, int offset, int len) throws GSSException {
|
||||
initialized = true;
|
||||
return INIT_TOKEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int initSecContext(InputStream inStream, OutputStream outStream) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] acceptSecContext(byte[] inToken, int offset, int len) throws GSSException {
|
||||
accepted = Arrays.equals(INIT_TOKEN, Arrays.copyOfRange(inToken, offset, offset + len));
|
||||
return ACCEPT_TOKEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptSecContext(InputStream inStream, OutputStream outStream) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEstablished() {
|
||||
return initialized || accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() throws GSSException {}
|
||||
|
||||
@Override
|
||||
public int getWrapSizeLimit(int qop, boolean confReq, int maxTokenSize) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] wrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unwrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMIC(byte[] inMsg, int offset, int len, MessageProp msgProp) throws GSSException {
|
||||
return MIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getMIC(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyMIC(byte[] inToken, int tokOffset, int tokLen, byte[] inMsg, int msgOffset, int msgLen, MessageProp msgProp) throws GSSException {
|
||||
if (!Arrays.equals(MIC, Arrays.copyOfRange(inToken, tokOffset, tokOffset + tokLen))) {
|
||||
throw new GSSException(GSSException.BAD_MIC);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyMIC(InputStream tokStream, InputStream msgStream, MessageProp msgProp) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] export() throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestMutualAuth(boolean state) throws GSSException {
|
||||
this.mutualAuthState = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInteg(boolean state) throws GSSException {
|
||||
this.integState = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestReplayDet(boolean state) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSequenceDet(boolean state) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestCredDeleg(boolean state) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestAnonymity(boolean state) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestConf(boolean state) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLifetime(int lifetime) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChannelBinding(ChannelBinding cb) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getMutualAuthState() {
|
||||
return mutualAuthState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIntegState() {
|
||||
return integState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCredDelegState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getReplayDetState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSequenceDetState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAnonymityState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransferable() throws GSSException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProtReady() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getConfState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLifetime() {
|
||||
return INDEFINITE_LIFETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName getSrcName() throws GSSException {
|
||||
try {
|
||||
String hostname = InetAddress.getLocalHost().getCanonicalHostName();
|
||||
return new BogusGSSName("user@" + hostname, GSSName.NT_HOSTBASED_SERVICE);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName getTargName() throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Oid getMech() throws GSSException {
|
||||
return BogusGSSManager.KRB5_MECH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSCredential getDelegCred() throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitiator() throws GSSException {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.schmizz.sshj.util.gss;
|
||||
|
||||
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
public class BogusGSSCredential
|
||||
implements GSSCredential {
|
||||
|
||||
private final GSSName name;
|
||||
private final int usage;
|
||||
|
||||
public BogusGSSCredential(GSSName name, int usage) {
|
||||
this.name = name;
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() throws GSSException {}
|
||||
|
||||
@Override
|
||||
public GSSName getName() throws GSSException {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName getName(Oid mech) throws GSSException {
|
||||
return name.canonicalize(mech);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingLifetime() throws GSSException {
|
||||
return INDEFINITE_LIFETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingInitLifetime(Oid mech) throws GSSException {
|
||||
return INDEFINITE_LIFETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingAcceptLifetime(Oid mech) throws GSSException {
|
||||
return INDEFINITE_LIFETIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsage() throws GSSException {
|
||||
return usage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsage(Oid mech) throws GSSException {
|
||||
return usage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Oid[] getMechs() throws GSSException {
|
||||
return new Oid[] { BogusGSSManager.KRB5_MECH };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(GSSName name, int initLifetime, int acceptLifetime, Oid mech, int usage) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (name == null ? 0 : name.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof BogusGSSCredential)) {
|
||||
return false;
|
||||
}
|
||||
GSSName otherName = ((BogusGSSCredential) obj).name;
|
||||
return name == null ? otherName == null : name.equals((Object) otherName);
|
||||
}
|
||||
}
|
||||
106
src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java
Normal file
106
src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package net.schmizz.sshj.util.gss;
|
||||
|
||||
import java.security.Provider;
|
||||
|
||||
import org.apache.sshd.server.auth.gss.UserAuthGSS;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implements a fake Kerberos 5 mechanism. MINA only supports Kerberos 5 over
|
||||
* GSS-API, so we can't implement a separate mechanism.
|
||||
*/
|
||||
public class BogusGSSManager
|
||||
extends GSSManager {
|
||||
|
||||
public static final Oid KRB5_MECH = UserAuthGSS.KRB5_MECH;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BogusGSSManager.class);
|
||||
|
||||
@Override
|
||||
public Oid[] getMechs() {
|
||||
return new Oid[] { KRB5_MECH };
|
||||
}
|
||||
|
||||
@Override
|
||||
public Oid[] getNamesForMech(Oid mech) throws GSSException {
|
||||
return new Oid[] { GSSName.NT_EXPORT_NAME, GSSName.NT_HOSTBASED_SERVICE };
|
||||
}
|
||||
|
||||
@Override
|
||||
public Oid[] getMechsForName(Oid nameType) {
|
||||
return new Oid[] { KRB5_MECH };
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName createName(String nameStr, Oid nameType) throws GSSException {
|
||||
return new BogusGSSName(nameStr, nameType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName createName(byte[] name, Oid nameType) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName createName(String nameStr, Oid nameType, Oid mech) throws GSSException {
|
||||
return this.createName(nameStr, nameType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName createName(byte[] name, Oid nameType, Oid mech) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSCredential createCredential(int usage) throws GSSException {
|
||||
return new BogusGSSCredential(null, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSCredential createCredential(GSSName name, int lifetime, Oid mech, int usage) throws GSSException {
|
||||
return new BogusGSSCredential(name, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSCredential createCredential(GSSName name, int lifetime, Oid[] mechs, int usage) throws GSSException {
|
||||
return new BogusGSSCredential(name, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSContext createContext(GSSName peer, Oid mech, GSSCredential myCred, int lifetime) throws GSSException {
|
||||
return new BogusGSSContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSContext createContext(GSSCredential myCred) throws GSSException {
|
||||
return new BogusGSSContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSContext createContext(byte[] interProcessToken) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProviderAtFront(Provider p, Oid mech) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProviderAtEnd(Provider p, Oid mech) throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
static GSSException unavailable() throws GSSException {
|
||||
GSSException e = new GSSException(GSSException.UNAVAILABLE);
|
||||
log.error(e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
58
src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java
Normal file
58
src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package net.schmizz.sshj.util.gss;
|
||||
|
||||
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
|
||||
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
public class BogusGSSName
|
||||
implements GSSName {
|
||||
|
||||
private final String name;
|
||||
private final Oid oid;
|
||||
|
||||
public BogusGSSName(String name, Oid oid) {
|
||||
this.name = name;
|
||||
this.oid = oid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(GSSName another) throws GSSException {
|
||||
if (!(another instanceof BogusGSSName)) {
|
||||
throw new GSSException(GSSException.BAD_NAMETYPE);
|
||||
}
|
||||
BogusGSSName otherName = (BogusGSSName) another;
|
||||
return name.equals(otherName.name) && oid.equals(otherName.oid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GSSName canonicalize(Oid mech) throws GSSException {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] export() throws GSSException {
|
||||
throw unavailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Oid getStringNameType() throws GSSException {
|
||||
return oid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMN() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import com.hierynomus.sshj.test.util.FileUtil;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class SCPFileTransferTest {
|
||||
|
||||
public static final String DEFAULT_FILE_NAME = "my_file.txt";
|
||||
File targetDir;
|
||||
File sourceFile;
|
||||
File targetFile;
|
||||
SSHClient sshClient;
|
||||
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
sourceFile = tempFolder.newFile(DEFAULT_FILE_NAME);
|
||||
FileUtil.writeToFile(sourceFile, "This is my file");
|
||||
targetDir = tempFolder.newFolder();
|
||||
targetFile = new File(targetDir, DEFAULT_FILE_NAME);
|
||||
sshClient = fixture.setupConnectedDefaultClient();
|
||||
sshClient.authPassword("test", "test");
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
if (targetFile.exists()) {
|
||||
targetFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSCPUploadFile() throws IOException {
|
||||
SCPFileTransfer scpFileTransfer = sshClient.newSCPFileTransfer();
|
||||
assertFalse(targetFile.exists());
|
||||
assertTrue(targetDir.exists());
|
||||
scpFileTransfer.upload(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath());
|
||||
assertTrue(targetFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSCPUploadFileWithBandwidthLimit() throws IOException {
|
||||
// Limit upload transfer at 2Mo/s
|
||||
SCPFileTransfer scpFileTransfer = sshClient.newSCPFileTransfer().bandwidthLimit(16000);
|
||||
assertFalse(targetFile.exists());
|
||||
scpFileTransfer.upload(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath());
|
||||
assertTrue(targetFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSCPDownloadFile() throws IOException {
|
||||
SCPFileTransfer scpFileTransfer = sshClient.newSCPFileTransfer();
|
||||
assertFalse(targetFile.exists());
|
||||
scpFileTransfer.download(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath());
|
||||
assertTrue(targetFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSCPDownloadFileWithBandwidthLimit() throws IOException {
|
||||
// Limit download transfer at 128Ko/s
|
||||
SCPFileTransfer scpFileTransfer = sshClient.newSCPFileTransfer().bandwidthLimit(1024);
|
||||
assertFalse(targetFile.exists());
|
||||
scpFileTransfer.download(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath());
|
||||
assertTrue(targetFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSCPDownloadFileWithoutPathEscaping() throws IOException {
|
||||
SCPFileTransfer scpFileTransfer = sshClient.newSCPFileTransfer();
|
||||
assertFalse(targetFile.exists());
|
||||
File file = tempFolder.newFile("new file.txt");
|
||||
FileUtil.writeToFile(file, "Some content");
|
||||
scpFileTransfer.download(tempFolder.getRoot().getAbsolutePath() + "/new file.txt", targetFile.getAbsolutePath());
|
||||
assertTrue(targetFile.exists());
|
||||
assertThat(FileUtil.readFromFile(targetFile), CoreMatchers.containsString("Some content"));
|
||||
}
|
||||
}
|
||||
15
src/test/resources/keyformats/openssh
Normal file
15
src/test/resources/keyformats/openssh
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQCm2IJ9gWDkPTlQ37NNUB0za5mCsQ8bi++8fyEqw7wl8ZNBh3qt
|
||||
TcnL+m+NZfQjUC0BXic7PcMLVm4A3ID2IAZQM+axfq9aL4huWerm4ua6tvdt4gQK
|
||||
oL1+8JFmdFvFw5pWW/NZHtkIprbVf7KtYrU27WmMhXruN071UzqLsw08cwIDAQAB
|
||||
AoGAHQ7cOyuLSnT3RISRX8eyLkBxLffUX8HRcQzbI+2PGTSnpuQHk6NWn/Xv87pr
|
||||
+LKABBr3zjOFgrX81p2QwEz3jDxNXzbOeZzhuvGXCX5GocuEO4n5EhDvXRDF4uht
|
||||
uvVV5FsQv/sTOR0PNo1nELiAA8k3NYDxraB83q7wtsmErtECQQDYWMnq8mwRe49d
|
||||
jIXNKJeNiuLUYxO3CLI/vx279gDKlKrt677trr1e7JZqm/DapEWG511tw3cW63gQ
|
||||
+qxtgkw1AkEAxW0UeaNaJd7DApqwGAcS1JkygCKwzQ4ns/Co15qUgMkqCkmQU9AU
|
||||
/zQpt2+BjdYVe50r/nr8K1KYwrBsyndrBwJBALe90N+FvFqswfoFmq2/R9eimTsg
|
||||
WmIdNKYHPs2gBNQIp5MhoSpkOdkgvi8U+d33nkUQwryyQbZpjbN98mufOfECQEML
|
||||
eBiW0NZrf+4yefqu7EYmgG/jWAdK91C0OaJ+bFAQAKbdtJXB5F+GZ2RUCbsRKNqB
|
||||
1Z7mRRyxQA9dupRHWaECQQCM9bbCtfGesgvZlhBavlWavu8iCvJlAbGdf5QMlFQE
|
||||
kABmZg84Fy3NUFCD+RXCuatb4Oo9P/WPIbjYiC4p0hLJ
|
||||
-----END RSA PRIVATE KEY-----
|
||||
1
src/test/resources/keyformats/openssh.pub
Normal file
1
src/test/resources/keyformats/openssh.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCm2IJ9gWDkPTlQ37NNUB0za5mCsQ8bi++8fyEqw7wl8ZNBh3qtTcnL+m+NZfQjUC0BXic7PcMLVm4A3ID2IAZQM+axfq9aL4huWerm4ua6tvdt4gQKoL1+8JFmdFvFw5pWW/NZHtkIprbVf7KtYrU27WmMhXruN071UzqLsw08cw==
|
||||
15
src/test/resources/keyformats/pkcs8
Normal file
15
src/test/resources/keyformats/pkcs8
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQCm2IJ9gWDkPTlQ37NNUB0za5mCsQ8bi++8fyEqw7wl8ZNBh3qt
|
||||
TcnL+m+NZfQjUC0BXic7PcMLVm4A3ID2IAZQM+axfq9aL4huWerm4ua6tvdt4gQK
|
||||
oL1+8JFmdFvFw5pWW/NZHtkIprbVf7KtYrU27WmMhXruN071UzqLsw08cwIDAQAB
|
||||
AoGAHQ7cOyuLSnT3RISRX8eyLkBxLffUX8HRcQzbI+2PGTSnpuQHk6NWn/Xv87pr
|
||||
+LKABBr3zjOFgrX81p2QwEz3jDxNXzbOeZzhuvGXCX5GocuEO4n5EhDvXRDF4uht
|
||||
uvVV5FsQv/sTOR0PNo1nELiAA8k3NYDxraB83q7wtsmErtECQQDYWMnq8mwRe49d
|
||||
jIXNKJeNiuLUYxO3CLI/vx279gDKlKrt677trr1e7JZqm/DapEWG511tw3cW63gQ
|
||||
+qxtgkw1AkEAxW0UeaNaJd7DApqwGAcS1JkygCKwzQ4ns/Co15qUgMkqCkmQU9AU
|
||||
/zQpt2+BjdYVe50r/nr8K1KYwrBsyndrBwJBALe90N+FvFqswfoFmq2/R9eimTsg
|
||||
WmIdNKYHPs2gBNQIp5MhoSpkOdkgvi8U+d33nkUQwryyQbZpjbN98mufOfECQEML
|
||||
eBiW0NZrf+4yefqu7EYmgG/jWAdK91C0OaJ+bFAQAKbdtJXB5F+GZ2RUCbsRKNqB
|
||||
1Z7mRRyxQA9dupRHWaECQQCM9bbCtfGesgvZlhBavlWavu8iCvJlAbGdf5QMlFQE
|
||||
kABmZg84Fy3NUFCD+RXCuatb4Oo9P/WPIbjYiC4p0hLJ
|
||||
-----END RSA PRIVATE KEY-----
|
||||
18
src/test/resources/keyformats/pkcs8-blanks
Normal file
18
src/test/resources/keyformats/pkcs8-blanks
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQCm2IJ9gWDkPTlQ37NNUB0za5mCsQ8bi++8fyEqw7wl8ZNBh3qt
|
||||
TcnL+m+NZfQjUC0BXic7PcMLVm4A3ID2IAZQM+axfq9aL4huWerm4ua6tvdt4gQK
|
||||
oL1+8JFmdFvFw5pWW/NZHtkIprbVf7KtYrU27WmMhXruN071UzqLsw08cwIDAQAB
|
||||
AoGAHQ7cOyuLSnT3RISRX8eyLkBxLffUX8HRcQzbI+2PGTSnpuQHk6NWn/Xv87pr
|
||||
+LKABBr3zjOFgrX81p2QwEz3jDxNXzbOeZzhuvGXCX5GocuEO4n5EhDvXRDF4uht
|
||||
uvVV5FsQv/sTOR0PNo1nELiAA8k3NYDxraB83q7wtsmErtECQQDYWMnq8mwRe49d
|
||||
jIXNKJeNiuLUYxO3CLI/vx279gDKlKrt677trr1e7JZqm/DapEWG511tw3cW63gQ
|
||||
+qxtgkw1AkEAxW0UeaNaJd7DApqwGAcS1JkygCKwzQ4ns/Co15qUgMkqCkmQU9AU
|
||||
/zQpt2+BjdYVe50r/nr8K1KYwrBsyndrBwJBALe90N+FvFqswfoFmq2/R9eimTsg
|
||||
WmIdNKYHPs2gBNQIp5MhoSpkOdkgvi8U+d33nkUQwryyQbZpjbN98mufOfECQEML
|
||||
eBiW0NZrf+4yefqu7EYmgG/jWAdK91C0OaJ+bFAQAKbdtJXB5F+GZ2RUCbsRKNqB
|
||||
1Z7mRRyxQA9dupRHWaECQQCM9bbCtfGesgvZlhBavlWavu8iCvJlAbGdf5QMlFQE
|
||||
kABmZg84Fy3NUFCD+RXCuatb4Oo9P/WPIbjYiC4p0hLJ
|
||||
-----END RSA PRIVATE KEY-----
|
||||
19
src/test/resources/keyformats/putty
Normal file
19
src/test/resources/keyformats/putty
Normal file
@@ -0,0 +1,19 @@
|
||||
PuTTY-User-Key-File-2: ssh-rsa
|
||||
Encryption: none
|
||||
Comment: rsa-key-20150817
|
||||
Public-Lines: 4
|
||||
AAAAB3NzaC1yc2EAAAABJQAAAIEAhoCAakZdrCNrHNpJHRIED4movsposAlk4ZPz
|
||||
n/IFGkiIZOWRF/p4Sq+CaLigamwpe3f2/vYxCwtF3oCcJMQdn6CLYytHgrC2pRWa
|
||||
bNBBClSO4jzWMlBRBZnzXBHKJ04kviqIybEvrN2weg5ArSOK7297DU2id+kDxSJz
|
||||
QleZ+9c=
|
||||
Private-Lines: 8
|
||||
AAAAgBItCm858PuWFWTDjVb0mMPUVRLdFRDerMSJnXZ6pr5c1ClPdHjcqHjLnABQ
|
||||
TQd2Znh3/siCIk2ZvVVrU17qEdcZyivntbi8NuehqcVQr2ny8pc+sXvxXv2inoA3
|
||||
4mIVqjhIoljYf1VWgDXUxUsGU6QZdMfkDCEuoxL9QM7RdFXNAAAAQQDk01miHj3M
|
||||
LozpMovCU2oKDFLsmOrXe7jroff2sM4BX5Iwym4N197O7ZIs5E52K0bWIDH9SouX
|
||||
RTseqiHsPxexAAAAQQCWeZFwG7qm8cyAGSzsDsN35cmzgkvlFl4uIuJ9jh0S2Yt5
|
||||
2couD/AoQVmHqGaxwYPc+q24yvbFbjCxtlkqMTYHAAAAQQCcpItltKrhNW2svG2P
|
||||
NuOi0TQmMZQigMOYQx4wd/8G2n+nr0Qsi4wuq/qccgKViVRyobB7nxQqoJdGAI90
|
||||
ECNG
|
||||
Private-MAC: 4f200e5f5b766351b996d92f3b733b671b9ff957
|
||||
|
||||
Reference in New Issue
Block a user