Compare commits

...

82 Commits

Author SHA1 Message Date
Jeroen van Erp
c0487c9ee5 v0.15.0 2015-11-20 14:46:06 +01:00
Jeroen van Erp
3372db75b5 Updated README with release notes 2015-11-20 13:52:40 +01:00
Jeroen van Erp
db75bad25c Added support for ed25519 keys (Fixes #220) 2015-11-20 13:48:59 +01:00
Jeroen van Erp
a73776ad40 Merge pull request #226 from hierynomus/ecdsa_fix
Correctly calculating ECDSA key fingerprint (Fixes #225)
2015-11-18 22:30:57 +01:00
Jeroen van Erp
237c7d18b6 Correctly calculating ECDSA key fingerprint (Fixes #225) 2015-11-18 22:19:15 +01:00
Jeroen van Erp
b7c8cda851 Refactored test to be reusable for other algorithm variants 2015-11-18 12:41:11 +01:00
Jeroen van Erp
2b6fedc939 Added all RFC Ciphers and some extended ones 2015-11-11 22:10:29 +01:00
Jeroen van Erp
51e1ff24e4 Merge pull request #223 from fingolfin/patch-1
README.adoc: fix typos (exhange -> exchange)
2015-11-10 09:40:38 +01:00
Max Horn
05efcb4889 README.adoc: fix typos (exhange -> exchange) 2015-11-10 08:39:55 +01:00
Jeroen van Erp
d456612d25 Now v0.14.0 for real, forgot buildfile 2015-11-04 10:32:49 +01:00
Jeroen van Erp
6feed72251 v0.14.0 2015-11-04 10:27:51 +01:00
Jeroen van Erp
67e44241d0 Upgraded wrapper 2015-11-04 10:27:46 +01:00
Jeroen van Erp
a2a5923767 Added todo comment 2015-11-04 10:27:39 +01:00
Jeroen van Erp
bdf9ab7452 Added missing key signature algorithm to README 2015-11-04 09:40:50 +01:00
Jeroen van Erp
afdfa91eb7 Added curve25519-sha256@libssh.org to the default config 2015-11-03 16:22:18 +01:00
Jeroen van Erp
29a6cf6f79 Added support for curve25519-sha256@libssh.org (Fixes #171) 2015-11-03 16:18:53 +01:00
Jeroen van Erp
eece80cf48 Enabled ecdh-sha2-nistp521 2015-11-03 13:47:04 +01:00
Jeroen van Erp
7973cb1ff6 Upgraded Apache SSHD to 1.0.0 2015-11-03 13:43:00 +01:00
Jeroen van Erp
75c0ae9a83 Updated readme with new kex algorithms 2015-11-02 15:31:31 +01:00
Jeroen van Erp
f2314e74ed Added support for ecdh-sha2-nistp256 and ecdh-sha2-nistp384 key exchange algorithms 2015-11-02 15:29:47 +01:00
Jeroen van Erp
e041e3e1e3 updated release notes 2015-10-29 12:37:17 +01:00
Jeroen van Erp
47df71c836 Implemented diffie-hellman-group-exchange Kex methods (Fixes #167) 2015-10-29 12:30:58 +01:00
Jeroen van Erp
e24ed6ee7b Merge pull request #216 from juddgaddie/master
Throw a SCPRemoteException when an error occurs on the remote server
2015-10-28 22:46:51 +01:00
gaddiej
10f8645ecd Throw a SCPRemoteException when an error occurs on the remote server. SCPRemoteException contains the error message returned from the remote server. 2015-10-24 13:54:49 +00:00
Jeroen van Erp
d520585a09 SCP remote path escaping is now configurable (Fixes #212, #184, #152) 2015-09-21 14:51:57 +02:00
Jeroen van Erp
28a11b0b45 Merge pull request #210 from iterate-ch/issue-209
Fix issue 209.
2015-08-18 18:55:50 +02:00
Jeroen van Erp
a335185827 Moved to SNAPSHOT 2015-08-18 17:30:25 +02:00
Jeroen van Erp
74a4012023 Merge pull request #208 from lguerin/bandwidth
SCP : limit the used bandwidth
2015-08-18 17:23:55 +02:00
Billy Keyes
c98ad22a7a Skip blank lines when detecting key formats
Some private keys found in the wild start with a blank line, which
breaks SSHJ. OpenSSH utilities worked as expected with these key files.

Also add some basic tests for key formats.
2015-08-18 14:25:13 +02:00
Jeroen van Erp
1c749da957 v0.13.0 2015-08-18 14:25:13 +02:00
David Kocher
5d81e87bce Fix issue 209. 2015-08-17 10:11:55 +02:00
lguerin
d18e9d9961 Refactor SCP arguments 2015-08-13 14:44:45 +02:00
lguerin
84990ada08 Fix typo 2015-08-13 14:41:09 +02:00
lguerin
9c424f9431 Remove underscores from Test method's name 2015-08-13 11:37:15 +02:00
lguerin
dec00efcaa Fix typo and clarify rate transfer comment 2015-08-13 10:49:56 +02:00
Jeroen van Erp
742553912c Move to new travis infra 2015-08-13 10:23:08 +02:00
lguerin
e81fdb8d8b Verbose failed tests 2015-08-13 09:36:02 +02:00
lguerin
782ff9b83e Add an option to limit the used bandwidth with SCP upload and download features 2015-08-12 16:55:08 +02:00
Jeroen van Erp
84d15f4cf5 Removed old pom 2015-08-03 14:06:55 +02:00
Jeroen van Erp
1ebcbb07ba Fixed examples build 2015-08-03 14:06:38 +02:00
Jeroen van Erp
9982e5c30e Added testcase for #194 2015-06-17 21:49:05 +02:00
Jeroen van Erp
3f340d6927 Updated version in default config 2015-06-17 16:17:36 +02:00
Jeroen van Erp
b8eec64a37 Added tests and categories 2015-06-17 16:04:01 +02:00
Jeroen van Erp
314d9d01cf Updated release notes 2015-06-17 13:04:32 +02:00
Jeroen van Erp
c526f8e3de Merge branch 'bkarge-issue-183' 2015-06-17 12:37:55 +02:00
Jeroen van Erp
9529c30105 Reformatted 2015-06-17 12:36:31 +02:00
Jeroen van Erp
6a476858d1 Added RemoteFileTest 2015-06-17 12:28:57 +02:00
Jeroen van Erp
6bfb268c11 Merge branch 'issue-183' of https://github.com/bkarge/sshj into bkarge-issue-183 2015-06-17 12:12:56 +02:00
Jeroen van Erp
e334525da5 Rewritten integration tests 2015-06-17 12:12:37 +02:00
Jeroen van Erp
8776500fa0 Merge pull request #201 from iterate-ch/feature/algorithms-verifier
Add option for client to verify negotiated key exchange algorithms.
2015-06-16 15:50:06 +02:00
David Kocher
a747db88ed Add option for client to verify negotiated key exchange algorithms. 2015-06-16 15:42:01 +02:00
Jeroen van Erp
97065264de Cleared some JavaDoc warnings 2015-06-16 14:12:36 +02:00
Jeroen van Erp
7c26ac669a Started better integration testing setup with Mina 2015-06-16 14:12:24 +02:00
Jeroen van Erp
1c5b462206 Merge pull request #195 from bluekeyes/feature/gss-api
Add support for "gssapi-with-mic" authentication (Kerberos)
2015-06-16 10:33:44 +02:00
Jeroen van Erp
4cb9610cdd Merge pull request #196 from Boris-de/fix_hostname_matching
bugfix: match complete host instead of contains on the hoststring
2015-06-16 10:23:52 +02:00
Billy Keyes
b9d0a03cb3 Add simple test for AuthGssApiWithMic
Mock enough of the JGSS API to avoid needing a real Kerberos
environment. I'm not sure how accurate this is, but it should test that
the client is sending the correct packets in the corect order.
2015-06-11 11:44:44 -07:00
Billy Keyes
4adc83b9df Expose GSSManager in AuthGssApiWithMic
The default implementation only supports Kerberos and encourages
subclassing, so there should be a way to provide subclasses.
2015-06-11 11:38:02 -07:00
Björn Karge
14edb33fa9 fix for issue 183 (sftp.RemoteFile.ReadAheadRemoteFileInputStream) (revised) 2015-06-04 10:50:13 +08:00
Björn Karge
8e74330b0b fix for issue 183 (sftp.RemoteFile.ReadAheadRemoteFileInputStream) 2015-06-03 14:36:23 +08:00
Boris Wachtmeister
5217d34198 bugfix: match complete host instead of contains on the hoststring
The SimpleEntry currently matches the hostname of the connection against
the complete hoststring of the entry. This way substrings also match, so
for example "10.0.0.1" matches on an entry for "10.0.0.10", resulting in
a host-key-changed message if the key differs which is usually does.
2015-05-28 21:57:25 +02:00
Billy Keyes
d3d019c1c2 Remove unused imports in SSHClient 2015-05-19 10:49:20 -07:00
Ben Hamme
49185b044d Added AuthGssApiWithMic for Kerberos auth 2015-05-18 15:00:11 -07:00
Jeroen van Erp
a18d623f44 Release notes for 0.12.0 2015-04-14 13:14:15 +02:00
Jeroen van Erp
6855873ffd removed release plugin 2015-04-14 12:57:40 +02:00
Jeroen van Erp
2ca8d8b19e Upgraded gradle 2015-04-14 12:03:38 +02:00
Jeroen van Erp
da32b145df Added braces to single-line statements 2015-04-13 11:50:34 +02:00
Jeroen van Erp
8ea6bb4a66 Updated README for fixed #181 and #180 issue 2015-04-01 11:28:14 +02:00
Jeroen van Erp
6cf767528a Added comment to check why that field is needed 2015-04-01 11:25:43 +02:00
Jeroen van Erp
b123a6ae30 Merge pull request #181 from iterate-ch/issue-180
Fix length field for SSH_FXP_WRITE packets.
2015-04-01 11:24:30 +02:00
Jeroen van Erp
4250c61e45 Deprecated Proxy connect methods, moved Sockets utility class to backport package 2015-03-31 09:27:13 +02:00
Jeroen van Erp
ace09fa8c8 Added support for HTTP CONNECT proxies by implementing custom Socket (Fixes #170) 2015-03-30 21:53:04 +02:00
Jeroen van Erp
8398b6e3c3 Revert "Added support for (unauthenticated) HTTP proxies (fixes #170)"
This reverts commit fc535a5e76.
2015-03-30 21:48:07 +02:00
Jeroen van Erp
3c1e0c1629 Updated readme 2015-03-27 16:04:42 +01:00
Andrew Kondratovich
e6c7c17664 Correctly closing channel and socket when LocalPortForwarder fails to open it. (Fix #175) 2015-03-27 16:02:07 +01:00
Jeroen van Erp
1e061aef25 Updated readme with work in progress for next release 2015-03-27 15:30:58 +01:00
Jeroen van Erp
66b772bac1 Compiling for JDK6, fixes #179 and #185 2015-03-27 15:25:45 +01:00
Jeroen van Erp
fc535a5e76 Added support for (unauthenticated) HTTP proxies (fixes #170) 2015-03-27 15:19:04 +01:00
Jeroen van Erp
c7373f05cc Merge pull request #186 from bluekeyes/fix/read-end-of-stream
Detect end-of-stream in TransportImpl#init
2015-03-16 10:34:47 +01:00
Billy Keyes
3ebd2eb363 Detect end-of-stream in TransportImpl#init
OpenSSH will drop connections based on the value of MaxStartups when
there are too many unauthenticated connection. When this happens, reads
on the client socket return -1, which was previously inserted into the
identification buffer, leading to the error in #118.
2015-03-13 15:34:18 -07:00
David Kocher
8638091517 Fix length field for SSH_FXP_WRITE packets. 2015-02-24 15:27:08 +01:00
hierynomus
5fc08a3fc8 back to snapshot 2015-01-23 10:08:51 +01:00
hierynomus
92df7c6924 v0.11.0 2015-01-23 10:07:43 +01:00
106 changed files with 3549 additions and 1018 deletions

View File

@@ -1 +1,2 @@
language: java
language: java
sudo: false

View File

@@ -1,7 +1,7 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.11.0
:sshj_version: 0.15.0
:source-highlighter: pygments
image::https://travis-ci.org/hierynomus/sshj.svg?branch=master[]
@@ -59,13 +59,15 @@ In the `examples` directory, there is a separate Maven project that shows how th
Implementations / adapters for the following algorithms are included:
ciphers::
`aes{128,192,256}-{cbc,ctr}`, `blowfish-cbc`, `3des-cbc`
`aes{128,192,256}-{cbc,ctr}`, `blowfish-{cbc,ctr}`, `3des-{cbc,ctr}`, `twofish{128,192,256}-{cbc,ctr}`, `twofish-cbc`, `serpent{128,192,256}-{cbc,ctr}`, `idea-{cbc,ctr}`, `cast128-{cbc,ctr}`, `arcfour`, `arcfour{128,256}`
SSHJ also supports the following extended (non official) ciphers: `camellia{128,192,256}-{cbc,ctr}`, `camellia{128,192,256}-{cbc,ctr}@openssh.org`
key exchange::
`diffie-hellman-group1-sha1`, `diffie-hellman-group14-sha1`
`diffie-hellman-group1-sha1`, `diffie-hellman-group14-sha1`, `diffie-hellman-group-exchange-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`, `ssh-ed25519
mac::
`hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`
@@ -78,6 +80,8 @@ private key files::
If you need something that is not included, it shouldn't be too hard to add (do contribute it!)
== Comparing to other implementations
http://ssh-comparison.quendi.de/comparison.html[SSH Implementation Comparison]
== Dependencies
Java 6+. http://www.slf4j.org/download.html[slf4j] is required. http://www.bouncycastle.org/java.html[bouncycastle] is highly recommended and required for using some of the crypto algorithms. http://www.jcraft.com/jzlib/[jzlib] is required for using zlib compression.
@@ -92,15 +96,38 @@ Google Group: http://groups.google.com/group/sshj-users
Fork away!
== Release history
* SSHJ 0.11.0 (No date set yet)
** New maven coordinates `com.hierynomus:sshj:0.11.0` as https://github.com/hierynomus[@hierynomus] took over as maintainer of SSHJ
** Migrated build system to Gradle 2.2.1
** Merged https://github.com/hierynomus/sshj/issues/150[#150]: Fix for incorrect file handle on some SSH servers, fixes: https://github.com/hierynomus/sshj/issues/54[#54], https://github.com/hierynomus/sshj/issues/119[#119], https://github.com/hierynomus/sshj/issues/168[#168], https://github.com/hierynomus/sshj/issues/169[#169]
** Made `jzlib` optional in OSGi bundling, fixes: https://github.com/hierynomus/sshj/issues/162[#162]
** Improved some log levels, fixes: https://github.com/hierynomus/sshj/issues/161[#161]
** Merged https://github.com/hierynomus/sshj/issues/156[#156], https://github.com/hierynomus/sshj/issues/164[#164], https://github.com/hierynomus/sshj/issues/165[#165]: Fixed block sizes for `hmac-sha2-256` and `hmac-sha2-512`
** Merged https://github.com/hierynomus/sshj/issues/141[#141]: Add proxy support
** Merged https://github.com/hierynomus/sshj/issues/157[#157], https://github.com/hierynomus/sshj/issues/163[#163]: Doc and build fixes
** Upgraded BouncyCastle to 1.51, fixes: https://github.com/hierynomus/sshj/issues/142[#142]
** Implemented keep-alive with connection drop detection, fixes https://github.com/hierynomus/sshj/issues/166[#166]
SSHJ 0.15.0 (2015-11-20)::
* Fixed https://github.com/hierynomus/sshj/issues/220[#220]: Added support for `ssh-ed25519` host keys
* Fixed https://github.com/hierynomus/sshj/issues/225[#225]: Fixed bug in ECDSA fingerprint calculation that sometimes produced an incorrect fingerprint
* Added `arcfour` Stream Ciphers from RFC4253 and RFC4345
* Added all Block Ciphers from RFC4344 and RFC4253
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-exchange-sha1` and `diffie-hellman-group-exchange-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
* Compiling to JDK6, fixes https://github.com/hierynomus/sshj/issues/179[#179] and https://github.com/hierynomus/sshj/issues/185[#185]
* Correctly close socket and channel when LocalPortForwarder fails to open and start the channel (Fixes https://github.com/hierynomus/sshj/issues/175[#175] and https://github.com/hierynomus/sshj/issues/176[#176])
* Merged https://github.com/hierynomus/sshj/issues/181[#181]: Invalid write packet length when reading with offset (Fixes https://github.com/hierynomus/sshj/issues/180[#180])
SSHJ 0.11.0 (2015-01-23)::
* New maven coordinates `com.hierynomus:sshj:0.11.0` as https://github.com/hierynomus[@hierynomus] took over as maintainer of SSHJ
* Migrated build system to Gradle 2.2.1
* Merged https://github.com/hierynomus/sshj/issues/150[#150]: Fix for incorrect file handle on some SSH servers, fixes: https://github.com/hierynomus/sshj/issues/54[#54], https://github.com/hierynomus/sshj/issues/119[#119], https://github.com/hierynomus/sshj/issues/168[#168], https://github.com/hierynomus/sshj/issues/169[#169]
* Made `jzlib` optional in OSGi bundling, fixes: https://github.com/hierynomus/sshj/issues/162[#162]
* Improved some log levels, fixes: https://github.com/hierynomus/sshj/issues/161[#161]
* Merged https://github.com/hierynomus/sshj/issues/156[#156], https://github.com/hierynomus/sshj/issues/164[#164], https://github.com/hierynomus/sshj/issues/165[#165]: Fixed block sizes for `hmac-sha2-256` and `hmac-sha2-512`
* Merged https://github.com/hierynomus/sshj/issues/141[#141]: Add proxy support
* Merged https://github.com/hierynomus/sshj/issues/157[#157], https://github.com/hierynomus/sshj/issues/163[#163]: Doc and build fixes
* Upgraded BouncyCastle to 1.51, fixes: https://github.com/hierynomus/sshj/issues/142[#142]
* Implemented keep-alive with connection drop detection, fixes https://github.com/hierynomus/sshj/issues/166[#166]

View File

@@ -4,13 +4,16 @@ apply plugin: "signing"
apply plugin: "osgi"
group = "com.hierynomus"
version = "0.11.0-SNAPSHOT"
version = "0.15.0"
repositories {
mavenCentral()
mavenLocal()
}
sourceCompatibility = 1.6
targetCompatibility = 1.6
configurations {
compile {
transitive = false
@@ -18,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"
@@ -38,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"
}
@@ -155,4 +170,4 @@ uploadArchives {
}
}
}
}
}

View File

@@ -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.15.0</version>
</dependency>
</dependencies>

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#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
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip

344
pom.xml
View File

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

View File

@@ -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());
}
}

View File

@@ -0,0 +1,13 @@
package com.hierynomus.sshj.backport;
import java.math.BigDecimal;
public class JavaVersion {
public static boolean isJava7OrEarlier() {
String property = System.getProperty("java.specification.version");
float diff = Float.parseFloat(property) - 1.7f;
return diff < 0.01;
}
}

View File

@@ -0,0 +1,62 @@
package com.hierynomus.sshj.backport;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.nio.charset.Charset;
public class Jdk7HttpProxySocket extends Socket {
private Proxy httpProxy = null;
public Jdk7HttpProxySocket(Proxy proxy) {
super(proxy.type() == Proxy.Type.HTTP ? Proxy.NO_PROXY : proxy);
if (proxy.type() == Proxy.Type.HTTP) {
this.httpProxy = proxy;
}
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
if (httpProxy != null) {
connectHttpProxy(endpoint, timeout);
} else {
super.connect(endpoint, timeout);
}
}
private void connectHttpProxy(SocketAddress endpoint, int timeout) throws IOException {
super.connect(httpProxy.address(), timeout);
if (!(endpoint instanceof InetSocketAddress)) {
throw new SocketException("Expected an InetSocketAddress to connect to, got: " + endpoint);
}
InetSocketAddress isa = (InetSocketAddress) endpoint;
String httpConnect = "CONNECT " + isa.getHostName() + ":" + isa.getPort() + " HTTP/1.0\n\n";
getOutputStream().write(httpConnect.getBytes(Charset.forName("UTF-8")));
checkAndFlushProxyResponse();
}
private void checkAndFlushProxyResponse()throws IOException {
InputStream socketInput = getInputStream();
byte[] tmpBuffer = new byte[512];
int len = socketInput.read(tmpBuffer, 0, tmpBuffer.length);
if (len == 0) {
throw new SocketException("Empty response from proxy");
}
String proxyResponse = new String(tmpBuffer, 0, len, "UTF-8");
// Expecting HTTP/1.x 200 OK
if (proxyResponse.contains("200")) {
// Flush any outstanding message in buffer
if (socketInput.available() > 0) {
socketInput.skip(socketInput.available());
}
// Proxy Connect Successful
} else {
throw new SocketException("Fail to create Socket\nResponse was:" + proxyResponse);
}
}
}

View File

@@ -0,0 +1,26 @@
package com.hierynomus.sshj.backport;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
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 The (potentially wrapped) Socket as a Closeable.
*/
public static Closeable asCloseable(final Socket socket) {
if (Closeable.class.isAssignableFrom(socket.getClass())) {
return Closeable.class.cast(socket);
} else {
return new Closeable() {
@Override
public void close() throws IOException {
socket.close();
}
};
}
}
}

View File

@@ -0,0 +1,55 @@
package com.hierynomus.sshj.secg;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.math.BigInteger;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.util.Arrays;
public class SecgUtils {
/**
* SECG 2.3.4 Octet String to ECPoint
*/
public 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
*/
public 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;
}
}

View File

@@ -0,0 +1,40 @@
package com.hierynomus.sshj.signature;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.util.Arrays;
/**
* Our own extension of the EdDSAPublicKey that comes from ECC-25519, as that class does not implement equality.
* The code uses the equality of the keys as an indicator whether they're the same during host key verification.
*/
public class Ed25519PublicKey extends EdDSAPublicKey {
public Ed25519PublicKey(EdDSAPublicKeySpec spec) {
super(spec);
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512");
if (!spec.getParams().getCurve().equals(ed25519.getCurve())) {
throw new SSHRuntimeException("Cannot create Ed25519 Public Key from wrong spec");
}
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Ed25519PublicKey)) {
return false;
}
Ed25519PublicKey otherKey = (Ed25519PublicKey) other;
return Arrays.equals(getAbyte(), otherKey.getAbyte());
}
@Override
public int hashCode() {
return getA().hashCode();
}
}

View File

@@ -0,0 +1,94 @@
package com.hierynomus.sshj.signature;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.signature.Signature;
import java.security.*;
public class SignatureEdDSA implements Signature {
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Signature> {
@Override
public String getName() {
return KeyType.ED25519.toString();
}
@Override
public Signature create() {
return new SignatureEdDSA();
}
}
final EdDSAEngine engine;
protected SignatureEdDSA() {
try {
engine = new EdDSAEngine(MessageDigest.getInstance("SHA-512"));
} catch (NoSuchAlgorithmException e) {
throw new SSHRuntimeException(e);
}
}
@Override
public void init(PublicKey pubkey, PrivateKey prvkey) {
try {
if (pubkey != null) {
engine.initVerify(pubkey);
}
if (prvkey != null) {
engine.initSign(prvkey);
}
} catch (InvalidKeyException e) {
throw new SSHRuntimeException(e);
}
}
@Override
public void update(byte[] H) {
update(H, 0, H.length);
}
@Override
public void update(byte[] H, int off, int len) {
try {
engine.update(H, off, len);
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
}
@Override
public byte[] sign() {
try {
return engine.sign();
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
}
@Override
public byte[] encode(byte[] signature) {
return signature;
}
@Override
public boolean verify(byte[] sig) {
try {
Buffer.PlainBuffer plainBuffer = new Buffer.PlainBuffer(sig);
String algo = plainBuffer.readString();
if (!"ssh-ed25519".equals(algo)) {
throw new SSHRuntimeException("Expected 'ssh-ed25519' key algorithm, but was: " + algo);
}
byte[] bytes = plainBuffer.readBytes();
return engine.verify(bytes);
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
} catch (Buffer.BufferException e) {
throw new SSHRuntimeException(e);
}
}
}

View File

@@ -0,0 +1,122 @@
package com.hierynomus.sshj.transport.cipher;
import net.schmizz.sshj.transport.cipher.BaseCipher;
import net.schmizz.sshj.transport.cipher.BlockCipher;
import net.schmizz.sshj.transport.cipher.Cipher;
/**
* All BlockCiphers supported by SSH according to the following RFCs
*
* - https://tools.ietf.org/html/rfc4344#section-3.1
* - https://tools.ietf.org/html/rfc4253#section-6.3
*
* TODO: https://tools.ietf.org/html/rfc5647
*
* Some of the Ciphers are still implemented in net.schmizz.sshj.transport.cipher.*. These are scheduled to be migrated to here.
*/
public class BlockCiphers {
public static final String COUNTER_MODE = "CTR";
public static final String CIPHER_BLOCK_CHAINING_MODE = "CBC";
public static Factory BlowfishCTR() {
return new Factory(8, 256, "blowfish-ctr", "Blowfish", COUNTER_MODE);
}
public static Factory Twofish128CTR() {
return new Factory(16, 128, "twofish128-ctr", "Twofish", COUNTER_MODE);
}
public static Factory Twofish192CTR() {
return new Factory(16, 192, "twofish192-ctr", "Twofish", COUNTER_MODE);
}
public static Factory Twofish256CTR() {
return new Factory(16, 256, "twofish256-ctr", "Twofish", COUNTER_MODE);
}
public static Factory Twofish128CBC() {
return new Factory(16, 128, "twofish128-cbc", "Twofish", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory Twofish192CBC() {
return new Factory(16, 192, "twofish192-cbc", "Twofish", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory Twofish256CBC() {
return new Factory(16, 256, "twofish256-cbc", "Twofish", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory TwofishCBC() {
return new Factory(16, 256, "twofish-cbc", "Twofish", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory Serpent128CTR() {
return new Factory(16, 128, "serpent128-ctr", "Serpent", COUNTER_MODE);
}
public static Factory Serpent192CTR() {
return new Factory(16, 192, "serpent192-ctr", "Serpent", COUNTER_MODE);
}
public static Factory Serpent256CTR() {
return new Factory(16, 256, "serpent256-ctr", "Serpent", COUNTER_MODE);
}
public static Factory Serpent128CBC() {
return new Factory(16, 128, "serpent128-cbc", "Serpent", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory Serpent192CBC() {
return new Factory(16, 192, "serpent192-cbc", "Serpent", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory Serpent256CBC() {
return new Factory(16, 256, "serpent256-cbc", "Serpent", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory IDEACTR() {
return new Factory(8, 128, "idea-ctr", "IDEA", COUNTER_MODE);
}
public static Factory IDEACBC() {
return new Factory(8, 128, "idea-cbc", "IDEA", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory Cast128CTR() {
return new Factory(8, 128, "cast128-ctr", "CAST5", COUNTER_MODE);
}
public static Factory Cast128CBC() {
return new Factory(8, 128, "cast128-cbc", "CAST5", CIPHER_BLOCK_CHAINING_MODE);
}
public static Factory TripleDESCTR() {
return new Factory(8, 192, "3des-ctr", "DESede", COUNTER_MODE);
}
/** Named factory for BlockCipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
private int keysize;
private String cipher;
private String mode;
private String name;
private int ivsize;
/**
* @param ivsize
* @param keysize The keysize used in bits.
* @param name
* @param cipher
* @param mode
*/
public Factory(int ivsize, int keysize, String name, String cipher, String mode) {
this.name = name;
this.keysize = keysize;
this.cipher = cipher;
this.mode = mode;
this.ivsize = ivsize;
}
@Override
public Cipher create() {
return new BlockCipher(ivsize, keysize / 8, cipher, cipher + "/" + mode + "/NoPadding");
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
}
}

View File

@@ -0,0 +1,52 @@
package com.hierynomus.sshj.transport.cipher;
import static com.hierynomus.sshj.transport.cipher.BlockCiphers.CIPHER_BLOCK_CHAINING_MODE;
import static com.hierynomus.sshj.transport.cipher.BlockCiphers.COUNTER_MODE;
/**
* Set of Block Ciphers that are (not yet) part of any of the official RFCs for SSH, but
* that are either supported by other SSH implementations, or are being pushed for to be
* included in a new RFC.
*
* - http://tools.ietf.org/id/draft-kanno-secsh-camellia-01.txt
*/
public class ExtendedBlockCiphers {
public static BlockCiphers.Factory Camellia128CTR() {
return new BlockCiphers.Factory(16, 128, "camellia128-ctr", "Camellia", COUNTER_MODE);
}
public static BlockCiphers.Factory Camellia128CTROpenSSHOrg() {
return new BlockCiphers.Factory(16, 128, "camellia128-ctr@openssh.org", "Camellia", COUNTER_MODE);
}
public static BlockCiphers.Factory Camellia192CTR() {
return new BlockCiphers.Factory(16, 192, "camellia192-ctr", "Camellia", COUNTER_MODE);
}
public static BlockCiphers.Factory Camellia192CTROpenSSHOrg() {
return new BlockCiphers.Factory(16, 192, "camellia192-ctr@openssh.org", "Camellia", COUNTER_MODE);
}
public static BlockCiphers.Factory Camellia256CTR() {
return new BlockCiphers.Factory(16, 256, "camellia256-ctr", "Camellia", COUNTER_MODE);
}
public static BlockCiphers.Factory Camellia256CTROpenSSHOrg() {
return new BlockCiphers.Factory(16, 256, "camellia256-ctr@openssh.org", "Camellia", COUNTER_MODE);
}
public static BlockCiphers.Factory Camellia128CBC() {
return new BlockCiphers.Factory(16, 128, "camellia128-cbc", "Camellia", CIPHER_BLOCK_CHAINING_MODE);
}
public static BlockCiphers.Factory Camellia128CBCOpenSSHOrg() {
return new BlockCiphers.Factory(16, 128, "camellia128-cbc@openssh.org", "Camellia", CIPHER_BLOCK_CHAINING_MODE);
}
public static BlockCiphers.Factory Camellia192CBC() {
return new BlockCiphers.Factory(16, 192, "camellia192-cbc", "Camellia", CIPHER_BLOCK_CHAINING_MODE);
}
public static BlockCiphers.Factory Camellia192CBCOpenSSHOrg() {
return new BlockCiphers.Factory(16, 192, "camellia192-cbc@openssh.org", "Camellia", CIPHER_BLOCK_CHAINING_MODE);
}
public static BlockCiphers.Factory Camellia256CBC() {
return new BlockCiphers.Factory(16, 256, "camellia256-cbc", "Camellia", CIPHER_BLOCK_CHAINING_MODE);
}
public static BlockCiphers.Factory Camellia256CBCOpenSSHOrg() {
return new BlockCiphers.Factory(16, 256, "camellia256-cbc@openssh.org", "Camellia", CIPHER_BLOCK_CHAINING_MODE);
}
}

View File

@@ -0,0 +1,19 @@
package com.hierynomus.sshj.transport.cipher;
import net.schmizz.sshj.transport.cipher.BaseCipher;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
public class StreamCipher extends BaseCipher {
public StreamCipher(int bsize, String algorithm, String transformation) {
super(0, bsize, algorithm, transformation);
}
@Override
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
cipher.init(getMode(mode), getKeySpec(key), new SecureRandom());
}
}

View File

@@ -0,0 +1,61 @@
package com.hierynomus.sshj.transport.cipher;
import net.schmizz.sshj.transport.cipher.Cipher;
/**
* Implementations of the Stream Ciphers that are defined in the RFCs
*
* - https://tools.ietf.org/html/rfc4253#section-6.3
* - https://tools.ietf.org/html/rfc4345
*/
public class StreamCiphers {
public static Factory Arcfour() {
return new Factory(128, "arcfour", "ARCFOUR", "ECB");
}
public static Factory Arcfour128() {
return new Factory(128, "arcfour128", "RC4", "ECB");
}
public static Factory Arcfour256() {
return new Factory(256, "arcfour256", "RC4", "ECB");
}
/** Named factory for BlockCipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
private int keysize;
private String cipher;
private String mode;
private String name;
/**
* @param keysize The keysize used in bits.
* @param name
* @param cipher
* @param mode
*/
public Factory(int keysize, String name, String cipher, String mode) {
this.name = name;
this.keysize = keysize;
this.cipher = cipher;
this.mode = mode;
}
@Override
public Cipher create() {
return new StreamCipher(keysize / 8, cipher, cipher + "/" + mode + "/NoPadding");
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
}
}

View File

@@ -15,6 +15,9 @@
*/
package net.schmizz.sshj;
import com.hierynomus.sshj.signature.SignatureEdDSA;
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
import com.hierynomus.sshj.transport.cipher.StreamCiphers;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils;
@@ -31,8 +34,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;
@@ -49,6 +51,7 @@ import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.image.ByteLookupTable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
@@ -81,7 +84,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 +101,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) {
@@ -124,7 +134,29 @@ public class DefaultConfig
new AES192CBC.Factory(),
new AES256CBC.Factory(),
new TripleDESCBC.Factory(),
new BlowfishCBC.Factory()));
new BlowfishCBC.Factory(),
BlockCiphers.BlowfishCTR(),
BlockCiphers.Cast128CBC(),
BlockCiphers.Cast128CTR(),
BlockCiphers.IDEACBC(),
BlockCiphers.IDEACTR(),
BlockCiphers.Serpent128CBC(),
BlockCiphers.Serpent128CTR(),
BlockCiphers.Serpent192CBC(),
BlockCiphers.Serpent192CTR(),
BlockCiphers.Serpent256CBC(),
BlockCiphers.Serpent256CTR(),
BlockCiphers.TripleDESCTR(),
BlockCiphers.Twofish128CBC(),
BlockCiphers.Twofish128CTR(),
BlockCiphers.Twofish192CBC(),
BlockCiphers.Twofish192CTR(),
BlockCiphers.Twofish256CBC(),
BlockCiphers.Twofish256CTR(),
BlockCiphers.TwofishCBC(),
StreamCiphers.Arcfour(),
StreamCiphers.Arcfour128(),
StreamCiphers.Arcfour256()));
boolean warn = false;
// Ref. https://issues.apache.org/jira/browse/SSHD-24
@@ -138,6 +170,7 @@ public class DefaultConfig
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (Exception e) {
warn = true;
log.warn(e.getCause().getMessage());
i.remove();
}
}
@@ -145,10 +178,11 @@ public class DefaultConfig
log.warn("Disabling high-strength ciphers: cipher strengths apparently limited by JCE policy");
setCipherFactories(avail);
log.debug("Available cipher factories: {}", avail);
}
protected void initSignatureFactories() {
setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory());
setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory(), new SignatureEdDSA.Factory());
}
protected void initMACFactories() {

View File

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

View File

@@ -15,6 +15,9 @@
*/
package net.schmizz.sshj;
import com.hierynomus.sshj.backport.JavaVersion;
import com.hierynomus.sshj.backport.Jdk7HttpProxySocket;
import javax.net.SocketFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -45,34 +48,53 @@ public abstract class SocketClient {
this.defaultPort = defaultPort;
}
public void connect(InetAddress host, int port)
throws IOException {
public void connect(InetAddress host, int port) throws IOException {
socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(host, port), connectTimeout);
onConnect();
}
public void connect(InetAddress host, int port, Proxy proxy)
throws IOException {
socket = new Socket(proxy);
/**
* Connect to a host via a proxy.
* @param host The host address to connect to.
* @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#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
socket = new Jdk7HttpProxySocket(proxy);
} else {
socket = new Socket(proxy);
}
socket.connect(new InetSocketAddress(host, port), connectTimeout);
onConnect();
}
public void connect(String hostname, int port)
throws IOException {
public void connect(String hostname, int port) throws IOException {
this.hostname = hostname;
connect(InetAddress.getByName(hostname), port);
}
public void connect(String hostname, int port, Proxy proxy)
throws IOException {
/**
* Connect to a host via a proxy.
* @param hostname The host name to connect to.
* @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#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(String hostname, int port, Proxy proxy) throws IOException {
this.hostname = hostname;
connect(InetAddress.getByName(hostname), port, proxy);
}
public void connect(InetAddress host, int port,
InetAddress localAddr, int localPort)
public void connect(InetAddress host, int port, InetAddress localAddr, int localPort)
throws IOException {
socket = socketFactory.createSocket();
socket.bind(new InetSocketAddress(localAddr, localPort));
@@ -80,35 +102,44 @@ public abstract class SocketClient {
onConnect();
}
public void connect(String hostname, int port,
InetAddress localAddr, int localPort)
throws IOException {
public void connect(String hostname, int port, InetAddress localAddr, int localPort) throws IOException {
this.hostname = hostname;
connect(InetAddress.getByName(hostname), port, localAddr, localPort);
}
public void connect(InetAddress host)
throws IOException {
public void connect(InetAddress host) throws IOException {
connect(host, defaultPort);
}
public void connect(String hostname)
throws IOException {
public void connect(String hostname) throws IOException {
connect(hostname, defaultPort);
}
public void connect(InetAddress host, Proxy proxy)
throws IOException {
/**
* Connect to a host via a proxy.
* @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#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(InetAddress host, Proxy proxy) throws IOException {
connect(host, defaultPort, proxy);
}
public void connect(String hostname, Proxy proxy)
throws IOException {
/**
* Connect to a host via a proxy.
* @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#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(String hostname, Proxy proxy) throws IOException {
connect(hostname, defaultPort, proxy);
}
public void disconnect()
throws IOException {
public void disconnect() throws IOException {
if (socket != null) {
socket.close();
socket = null;
@@ -131,7 +162,6 @@ public abstract class SocketClient {
return socket.getLocalPort();
}
public InetAddress getLocalAddress() {
return socket.getLocalAddress();
}
@@ -149,10 +179,11 @@ public abstract class SocketClient {
}
public void setSocketFactory(SocketFactory factory) {
if (factory == null)
if (factory == null) {
socketFactory = SocketFactory.getDefault();
else
} else {
socketFactory = factory;
}
}
public SocketFactory getSocketFactory() {
@@ -187,8 +218,7 @@ public abstract class SocketClient {
return output;
}
void onConnect()
throws IOException {
void onConnect() throws IOException {
socket.setSoTimeout(timeout);
input = socket.getInputStream();
output = socket.getOutputStream();

View File

@@ -15,6 +15,14 @@
*/
package net.schmizz.sshj.common;
import com.hierynomus.sshj.secg.SecgUtils;
import com.hierynomus.sshj.signature.Ed25519PublicKey;
import net.i2p.crypto.eddsa.*;
import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECParameterSpec;
@@ -25,6 +33,7 @@ import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.*;
import java.security.KeyFactory;
import java.security.interfaces.*;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
@@ -135,7 +144,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);
@@ -151,35 +160,54 @@ public enum KeyType {
@Override
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
final ECPublicKey ecdsa = (ECPublicKey) pk;
final java.security.spec.ECPoint point = ecdsa.getW();
final byte[] x = trimStartingZeros(point.getAffineX().toByteArray());
final byte[] y = trimStartingZeros(point.getAffineY().toByteArray());
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
buf.putString(sType)
.putString(NISTP_CURVE)
.putUInt32(1 + x.length + y.length)
.putRawBytes(new byte[] { (byte) 0x04 })
.putRawBytes(x)
.putRawBytes(y)
;
.putBytes(encoded);
}
@Override
protected boolean isMyType(Key key) {
return ("ECDSA".equals(key.getAlgorithm()));
}
},
private byte[] trimStartingZeros(byte[] in) {
int i = 0;
for (; i < in.length; i++) {
if (in[i] != 0) {
break;
ED25519("ssh-ed25519") {
private final Logger logger = LoggerFactory.getLogger(KeyType.class);
@Override
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf) throws GeneralSecurityException {
try {
final int keyLen = buf.readUInt32AsInt();
final byte[] p = new byte[keyLen];
buf.readRawBytes(p);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Key algo: %s, Key curve: 25519, Key Len: %s\np: %s",
type,
keyLen,
Arrays.toString(p))
);
}
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512");
GroupElement point = ed25519.getCurve().createPoint(p, true);
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(point, ed25519);
return new Ed25519PublicKey(publicSpec);
} catch (Buffer.BufferException be) {
throw new SSHRuntimeException(be);
}
final byte[] out = new byte[in.length - i];
System.arraycopy(in, i, out, 0, out.length);
return out;
}
@Override
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
EdDSAPublicKey key = (EdDSAPublicKey) pk;
buf.putString(sType).putBytes(key.getAbyte());
}
@Override
protected boolean isMyType(Key key) {
return "EdDSA".equals(key.getAlgorithm());
}
},

View File

@@ -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),

View File

@@ -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);

View File

@@ -18,11 +18,12 @@ package net.schmizz.sshj.connection.channel;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.common.IOUtils;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import static com.hierynomus.sshj.backport.Sockets.asCloseable;
public class SocketStreamCopyMonitor
extends Thread {
@@ -32,16 +33,6 @@ public class SocketStreamCopyMonitor
setDaemon(true);
}
private static Closeable wrapSocket(final Socket socket) {
return new Closeable() {
@Override
public void close()
throws IOException {
socket.close();
}
};
}
public static void monitor(final int frequency, final TimeUnit unit,
final Event<IOException> x, final Event<IOException> y,
final Channel channel, final Socket socket) {
@@ -54,7 +45,7 @@ public class SocketStreamCopyMonitor
}
} catch (IOException ignored) {
} finally {
IOUtils.closeQuietly(channel, wrapSocket(socket));
IOUtils.closeQuietly(channel, asCloseable(socket));
}
}
}).start();

View File

@@ -16,12 +16,12 @@
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.SocketStreamCopyMonitor;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,6 +30,8 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import static com.hierynomus.sshj.backport.Sockets.asCloseable;
public class LocalPortForwarder {
public static class Parameters {
@@ -112,11 +114,15 @@ public class LocalPortForwarder {
this.serverSocket = serverSocket;
}
protected DirectTCPIPChannel openChannel(Socket socket)
throws TransportException, ConnectionException {
final DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, socket, parameters);
chan.open();
return chan;
private void startChannel(Socket socket) throws IOException {
DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, socket, parameters);
try {
chan.open();
chan.start();
} catch (IOException e) {
IOUtils.closeQuietly(chan, asCloseable(socket));
throw e;
}
}
/**
@@ -130,7 +136,7 @@ public class LocalPortForwarder {
while (!Thread.currentThread().isInterrupted()) {
final Socket socket = serverSocket.accept();
log.debug("Got connection from {}", socket.getRemoteSocketAddress());
openChannel(socket).start();
startChannel(socket);
}
log.debug("Interrupted!");
}

View File

@@ -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,27 +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)
.putUInt32(len - off)
.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();
}
@@ -139,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());
}
@@ -156,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];
@@ -200,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;
}
@@ -237,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
@@ -267,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();
}
}
}
}

View File

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

View File

@@ -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");

View File

@@ -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.
*

View File

@@ -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;
@@ -158,7 +159,10 @@ public final class TransportImpl
// Read server's ID
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
while ((serverID = readIdentification(buf)).isEmpty()) {
buf.putByte((byte) connInfo.in.read());
int b = connInfo.in.read();
if (b == -1)
throw new TransportException("Server closed connection during identification exchange");
buf.putByte((byte) b);
}
log.info("Server identity string: {}", serverID);
@@ -232,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 {

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code aes128-cbc} cipher */
public class AES128CBC
extends BaseCipher {
extends BlockCipher {
/** Named factory for AES128CBC Cipher */
public static class Factory

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code aes128-ctr} cipher */
public class AES128CTR
extends BaseCipher {
extends BlockCipher {
/** Named factory for AES128CBC Cipher */
public static class Factory

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code aes192-cbc} cipher */
public class AES192CBC
extends BaseCipher {
extends BlockCipher {
/** Named factory for AES192CBC Cipher */
public static class Factory

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code aes192-ctr} cipher */
public class AES192CTR
extends BaseCipher {
extends BlockCipher {
/** Named factory for AES192CTR Cipher */
public static class Factory

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code aes256-ctr} cipher */
public class AES256CBC
extends BaseCipher {
extends BlockCipher {
/** Named factory for AES256CBC Cipher */
public static class Factory

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code aes256-ctr} cipher */
public class AES256CTR
extends BaseCipher {
extends BlockCipher {
/** Named factory for AES256CBC Cipher */
public static class Factory

View File

@@ -22,9 +22,11 @@ import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
/** Base class for all Cipher implementations delegating to the JCE provider. */
public class BaseCipher
public abstract class BaseCipher
implements Cipher {
private static byte[] resize(byte[] data, int size) {
@@ -66,14 +68,22 @@ public class BaseCipher
iv = BaseCipher.resize(iv, ivsize);
try {
cipher = SecurityUtils.getCipher(transformation);
cipher.init((mode == Mode.Encrypt ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE),
new SecretKeySpec(key, algorithm), new IvParameterSpec(iv));
initCipher(cipher, mode, key, iv);
} catch (GeneralSecurityException e) {
cipher = null;
throw new SSHRuntimeException(e);
}
}
protected abstract void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException;
protected SecretKeySpec getKeySpec(byte[] key) {
return new SecretKeySpec(key, algorithm);
}
protected int getMode(Mode mode) {
return mode == Mode.Encrypt ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE;
}
@Override
public void update(byte[] input, int inputOffset, int inputLen) {
try {

View File

@@ -0,0 +1,17 @@
package net.schmizz.sshj.transport.cipher;
import javax.crypto.spec.IvParameterSpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
public class BlockCipher extends BaseCipher {
public BlockCipher(int ivsize, int bsize, String algorithm, String transformation) {
super(ivsize, bsize, algorithm, transformation);
}
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
cipher.init(getMode(mode),
getKeySpec(key), new IvParameterSpec(iv));
}
}

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code blowfish-ctr} cipher */
public class BlowfishCBC
extends BaseCipher {
extends BlockCipher {
/** Named factory for BlowfishCBC Cipher */
public static class Factory

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.transport.cipher;
/** {@code 3des-cbc} cipher */
public class TripleDESCBC
extends BaseCipher {
extends BlockCipher {
/** Named factory for TripleDESCBC Cipher */
public static class Factory

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View 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());
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,44 @@
package net.schmizz.sshj.transport.kex;
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 static com.hierynomus.sshj.secg.SecgUtils.getDecoded;
import static com.hierynomus.sshj.secg.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()));
}
}

View 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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -92,7 +92,7 @@ public class OpenSSHKeyFile
try {
final String keydata = br.readLine();
if (keydata != null) {
String[] parts = keydata.split(" ");
String[] parts = keydata.trim().split(" ");
assert parts.length >= 2;
type = KeyType.fromString(parts[0]);
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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 {
static 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,17 +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() {
@@ -119,11 +94,13 @@ class SCPEngine {
exitStatus = scp.getExitStatus();
if (scp.getExitStatus() != 0)
log.warn("SCP exit status: {}", scp.getExitStatus());
} else
} else {
exitStatus = -1;
}
if (scp.getExitSignal() != null)
if (scp.getExitSignal() != null) {
log.warn("SCP exit signal: {}", scp.getExitSignal());
}
}
scp = null;
@@ -133,36 +110,36 @@ class SCPEngine {
throws IOException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
int x;
while ((x = scp.getInputStream().read()) != LF)
while ((x = scp.getInputStream().read()) != LF) {
if (x == -1) {
if (baos.size() == 0)
if (baos.size() == 0) {
return "";
else
} else {
throw new IOException("EOF while reading message");
} else
}
} else {
baos.write(x);
}
}
final String msg = baos.toString(IOUtils.UTF8.displayName());
log.debug("Read message: `{}`", msg);
return msg;
}
void sendMessage(String msg)
throws IOException {
void sendMessage(String msg) throws IOException {
log.debug("Sending message: {}", msg);
scp.getOutputStream().write((msg + LF).getBytes(IOUtils.UTF8));
scp.getOutputStream().flush();
check("Message ACK received");
}
void signal(String what)
throws IOException {
void signal(String what) throws IOException {
log.debug("Signalling: {}", what);
scp.getOutputStream().write(0);
scp.getOutputStream().flush();
}
long transferToRemote(StreamCopier.Listener listener, InputStream src, long length)
throws IOException {
long transferToRemote(StreamCopier.Listener listener, InputStream src, long length) throws IOException {
return new StreamCopier(src, scp.getOutputStream())
.bufSize(scp.getRemoteMaxPacketSize()).length(length)
.keepFlushing(false)
@@ -170,8 +147,7 @@ class SCPEngine {
.copy();
}
long transferFromRemote(StreamCopier.Listener listener, OutputStream dest, long length)
throws IOException {
long transferFromRemote(StreamCopier.Listener listener, OutputStream dest, long length) throws IOException {
return new StreamCopier(scp.getInputStream(), dest)
.bufSize(scp.getLocalMaxPacketSize()).length(length)
.keepFlushing(false)
@@ -182,5 +158,4 @@ class SCPEngine {
TransferListener getTransferListener() {
return listener;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View 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();
}
}

View File

@@ -0,0 +1,23 @@
package com.hierynomus.sshj;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
public class IntegrationTest {
@Test
public void shouldConnect() throws IOException {
SSHClient sshClient = new SSHClient(new DefaultConfig());
sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(new File("/Users/ajvanerp/.ssh/known_hosts")));
sshClient.connect("172.16.37.129");
sshClient.authPassword("jeroen", "jeroen");
assertThat("Is connected", sshClient.isAuthenticated());
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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");
}
}
}
}

View 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));
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,54 @@
package com.hierynomus.sshj.test;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import org.apache.sshd.server.SshServer;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
public abstract class BaseAlgorithmTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Rule
public SshFixture fixture = new SshFixture(false);
@After
public void stopServer() {
fixture.stopServer();
}
@Test
public void shouldVerifyAlgorithm() throws IOException {
attempt(100);
}
private void attempt(int times) throws IOException {
for (int i = 0; i < times; i++) {
logger.info("--> Attempt {}", i);
verify();
}
}
private void verify() throws IOException {
configureServer(fixture.getServer());
fixture.start();
Config config = getClientConfig(new DefaultConfig());
SSHClient sshClient = fixture.connectClient(fixture.setupClient(config));
assertThat("should be connected", sshClient.isConnected());
sshClient.disconnect();
// fixture.stopServer();
fixture.stopClient();
}
protected abstract Config getClientConfig(DefaultConfig defaultConfig);
protected abstract void configureServer(SshServer server);
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,4 @@
package com.hierynomus.sshj.test;
public interface SlowTests {
}

View 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;
}
}

View 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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1,61 @@
package com.hierynomus.sshj.transport.kex;
import com.hierynomus.sshj.test.BaseAlgorithmTest;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.transport.kex.DHGexSHA1;
import net.schmizz.sshj.transport.kex.DHGexSHA256;
import net.schmizz.sshj.transport.kex.ECDHNistP;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.kex.DHGEXServer;
import org.apache.sshd.server.kex.DHGServer;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static org.hamcrest.MatcherAssert.assertThat;
@RunWith(Parameterized.class)
public class KeyExchangeTest extends BaseAlgorithmTest {
@Parameterized.Parameters
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][]{
{DHGEXServer.newFactory(BuiltinDHFactories.dhgex), new DHGexSHA1.Factory()},
{DHGEXServer.newFactory(BuiltinDHFactories.dhgex256), new DHGexSHA256.Factory()},
{DHGServer.newFactory(BuiltinDHFactories.ecdhp256), new ECDHNistP.Factory256()},
{DHGServer.newFactory(BuiltinDHFactories.ecdhp384), new ECDHNistP.Factory384()},
{DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521()}
// Not supported yet by MINA {null, new Curve25519SHA256.Factory()}
});
}
private Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
private NamedFactory<KeyExchange> serverFactory;
public KeyExchangeTest(NamedFactory<KeyExchange> serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
this.clientFactory = clientFactory;
this.serverFactory = serverFactory;
}
@Override
protected Config getClientConfig(DefaultConfig config) {
config.setKeyExchangeFactories(Collections.singletonList(clientFactory));
return config;
}
@Override
protected void configureServer(SshServer server) {
server.setKeyExchangeFactories(Collections.singletonList(serverFactory));
}
}

View 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());
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}

View File

@@ -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);
}
}

View File

@@ -30,9 +30,12 @@ import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Scanner;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -127,6 +130,26 @@ public class OpenSSHKeyFileTest {
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
}
@Test
public void shouldHaveCorrectFingerprintForECDSA() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n";
PublicKey aPublic = keyFile.getPublic();
String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic);
assertThat(expected, containsString(sshjFingerprintSshjKey));
}
@Test
public void shouldHaveCorrectFingerprintForED25519() throws IOException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ed25519"));
String expected = "256 MD5:d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32 root@sshj (ED25519)\n";
PublicKey aPublic = keyFile.getPublic();
String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic);
assertThat(expected, containsString(sshjFingerprintSshjKey));
}
@Before
public void setup()
throws UnsupportedEncodingException, GeneralSecurityException {

View File

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

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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);
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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"));
}
}

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

View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCm2IJ9gWDkPTlQ37NNUB0za5mCsQ8bi++8fyEqw7wl8ZNBh3qtTcnL+m+NZfQjUC0BXic7PcMLVm4A3ID2IAZQM+axfq9aL4huWerm4ua6tvdt4gQKoL1+8JFmdFvFw5pWW/NZHtkIprbVf7KtYrU27WmMhXruN071UzqLsw08cw==

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

Some files were not shown because too many files have changed in this diff Show More