mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4d50c6a24 | ||
|
|
989fb8cde6 | ||
|
|
d10a33ec59 | ||
|
|
327a4c4c5b | ||
|
|
d5c045defd | ||
|
|
02b70ef427 | ||
|
|
fdf08ef3c9 | ||
|
|
633b42fec8 | ||
|
|
3c594d9a1c | ||
|
|
c2b9c0266d | ||
|
|
0e784dd171 | ||
|
|
f322a4b060 | ||
|
|
0cd19284ee | ||
|
|
a5017d55c8 | ||
|
|
2f7b181306 | ||
|
|
20223d3614 | ||
|
|
cac340dd43 | ||
|
|
00cd335f47 | ||
|
|
e14fb2f695 | ||
|
|
b0dee02bf9 | ||
|
|
17c09eb471 | ||
|
|
0301d4537f | ||
|
|
f71d34e106 | ||
|
|
254f739ac1 | ||
|
|
aa201fa08c | ||
|
|
8721269d0f | ||
|
|
971ccf6273 | ||
|
|
813469646e | ||
|
|
17c368f9c2 | ||
|
|
4de9f8ab9f | ||
|
|
deff097170 | ||
|
|
7556a7f6f6 | ||
|
|
c5792fe4a8 | ||
|
|
a96fbfcf2f | ||
|
|
15e6924fc4 | ||
|
|
9e8bef24c5 |
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
* @hierynomus
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@
|
||||
out/
|
||||
target/
|
||||
classes/
|
||||
bin/
|
||||
build/
|
||||
docs/
|
||||
.gradle/
|
||||
|
||||
32
README.adoc
32
README.adoc
@@ -1,7 +1,7 @@
|
||||
= sshj - SSHv2 library for Java
|
||||
Jeroen van Erp
|
||||
:sshj_groupid: com.hierynomus
|
||||
:sshj_version: 0.26.0
|
||||
:sshj_version: 0.27.0
|
||||
:source-highlighter: pygments
|
||||
|
||||
image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"]
|
||||
@@ -10,7 +10,6 @@ image:https://api.codacy.com/project/badge/Grade/14a0a316bb9149739b5ea26dbfa8da8
|
||||
image:https://codecov.io/gh/hierynomus/sshj/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/hierynomus/sshj"]
|
||||
image:http://www.javadoc.io/badge/com.hierynomus/sshj.svg?color=blue["JavaDocs", link="http://www.javadoc.io/doc/com.hierynomus/sshj"]
|
||||
image:https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj/badge.svg["Maven Central",link="https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj"]
|
||||
image:https://javadoc-emblem.rhcloud.com/doc/com.hierynomus/sshj/badge.svg["Javadoc",link="http://www.javadoc.io/doc/com.hierynomus/sshj"]
|
||||
|
||||
To get started, have a look at one of the examples. Hopefully you will find the API pleasant to work with :)
|
||||
|
||||
@@ -73,6 +72,7 @@ key exchange::
|
||||
`diffie-hellman-group14-sha256`, `diffie-hellman-group15-sha512`, `diffie-hellman-group16-sha512`, `diffie-hellman-group17-sha512`, `diffie-hellman-group18-sha512`
|
||||
`diffie-hellman-group-exchange-sha1`, `diffie-hellman-group-exchange-sha256`,
|
||||
`ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `curve25519-sha256@libssh.org`
|
||||
|
||||
SSHJ also supports the following extended (non official) key exchange algoriths:
|
||||
`diffie-hellman-group14-sha256@ssh.com`, `diffie-hellman-group15-sha256`, `diffie-hellman-group15-sha256@ssh.com`, `diffie-hellman-group15-sha384@ssh.com`,
|
||||
`diffie-hellman-group16-sha256`, `diffie-hellman-group16-sha384@ssh.com`, `diffie-hellman-group16-sha512@ssh.com`, `diffie-hellman-group18-sha512@ssh.com`
|
||||
@@ -81,7 +81,8 @@ signatures::
|
||||
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519`
|
||||
|
||||
mac::
|
||||
`hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`, `hmac-ripemd160`
|
||||
`hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`, `hmac-ripemd160`, `hmac-ripemd160@openssh.com`
|
||||
`hmac-md5-etm@openssh.com`, `hmac-md5-96-etm@openssh.com`, `hmac-sha1-etm@openssh.com`, `hmac-sha1-96-etm@openssh.com`, `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-512-etm@openssh.com`, `hmac-ripemd160-etm@openssh.com`
|
||||
|
||||
compression::
|
||||
`zlib` and `zlib@openssh.com` (delayed zlib)
|
||||
@@ -100,16 +101,31 @@ Java 6+. http://www.slf4j.org/download.html[slf4j] is required. http://www.bounc
|
||||
== Reporting bugs
|
||||
Issue tracker: https://github.com/hierynomus/sshj/issues
|
||||
|
||||
== Discussion
|
||||
Google Group: http://groups.google.com/group/sshj-users
|
||||
|
||||
== Contributing
|
||||
Fork away!
|
||||
|
||||
== Release history
|
||||
SSHJ 0.24.0 (2018-??-??)::
|
||||
SSHJ 0.27.0 (2019-01-24)::
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/415[#415]: Fixed wrongly prefixed '/' to path in SFTPClient.mkdirs
|
||||
* Added support for ETM (Encrypt-then-Mac) MAC algorithms.
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/454[#454]: Added missing capacity check for Buffer.putUint64
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/466[#466]: Added lock timeout for remote action to prevent hanging
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/470[#470]: Made EdDSA the default (first) signature factory
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/467[#467]: Added AES256-CBC as cipher mode in openssh-key-v1 support
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/464[#464]: Enabled curve25519-sha256@openssh.org in DefaultConfig
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/472[#472]: Handle server initiated global requests
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/485[#485]: Added support for all keytypes to openssh-key-v1 keyfiles.
|
||||
SSHJ 0.26.0 (2018-07-24)::
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/413[#413]: Use UTF-8 for PrivateKeyFileResource
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/427[#427]: Support encrypted ed25519 openssh-key-v1 files
|
||||
* Upgraded BouncyCastle to 1.60
|
||||
* Added support for hmac-ripemd160@openssh.com MAC
|
||||
SSHJ 0.24.0 (2018-04-04)::
|
||||
* Added support for hmac-ripemd160
|
||||
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/382[#382]: Fixed escaping in WildcardHostmatcher
|
||||
* Added integration testsuite using Docker against OpenSSH
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/187[#187]: Fixed length bug in Buffer.putString
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/405[#405]: Continue host verification if first hostkey does not match.
|
||||
SSHJ 0.23.0 (2017-10-13)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/372[#372]: Upgrade to 'net.i2p.crypto:eddsa:0.2.0'
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/355[#355] and https://github.com/hierynomus/sshj/issues/354[#354]: Correctly decode signature bytes
|
||||
|
||||
22
build.gradle
22
build.gradle
@@ -37,9 +37,6 @@ defaultTasks "build"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://dl.bintray.com/mockito/maven/"
|
||||
}
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.6
|
||||
@@ -48,6 +45,7 @@ targetCompatibility = 1.6
|
||||
configurations.compile.transitive = false
|
||||
|
||||
def bouncycastleVersion = "1.60"
|
||||
def sshdVersion = "2.1.0"
|
||||
|
||||
dependencies {
|
||||
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||
@@ -57,15 +55,17 @@ dependencies {
|
||||
compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
|
||||
compile "com.jcraft:jzlib:1.1.3"
|
||||
|
||||
compile "net.i2p.crypto:eddsa:0.2.0"
|
||||
compile "net.i2p.crypto:eddsa:0.3.0"
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
testCompile "org.mockito:mockito-core:2.9.2"
|
||||
testCompile "org.apache.sshd:sshd-core:1.2.0"
|
||||
testRuntime "ch.qos.logback:logback-classic:1.1.2"
|
||||
testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17'
|
||||
testCompile 'org.apache.httpcomponents:httpclient:4.5.2'
|
||||
testCompile "junit:junit:4.12"
|
||||
testCompile 'org.spockframework:spock-core:1.3-groovy-2.4'
|
||||
testCompile "org.mockito:mockito-core:2.28.2"
|
||||
testCompile "org.apache.sshd:sshd-core:$sshdVersion"
|
||||
testCompile "org.apache.sshd:sshd-sftp:$sshdVersion"
|
||||
testCompile "org.apache.sshd:sshd-scp:$sshdVersion"
|
||||
testRuntime "ch.qos.logback:logback-classic:1.2.3"
|
||||
testCompile 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
|
||||
testCompile 'org.apache.httpcomponents:httpclient:4.5.9'
|
||||
|
||||
}
|
||||
|
||||
|
||||
50
examples/src/main/java/net/schmizz/sshj/examples/Jump.java
Normal file
50
examples/src/main/java/net/schmizz/sshj/examples/Jump.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package net.schmizz.sshj.examples;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This example demonstrates connecting via an intermediate "jump" server using a direct TCP/IP channel.
|
||||
*/
|
||||
public class Jump {
|
||||
public static void main(String... args)
|
||||
throws IOException {
|
||||
SSHClient firstHop = new SSHClient();
|
||||
|
||||
firstHop.loadKnownHosts();
|
||||
|
||||
firstHop.connect("localhost");
|
||||
try {
|
||||
|
||||
firstHop.authPublickey(System.getProperty("user.name"));
|
||||
|
||||
DirectConnection tunnel = firstHop.newDirectConnection("localhost", 22);
|
||||
|
||||
SSHClient ssh = new SSHClient();
|
||||
try {
|
||||
ssh.loadKnownHosts();
|
||||
ssh.connectVia(tunnel);
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
|
||||
final Session session = ssh.startSession();
|
||||
try {
|
||||
final Session.Command cmd = session.exec("ping -c 1 google.com");
|
||||
System.out.println(IOUtils.readFully(cmd.getInputStream()).toString());
|
||||
cmd.join(5, TimeUnit.SECONDS);
|
||||
System.out.println("\n** exit status: " + cmd.getExitStatus());
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
}
|
||||
} finally {
|
||||
firstHop.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Fri Mar 18 11:26:35 CET 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=8626cbf206b4e201ade7b87779090690447054bc93f052954c78480fa6ed186e
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
|
||||
|
||||
110
gradlew
vendored
110
gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
14
gradlew.bat
vendored
14
gradlew.bat
vendored
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
FROM sickp/alpine-sshd:7.5
|
||||
FROM sickp/alpine-sshd:7.5-r2
|
||||
|
||||
ADD id_rsa.pub /home/sshj/.ssh/authorized_keys
|
||||
ADD authorized_keys /home/sshj/.ssh/authorized_keys
|
||||
|
||||
ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key
|
||||
ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub
|
||||
ADD test-container/ssh_host_ed25519_key /etc/ssh/ssh_host_ed25519_key
|
||||
ADD test-container/ssh_host_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub
|
||||
ADD test-container/sshd_config /etc/ssh/sshd_config
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
RUN \
|
||||
echo "root:smile" | chpasswd && \
|
||||
adduser -D -s /bin/ash sshj && \
|
||||
@@ -13,5 +16,8 @@ RUN \
|
||||
chmod 600 /home/sshj/.ssh/authorized_keys && \
|
||||
chmod 600 /etc/ssh/ssh_host_ecdsa_key && \
|
||||
chmod 644 /etc/ssh/ssh_host_ecdsa_key.pub && \
|
||||
chmod 600 /etc/ssh/ssh_host_ed25519_key && \
|
||||
chmod 644 /etc/ssh/ssh_host_ed25519_key.pub && \
|
||||
chown -R sshj:sshj /home/sshj
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "/entrypoint.sh"]
|
||||
7
src/itest/docker-image/authorized_keys
Normal file
7
src/itest/docker-image/authorized_keys
Normal file
@@ -0,0 +1,7 @@
|
||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOEQcvowiV3igdRO7rKPrZrao1hCQrnC4tgsxqSJdQCbABI+vHrdbJRfWZNuSk48aAtARJzJVmkn/r63EPJgkh8= root@itgcpkerberosstack-cbgateway-0-20151117031915
|
||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8gohA/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6M= ajvanerp@Heimdall.local
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq2LSHRavxAT7ja2f+5soOUJl/zKSI ajvanerp@Heimdall.xebialabs.com
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local
|
||||
@@ -1 +0,0 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBFG9PKAq8FtH0me+LHUE6YaVANCMqy/Znkffzief1W/gAAAKCyyoBkssqA
|
||||
ZAAAAAtzc2gtZWQyNTUxOQAAACBFG9PKAq8FtH0me+LHUE6YaVANCMqy/Znkffzief1W/g
|
||||
AAAED+Yfza2xk5LqP9pN6TpvhWYP0L60zOQJpHhbEuiS3LLkUb08oCrwW0fSZ74sdQTphp
|
||||
UA0IyrL9meR9/OJ5/Vb+AAAAF2FqdmFuZXJwQEhlaW1kYWxsLmxvY2FsAQIDBAUG
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEUb08oCrwW0fSZ74sdQTphpUA0IyrL9meR9/OJ5/Vb+ ajvanerp@Heimdall.local
|
||||
@@ -128,5 +128,5 @@ Subsystem sftp /usr/lib/ssh/sftp-server
|
||||
# PermitTTY no
|
||||
# ForceCommand cvs server
|
||||
|
||||
|
||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1
|
||||
macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com
|
||||
|
||||
@@ -24,7 +24,7 @@ import spock.lang.Specification
|
||||
class IntegrationBaseSpec extends Specification {
|
||||
protected static final int DOCKER_PORT = 2222
|
||||
protected static final String USERNAME = "sshj"
|
||||
protected static final String KEYFILE = "src/test/resources/id_rsa"
|
||||
protected static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa"
|
||||
protected final static String SERVER_IP = System.getProperty("serverIP", "127.0.0.1")
|
||||
|
||||
protected static SSHClient getConnectedClient(Config config) {
|
||||
|
||||
@@ -15,23 +15,34 @@
|
||||
*/
|
||||
package com.hierynomus.sshj
|
||||
|
||||
import com.hierynomus.sshj.signature.SignatureEdDSA
|
||||
import net.schmizz.sshj.DefaultConfig
|
||||
import net.schmizz.sshj.SSHClient
|
||||
import net.schmizz.sshj.signature.SignatureECDSA
|
||||
import net.schmizz.sshj.transport.TransportException
|
||||
import net.schmizz.sshj.userauth.UserAuthException
|
||||
import spock.lang.Unroll
|
||||
|
||||
class IntegrationSpec extends IntegrationBaseSpec {
|
||||
|
||||
def "should accept correct key"() {
|
||||
@Unroll
|
||||
def "should accept correct key for #signatureName"() {
|
||||
given:
|
||||
SSHClient sshClient = new SSHClient(new DefaultConfig())
|
||||
sshClient.addHostKeyVerifier("d3:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3") // test-containers/ssh_host_ecdsa_key's fingerprint
|
||||
def config = new DefaultConfig()
|
||||
config.setSignatureFactories(signatureFactory)
|
||||
SSHClient sshClient = new SSHClient(config)
|
||||
sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint
|
||||
|
||||
when:
|
||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
||||
|
||||
then:
|
||||
sshClient.isConnected()
|
||||
|
||||
where:
|
||||
signatureFactory << [new SignatureECDSA.Factory256(), new SignatureEdDSA.Factory()]
|
||||
fingerprint << ["d3:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3", "dc:68:38:ce:fc:6f:2c:d6:6d:6b:34:eb:5c:f0:41:6a"]
|
||||
signatureName = signatureFactory.getName()
|
||||
}
|
||||
|
||||
def "should decline wrong key"() throws IOException {
|
||||
@@ -46,15 +57,27 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
||||
thrown(TransportException.class)
|
||||
}
|
||||
|
||||
def "should authenticate"() {
|
||||
@Unroll
|
||||
def "should authenticate with key #key"() {
|
||||
given:
|
||||
SSHClient client = getConnectedClient()
|
||||
|
||||
when:
|
||||
client.authPublickey(USERNAME, KEYFILE)
|
||||
def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key")
|
||||
client.authPublickey(USERNAME, keyProvider)
|
||||
|
||||
then:
|
||||
client.isAuthenticated()
|
||||
|
||||
where:
|
||||
key | passphrase
|
||||
// "id_ecdsa_nistp256" | null // TODO: Need to improve PKCS8 key support.
|
||||
"id_ecdsa_opensshv1" | null
|
||||
"id_ed25519_opensshv1" | null
|
||||
"id_ed25519_opensshv1_aes256cbc.pem" | "foobar"
|
||||
"id_ed25519_opensshv1_protected" | "sshjtest"
|
||||
"id_rsa" | null
|
||||
"id_rsa_opensshv1" | null
|
||||
}
|
||||
|
||||
def "should not authenticate with wrong key"() {
|
||||
@@ -62,7 +85,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
|
||||
SSHClient client = getConnectedClient()
|
||||
|
||||
when:
|
||||
client.authPublickey("sshj", "src/test/resources/id_dsa")
|
||||
client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key")
|
||||
|
||||
then:
|
||||
thrown(UserAuthException.class)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hierynomus.sshj.transport.kex
|
||||
|
||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
||||
import com.hierynomus.sshj.transport.mac.Macs
|
||||
import net.schmizz.sshj.DefaultConfig
|
||||
import net.schmizz.sshj.transport.kex.Curve25519DH
|
||||
import net.schmizz.sshj.transport.kex.Curve25519SHA256
|
||||
import net.schmizz.sshj.transport.kex.DH
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA1
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA256
|
||||
import net.schmizz.sshj.transport.kex.ECDH
|
||||
import net.schmizz.sshj.transport.kex.ECDHNistP
|
||||
import spock.lang.Unroll
|
||||
|
||||
class KexSpec extends IntegrationBaseSpec {
|
||||
|
||||
@Unroll
|
||||
def "should correctly connect with #kex Key Exchange"() {
|
||||
given:
|
||||
def cfg = new DefaultConfig()
|
||||
cfg.setKeyExchangeFactories(kexFactory)
|
||||
def client = getConnectedClient(cfg)
|
||||
|
||||
when:
|
||||
client.authPublickey(USERNAME, KEYFILE)
|
||||
|
||||
then:
|
||||
client.authenticated
|
||||
|
||||
where:
|
||||
kexFactory << [DHGroups.Group1SHA1(),
|
||||
DHGroups.Group14SHA1(),
|
||||
DHGroups.Group14SHA256(),
|
||||
DHGroups.Group16SHA512(),
|
||||
DHGroups.Group18SHA512(),
|
||||
new DHGexSHA1.Factory(),
|
||||
new DHGexSHA256.Factory(),
|
||||
new Curve25519SHA256.Factory(),
|
||||
new Curve25519SHA256.FactoryLibSsh(),
|
||||
new ECDHNistP.Factory256(),
|
||||
new ECDHNistP.Factory384(),
|
||||
new ECDHNistP.Factory521()]
|
||||
kex = kexFactory.name
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import com.hierynomus.sshj.IntegrationBaseSpec
|
||||
import net.schmizz.sshj.DefaultConfig
|
||||
import net.schmizz.sshj.transport.mac.HMACRIPEMD160
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA2256
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Unroll
|
||||
|
||||
class MacSpec extends IntegrationBaseSpec {
|
||||
@@ -36,8 +37,32 @@ class MacSpec extends IntegrationBaseSpec {
|
||||
then:
|
||||
client.authenticated
|
||||
|
||||
cleanup:
|
||||
client.disconnect()
|
||||
|
||||
where:
|
||||
macFactory << [Macs.HMACRIPEMD160(), Macs.HMACRIPEMD160OpenSsh(), Macs.HMACSHA2256(), Macs.HMACSHA2512()]
|
||||
mac = macFactory.name
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "should correctly connect with Encrypt-Then-Mac #mac MAC"() {
|
||||
given:
|
||||
def cfg = new DefaultConfig()
|
||||
cfg.setMACFactories(macFactory)
|
||||
def client = getConnectedClient(cfg)
|
||||
|
||||
when:
|
||||
client.authPublickey(USERNAME, KEYFILE)
|
||||
|
||||
then:
|
||||
client.authenticated
|
||||
|
||||
cleanup:
|
||||
client.disconnect()
|
||||
|
||||
where:
|
||||
macFactory << [Macs.HMACRIPEMD160Etm(), Macs.HMACSHA2256Etm(), Macs.HMACSHA2512Etm()]
|
||||
mac = macFactory.name
|
||||
}
|
||||
}
|
||||
|
||||
5
src/itest/resources/keyfiles/id_ecdsa_nistp256
Normal file
5
src/itest/resources/keyfiles/id_ecdsa_nistp256
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIJUMlsSlXqCZmCjlN4kV7hzP+p9pu0fwJ8r4m1qle58SoAoGCCqGSM49
|
||||
AwEHoUQDQgAE4RBy+jCJXeKB1E7uso+tmtqjWEJCucLi2CzGpIl1AJsAEj68et1s
|
||||
lF9Zk25KTjxoC0BEnMlWaSf+vrcQ8mCSHw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
9
src/itest/resources/keyfiles/id_ecdsa_opensshv1
Normal file
9
src/itest/resources/keyfiles/id_ecdsa_opensshv1
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR0ImZtMAW5iPIKIQPzfYq9TvnoIpC+
|
||||
kvRY2UvBh28eK0xyNVfr218cdjvWxVrXqdTxW+IqMLWZMX+oL0YxpC+jAAAAsD+6Oow/uj
|
||||
qMAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8goh
|
||||
A/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6
|
||||
MAAAAgXNC11pInVAOd3xNphiHMoISeitf6h1IKbDM+niLrL5kAAAAXYWp2YW5lcnBASGVp
|
||||
bWRhbGwubG9jYWwB
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
7
src/itest/resources/keyfiles/id_ed25519_opensshv1
Normal file
7
src/itest/resources/keyfiles/id_ed25519_opensshv1
Normal file
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQAAAJDimgR84poE
|
||||
fAAAAAtzc2gtZWQyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQ
|
||||
AAAECmsckQycWnfGQK6XtQpaMGODbAkMQOdJNK6XJSipB7dDAdJiRkkBM8yC8seTEoAn2P
|
||||
fwbLKrkcahZ0xxPoWICJAAAACXJvb3RAc3NoagECAwQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABBLQVXV9f
|
||||
Wpw8AL9RTpAr//AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdk
|
||||
jTTBDF1GNz+228nuWprPV+NbQauAAAAAoGHEO7x3fSRBohvrIR52U4XD3uqRnhrPYm01k1
|
||||
f4HHNNv46m92Zw6JKIB9Trrvp0sdMI8MVb79bN45rbn6mvpABtWl6T5TOTyMnKzDfAOx9c
|
||||
FTaasWFmgtgkXOsu5pLrYBAQgCHWbzjjz6KoV1DmD4SAn9Ojf9Oh+YdAEKZcsvklgpu+Kj
|
||||
nzN/DR0jt7Nzep2kNCLAS24QEkvQeATVSDiL8=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB/aWL0WG
|
||||
iYPOTxGlFwvaCNAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq
|
||||
2LSHRavxAT7ja2f+5soOUJl/zKSIAAAAsKplAiFbOhzcOJYFYBYm8sqYbvhPF8jKdQFkbo
|
||||
LAOeq+vQ0YBV9XUWQQM2tmL+RPjykPJZ2thcHLpVp3PfUEgo4bImCt939b3Ji3cEwD3QuK
|
||||
MIhjhx1KvSJNF/uhjwPJnttwHG+ld8F5Gv7LpTOUmOzXKGLIgYRuwonhs5ezdNv5ERs+Cq
|
||||
M9p/SW5ehL5KPJhGa5a+ZQXRojwEH7J4Q5xztH1gviTdIEpFWWQBH8rX6y
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
27
src/itest/resources/keyfiles/id_rsa
Normal file
27
src/itest/resources/keyfiles/id_rsa
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEoAIBAAKCAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQm
|
||||
TIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6
|
||||
RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmu
|
||||
hA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPClizt
|
||||
spKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmL
|
||||
GGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQIBIwKCAQBXvO4uJlbrLJQDPYAt
|
||||
1i1ybGcF+rrR/Q34a2dgCpZDEwFiDTlcv1hxx689OXTf5cMPXGDZXX5udd9p7Wxa
|
||||
NqnIrvVUtQWLdqcZuEeO+gitHr8IyMJf5Lm8C/u5vl1PYOYhO0qxwmrTP1u6fZPh
|
||||
zWX2X1p5626/sy+TCisCRDeLRyes+Dtfs3bDjUq+zF3D/DmeYY55LUx0XS27uXNS
|
||||
QuUDMSnymFyj4o+jPK0q/j5w4bB+0rbsij+EP7S//jOFrSEcZgBhhIj0rHA5fo6w
|
||||
NrgtgRKD3HKFBM3b4VM8TdMbHsmf+nT9DjiDqcs+IxXMGlb1XTjtQFIN2eyRtNLd
|
||||
eQ0bAoGBAMwgv3rGytRjVbR4TT77eF81svzitOJWRdfXuKB5gqM3jsPR08f1MEtZ
|
||||
44kaI5ByJ3mBDt/EwNgLRdmBddPrLu3so9VLdRmWKI+KNGxwkcxzJv1xXdicgw+w
|
||||
S5WgigJryuUbtdylXQTlRArLUKsXULk/MndhGiD+a4fZ3dUtktF9AoGBAMqxh6tr
|
||||
S0ao0rN4hc9I92gwPubD1+XQr9TJQEEqGv3g5O3BdfDrTvizfaeNArahrIqyO5SK
|
||||
7iDg0xcHqdxmVmmCJ8CkIWBPXLU6erQ1QNlBJmnzYn5lR0Ksx2h/myjeXztvJKEM
|
||||
q4xUjAEzWjmwxxU3Y6l3FokvgIU4kOVoE4JdAoGARfyZa+xizHnUPeAae/5yaclE
|
||||
rnmdGma43En2KGQsyj7vHpEVaSDdW6nKWuRj9wKRMPkMafpQvxnOzjsD06EXZ4RV
|
||||
bbN4mw7pVcj8B+wUuyAqoAmchMfya8dqXy+6SfkSXS4Sd4knNODkIPVAOqjoegcJ
|
||||
/QtZamXbuYyGkjuCy3sCgYBLSUEFJ9ohjymwX/cvvAQfYmCBmTLu9b2mzmhSt94j
|
||||
yI+Lgl8BtnxrANbmdjQ1NLxuB6+61IRVWtIP3kZnzj1aY4sbqq1PqHLkOkrVOFnq
|
||||
S2YKGJJMND8KIur67ZFm84J1KUgej61uow9uKQRBUEnx8AByJOsdAwPZteys+sVq
|
||||
7wKBgAc3BL6ttDVYlL8U8fgvTCGuIajItvOQQj1n8RKyRLblYXBKAtY+pbULzmF+
|
||||
HscRgJMcwEIosEbTzzBNEVQm6dS6R/Q534C00Fpsw1z/PFTI8AOdUzTROGjuN8Fg
|
||||
YZoqMQLhk/PB8V4l7yJmPrE971RmJBBDlLDt6hZwOYEI2yF4
|
||||
-----END RSA PRIVATE KEY-----
|
||||
49
src/itest/resources/keyfiles/id_rsa_opensshv1
Normal file
49
src/itest/resources/keyfiles/id_rsa_opensshv1
Normal file
@@ -0,0 +1,49 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
15
src/itest/resources/keyfiles/id_unknown_key
Normal file
15
src/itest/resources/keyfiles/id_unknown_key
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN DSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,9B6744BB12A8EA8F
|
||||
|
||||
pzZw5s3zDVHYdejZxdTpaRx00Yd1grbJe6mIJGvZRB0Jm0hKXoOX71PUI814mc5+
|
||||
a5pzbyO98aciL/Eat5m3P692WQ0yOPMuphRnklsM3s4qrCjp2aRRbWvbyV/QV9bp
|
||||
Xz2yYvNqU3WJC3UJIaFFMvRo/lC/Wsz9OvHSSl3LnsXXhiOCeaE32etoOYdlk9ro
|
||||
N9NqDdaw28t9//iiHhuQK4afK6TZkU6DatFljJHILCC416Xh9+DDK9E+CDKzmlcw
|
||||
jSwtzgFKEhgrT0XKoZR9LJZDolT1YpFy7M3cFRYIuYvJfuLcjxVEldJE900QlaJS
|
||||
ybb6RxV6SRVwQYXTbIClcXes+oNJMv59DivAfajxECQC5sAynW/FnY1sz0igmz6D
|
||||
scclJuJIbawqiuV/Lv6bvgzMa/ZXL4b9JeJPuQELa7tCpvj4fpNk1IiftYISlwoT
|
||||
iG5pL8yLhPL4/fxGnKJzUPCA9mbwiloW2cAZZxTd+Utqmhemcl9gF0JGNR2XeYwS
|
||||
3obEjbkqMF0WR3AcVZU9B5d9SKUaAzTp4vu5yZtNVEIaiVlnI3hMwWMs2Jgahswo
|
||||
QF9MCPsRYsxLs7/u4a4qoQ==
|
||||
-----END DSA PRIVATE KEY-----
|
||||
@@ -16,48 +16,73 @@
|
||||
package com.hierynomus.sshj.transport.mac;
|
||||
|
||||
import net.schmizz.sshj.transport.mac.BaseMAC;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
|
||||
@SuppressWarnings("PMD.MethodNamingConventions")
|
||||
public class Macs {
|
||||
public static Factory HMACMD5() {
|
||||
return new Factory("hmac-md5", "HmacMD5", 16, 16);
|
||||
return new Factory("hmac-md5", "HmacMD5", 16, 16, false);
|
||||
}
|
||||
public static Factory HMACMD596() {
|
||||
return new Factory("hmac-md5-96", "HmacMD5", 12, 16);
|
||||
return new Factory("hmac-md5-96", "HmacMD5", 12, 16, false);
|
||||
}
|
||||
public static Factory HMACMD5Etm() {
|
||||
return new Factory("hmac-md5-etm@openssh.com", "HmacMD5", 16, 16, true);
|
||||
}
|
||||
public static Factory HMACMD596Etm() {
|
||||
return new Factory("hmac-md5-96-etm@openssh.com", "HmacMD5", 12, 16, true);
|
||||
}
|
||||
public static Factory HMACRIPEMD160() {
|
||||
return new Factory("hmac-ripemd160", "HMACRIPEMD160", 20, 20);
|
||||
return new Factory("hmac-ripemd160", "HMACRIPEMD160", 20, 20, false);
|
||||
}
|
||||
public static Factory HMACRIPEMD16096() {
|
||||
return new Factory("hmac-ripemd160-96", "HMACRIPEMD160", 12, 20);
|
||||
return new Factory("hmac-ripemd160-96", "HMACRIPEMD160", 12, 20, false);
|
||||
}
|
||||
public static Factory HMACRIPEMD160Etm() {
|
||||
return new Factory("hmac-ripemd160-etm@openssh.com", "HMACRIPEMD160", 20, 20, true);
|
||||
}
|
||||
public static Factory HMACRIPEMD160OpenSsh() {
|
||||
return new Factory("hmac-ripemd160@openssh.com", "HMACRIPEMD160", 20, 20);
|
||||
return new Factory("hmac-ripemd160@openssh.com", "HMACRIPEMD160", 20, 20, false);
|
||||
}
|
||||
public static Factory HMACSHA1() {
|
||||
return new Factory("hmac-sha1", "HmacSHA1", 20, 20);
|
||||
return new Factory("hmac-sha1", "HmacSHA1", 20, 20, false);
|
||||
}
|
||||
public static Factory HMACSHA196() {
|
||||
return new Factory("hmac-sha1-96", "HmacSHA1", 12, 20);
|
||||
return new Factory("hmac-sha1-96", "HmacSHA1", 12, 20, false);
|
||||
}
|
||||
public static Factory HMACSHA1Etm() {
|
||||
return new Factory("hmac-sha1-etm@openssh.com", "HmacSHA1", 20, 20, true);
|
||||
}
|
||||
public static Factory HMACSHA196Etm() {
|
||||
return new Factory("hmac-sha1-96@openssh.com", "HmacSHA1", 12, 20, true);
|
||||
}
|
||||
public static Factory HMACSHA2256() {
|
||||
return new Factory("hmac-sha2-256", "HmacSHA256", 32, 32);
|
||||
return new Factory("hmac-sha2-256", "HmacSHA256", 32, 32, false);
|
||||
}
|
||||
public static Factory HMACSHA2256Etm() {
|
||||
return new Factory("hmac-sha2-256-etm@openssh.com", "HmacSHA256", 32, 32, true);
|
||||
}
|
||||
public static Factory HMACSHA2512() {
|
||||
return new Factory("hmac-sha2-512", "HmacSHA512", 64, 64);
|
||||
return new Factory("hmac-sha2-512", "HmacSHA512", 64, 64, false);
|
||||
}
|
||||
public static Factory HMACSHA2512Etm() {
|
||||
return new Factory("hmac-sha2-512-etm@openssh.com", "HmacSHA512", 64, 64, true);
|
||||
}
|
||||
|
||||
private static class Factory implements net.schmizz.sshj.common.Factory.Named<BaseMAC> {
|
||||
public static class Factory implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||
|
||||
private String name;
|
||||
private String algorithm;
|
||||
private int bSize;
|
||||
private int defBSize;
|
||||
private final boolean etm;
|
||||
|
||||
public Factory(String name, String algorithm, int bSize, int defBSize) {
|
||||
public Factory(String name, String algorithm, int bSize, int defBSize, boolean etm) {
|
||||
this.name = name;
|
||||
this.algorithm = algorithm;
|
||||
this.bSize = bSize;
|
||||
this.defBSize = defBSize;
|
||||
this.etm = etm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,7 +92,7 @@ public class Macs {
|
||||
|
||||
@Override
|
||||
public BaseMAC create() {
|
||||
return new BaseMAC(algorithm, bSize, defBSize);
|
||||
return new BaseMAC(algorithm, bSize, defBSize, etm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.hierynomus.sshj.transport.verification;
|
||||
import net.schmizz.sshj.common.Base64;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -26,6 +25,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.hierynomus.sshj.transport.mac.Macs;
|
||||
|
||||
public class KnownHostMatchers {
|
||||
|
||||
public static HostMatcher createMatcher(String hostEntry) throws SSHException {
|
||||
@@ -63,7 +64,7 @@ public class KnownHostMatchers {
|
||||
}
|
||||
|
||||
private static class HashedHostMatcher implements HostMatcher {
|
||||
private final MAC sha1 = new HMACSHA1();
|
||||
private final MAC sha1 = Macs.HMACSHA1().create();
|
||||
private final String hash;
|
||||
private final String salt;
|
||||
private byte[] saltyBytes;
|
||||
|
||||
@@ -25,18 +25,22 @@ import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECPrivateKeySpec;
|
||||
import java.security.spec.RSAPrivateKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -106,7 +110,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
||||
logger.debug("Reading unencrypted keypair");
|
||||
return readUnencrypted(privateKeyBuffer, publicKey);
|
||||
} else {
|
||||
logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + kdfOptions);
|
||||
logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + Arrays.toString(kdfOptions));
|
||||
PlainBuffer decrypted = decryptBuffer(privateKeyBuffer, cipherName, kdfName, kdfOptions);
|
||||
return readUnencrypted(decrypted, publicKey);
|
||||
// throw new IOException("Cannot read encrypted keypair with " + cipherName + " yet.");
|
||||
@@ -124,10 +128,12 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
||||
private void initializeCipher(String kdfName, byte[] kdfOptions, Cipher cipher) throws Buffer.BufferException {
|
||||
if (kdfName.equals(BCRYPT)) {
|
||||
PlainBuffer opts = new PlainBuffer(kdfOptions);
|
||||
CharBuffer charBuffer = CharBuffer.wrap(pwdf.reqPassword(null));
|
||||
ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
|
||||
byte[] passphrase = Arrays.copyOfRange(byteBuffer.array(),
|
||||
byteBuffer.position(), byteBuffer.limit());
|
||||
byte[] passphrase = new byte[0];
|
||||
if (pwdf != null) {
|
||||
CharBuffer charBuffer = CharBuffer.wrap(pwdf.reqPassword(null));
|
||||
ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
|
||||
passphrase = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
|
||||
}
|
||||
byte[] keyiv = new byte[48];
|
||||
new BCrypt().pbkdf(passphrase, opts.readBytes(), opts.readUInt32AsInt(), keyiv);
|
||||
byte[] key = Arrays.copyOfRange(keyiv, 0, 32);
|
||||
@@ -141,6 +147,8 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
||||
private Cipher createCipher(String cipherName) {
|
||||
if (cipherName.equals(BlockCiphers.AES256CTR().getName())) {
|
||||
return BlockCiphers.AES256CTR().create();
|
||||
} else if (cipherName.equals(BlockCiphers.AES256CBC().getName())) {
|
||||
return BlockCiphers.AES256CBC().create();
|
||||
}
|
||||
throw new IllegalStateException("Cipher '" + cipherName + "' not currently implemented for openssh-key-v1 format");
|
||||
}
|
||||
@@ -180,14 +188,41 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
||||
}
|
||||
// The private key section contains both the public key and the private key
|
||||
String keyType = keyBuffer.readString(); // string keytype
|
||||
logger.info("Read key type: {}", keyType);
|
||||
KeyType kt = KeyType.fromString(keyType);
|
||||
logger.info("Read key type: {}", keyType, kt);
|
||||
KeyPair kp;
|
||||
switch (kt) {
|
||||
case ED25519:
|
||||
keyBuffer.readBytes(); // string publickey (again...)
|
||||
keyBuffer.readUInt32(); // length of privatekey+publickey
|
||||
byte[] privKey = new byte[32];
|
||||
keyBuffer.readRawBytes(privKey); // string privatekey
|
||||
keyBuffer.readRawBytes(new byte[32]); // string publickey (again...)
|
||||
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
|
||||
break;
|
||||
case RSA:
|
||||
BigInteger n = keyBuffer.readMPInt(); // Modulus
|
||||
keyBuffer.readMPInt(); // Public Exponent
|
||||
BigInteger d = keyBuffer.readMPInt(); // Private Exponent
|
||||
keyBuffer.readMPInt(); // iqmp (q^-1 mod p)
|
||||
keyBuffer.readMPInt(); // p (Prime 1)
|
||||
keyBuffer.readMPInt(); // q (Prime 2)
|
||||
kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory("RSA").generatePrivate(new RSAPrivateKeySpec(n, d)));
|
||||
break;
|
||||
case ECDSA256:
|
||||
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
|
||||
break;
|
||||
case ECDSA384:
|
||||
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-384"));
|
||||
break;
|
||||
case ECDSA521:
|
||||
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-521"));
|
||||
break;
|
||||
|
||||
byte[] pubKey = keyBuffer.readBytes(); // string publickey (again...)
|
||||
keyBuffer.readUInt32();
|
||||
byte[] privKey = new byte[32];
|
||||
keyBuffer.readRawBytes(privKey); // string privatekey
|
||||
keyBuffer.readRawBytes(new byte[32]); // string publickey (again...)
|
||||
String comment = keyBuffer.readString(); // string comment
|
||||
default:
|
||||
throw new IOException("Cannot decode keytype " + keyType + " in openssh-key-v1 files (yet).");
|
||||
}
|
||||
keyBuffer.readString(); // string comment
|
||||
byte[] padding = new byte[keyBuffer.available()];
|
||||
keyBuffer.readRawBytes(padding); // char[] padding
|
||||
for (int i = 0; i < padding.length; i++) {
|
||||
@@ -195,6 +230,16 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
||||
throw new IOException("Padding of key format contained wrong byte at position: " + i);
|
||||
}
|
||||
}
|
||||
return new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
|
||||
return kp;
|
||||
}
|
||||
|
||||
private PrivateKey createECDSAPrivateKey(KeyType kt, PlainBuffer buffer, String name) throws GeneralSecurityException, Buffer.BufferException {
|
||||
kt.readPubKeyFromBuffer(buffer); // Public key
|
||||
BigInteger s = new BigInteger(1, buffer.readBytes());
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName(name);
|
||||
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
|
||||
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
|
||||
return SecurityUtils.getKeyFactory("ECDSA").generatePrivate(pks);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,23 +19,23 @@ import java.util.Collection;
|
||||
|
||||
public class ErrorDeliveryUtil {
|
||||
|
||||
public static void alertPromises(Throwable x, Promise... promises) {
|
||||
for (Promise p : promises)
|
||||
public static void alertPromises(Throwable x, Promise<?, ?>... promises) {
|
||||
for (Promise<?, ?> p : promises)
|
||||
p.deliverError(x);
|
||||
}
|
||||
|
||||
public static void alertPromises(Throwable x, Collection<? extends Promise> promises) {
|
||||
for (Promise p : promises)
|
||||
public static void alertPromises(Throwable x, Collection<? extends Promise<?, ?>> promises) {
|
||||
for (Promise<?, ?> p : promises)
|
||||
p.deliverError(x);
|
||||
}
|
||||
|
||||
public static void alertEvents(Throwable x, Event... events) {
|
||||
for (Event e : events)
|
||||
public static void alertEvents(Throwable x, Event<?>... events) {
|
||||
for (Event<?> e : events)
|
||||
e.deliverError(x);
|
||||
}
|
||||
|
||||
public static void alertEvents(Throwable x, Collection<? extends Event> events) {
|
||||
for (Event e : events)
|
||||
public static void alertEvents(Throwable x, Collection<? extends Event<?>> events) {
|
||||
for (Event<?> e : events)
|
||||
e.deliverError(x);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
import com.hierynomus.sshj.transport.cipher.StreamCiphers;
|
||||
import com.hierynomus.sshj.transport.kex.DHGroups;
|
||||
import com.hierynomus.sshj.transport.kex.ExtendedDHGroups;
|
||||
import com.hierynomus.sshj.transport.mac.Macs;
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
@@ -34,7 +35,6 @@ import net.schmizz.sshj.transport.kex.Curve25519SHA256;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
||||
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
||||
import net.schmizz.sshj.transport.mac.*;
|
||||
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
|
||||
import net.schmizz.sshj.transport.random.JCERandom;
|
||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||
@@ -44,7 +44,6 @@ import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -106,7 +105,9 @@ public class DefaultConfig
|
||||
|
||||
protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) {
|
||||
if (bouncyCastleRegistered) {
|
||||
setKeyExchangeFactories(new Curve25519SHA256.Factory(),
|
||||
setKeyExchangeFactories(
|
||||
new Curve25519SHA256.Factory(),
|
||||
new Curve25519SHA256.FactoryLibSsh(),
|
||||
new DHGexSHA256.Factory(),
|
||||
new ECDHNistP.Factory521(),
|
||||
new ECDHNistP.Factory384(),
|
||||
@@ -208,23 +209,34 @@ public class DefaultConfig
|
||||
|
||||
protected void initSignatureFactories() {
|
||||
setSignatureFactories(
|
||||
new SignatureEdDSA.Factory(),
|
||||
new SignatureECDSA.Factory256(),
|
||||
new SignatureECDSA.Factory384(),
|
||||
new SignatureECDSA.Factory521(),
|
||||
new SignatureRSA.Factory(),
|
||||
new SignatureDSA.Factory(),
|
||||
new SignatureEdDSA.Factory()
|
||||
new SignatureRSA.FactoryCERT(),
|
||||
new SignatureDSA.Factory()
|
||||
);
|
||||
}
|
||||
|
||||
protected void initMACFactories() {
|
||||
setMACFactories(
|
||||
new HMACSHA1.Factory(),
|
||||
new HMACSHA196.Factory(),
|
||||
new HMACMD5.Factory(),
|
||||
new HMACMD596.Factory(),
|
||||
new HMACSHA2256.Factory(),
|
||||
new HMACSHA2512.Factory()
|
||||
Macs.HMACSHA1(),
|
||||
Macs.HMACSHA1Etm(),
|
||||
Macs.HMACSHA196(),
|
||||
Macs.HMACSHA196Etm(),
|
||||
Macs.HMACMD5(),
|
||||
Macs.HMACMD5Etm(),
|
||||
Macs.HMACMD596(),
|
||||
Macs.HMACMD596Etm(),
|
||||
Macs.HMACSHA2256(),
|
||||
Macs.HMACSHA2256Etm(),
|
||||
Macs.HMACSHA2512(),
|
||||
Macs.HMACSHA2512Etm(),
|
||||
Macs.HMACRIPEMD160(),
|
||||
Macs.HMACRIPEMD160Etm(),
|
||||
Macs.HMACRIPEMD16096(),
|
||||
Macs.HMACRIPEMD160OpenSsh()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.connection.ConnectionImpl;
|
||||
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import net.schmizz.sshj.connection.channel.direct.SessionChannel;
|
||||
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
|
||||
import net.schmizz.sshj.connection.channel.direct.*;
|
||||
import net.schmizz.sshj.connection.channel.forwarded.ConnectListener;
|
||||
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder;
|
||||
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder.ForwardedTCPIPChannel;
|
||||
@@ -61,7 +58,6 @@ import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -360,8 +356,7 @@ public class SSHClient
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPublickey(String username, KeyProvider... keyProviders)
|
||||
throws UserAuthException,
|
||||
TransportException {
|
||||
throws UserAuthException, TransportException {
|
||||
authPublickey(username, Arrays.<KeyProvider>asList(keyProviders));
|
||||
}
|
||||
|
||||
@@ -666,13 +661,27 @@ public class SSHClient
|
||||
*
|
||||
* @return a {@link LocalPortForwarder}
|
||||
*/
|
||||
public LocalPortForwarder newLocalPortForwarder(LocalPortForwarder.Parameters parameters,
|
||||
public LocalPortForwarder newLocalPortForwarder(Parameters parameters,
|
||||
ServerSocket serverSocket) {
|
||||
LocalPortForwarder forwarder = new LocalPortForwarder(conn, parameters, serverSocket, loggerFactory);
|
||||
forwarders.add(forwarder);
|
||||
return forwarder;
|
||||
}
|
||||
|
||||
/** Create a {@link DirectConnection} channel that connects to a remote address from the server.
|
||||
*
|
||||
* This can be used to open a tunnel to, for example, an HTTP server that is only
|
||||
* accessible from the SSH server, or opening an SSH connection via a 'jump' server.
|
||||
*
|
||||
* @param hostname name of the host to connect to from the server.
|
||||
* @param port remote port number.
|
||||
*/
|
||||
public DirectConnection newDirectConnection(String hostname, int port) throws IOException {
|
||||
DirectConnection tunnel = new DirectConnection(conn, hostname, port);
|
||||
tunnel.open();
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@code listener} for handling forwarded X11 channels. Without having done this, an incoming X11
|
||||
* forwarding will be summarily rejected.
|
||||
|
||||
@@ -17,6 +17,8 @@ package net.schmizz.sshj;
|
||||
|
||||
import com.hierynomus.sshj.backport.JavaVersion;
|
||||
import com.hierynomus.sshj.backport.Jdk7HttpProxySocket;
|
||||
import net.schmizz.sshj.connection.channel.Channel;
|
||||
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import java.io.IOException;
|
||||
@@ -43,6 +45,9 @@ public abstract class SocketClient {
|
||||
private int timeout = 0;
|
||||
|
||||
private String hostname;
|
||||
private int port;
|
||||
|
||||
private boolean tunneled = false;
|
||||
|
||||
SocketClient(int defaultPort) {
|
||||
this.defaultPort = defaultPort;
|
||||
@@ -71,6 +76,7 @@ public abstract class SocketClient {
|
||||
@Deprecated
|
||||
public void connect(String hostname, int port, Proxy proxy) throws IOException {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
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);
|
||||
@@ -103,6 +109,7 @@ public abstract class SocketClient {
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
|
||||
this.port = port;
|
||||
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);
|
||||
@@ -122,6 +129,7 @@ public abstract class SocketClient {
|
||||
connect(InetAddress.getByName(null), port);
|
||||
} else {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
socket = socketFactory.createSocket();
|
||||
socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
|
||||
onConnect();
|
||||
@@ -133,6 +141,7 @@ public abstract class SocketClient {
|
||||
connect(InetAddress.getByName(null), port, localAddr, localPort);
|
||||
} else {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
socket = socketFactory.createSocket();
|
||||
socket.bind(new InetSocketAddress(localAddr, localPort));
|
||||
socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
|
||||
@@ -140,11 +149,26 @@ public abstract class SocketClient {
|
||||
}
|
||||
}
|
||||
|
||||
public void connectVia(Channel channel, String hostname, int port) throws IOException {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
this.input = channel.getInputStream();
|
||||
this.output = channel.getOutputStream();
|
||||
this.tunneled = true;
|
||||
onConnect();
|
||||
}
|
||||
|
||||
/** Connect to a remote address via a direct TCP/IP connection from the server. */
|
||||
public void connectVia(DirectConnection directConnection) throws IOException {
|
||||
connectVia(directConnection, directConnection.getRemoteHost(), directConnection.getRemotePort());
|
||||
}
|
||||
|
||||
public void connect(InetAddress host) throws IOException {
|
||||
connect(host, defaultPort);
|
||||
}
|
||||
|
||||
public void connect(InetAddress host, int port) throws IOException {
|
||||
this.port = port;
|
||||
socket = socketFactory.createSocket();
|
||||
socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
||||
onConnect();
|
||||
@@ -152,6 +176,7 @@ public abstract class SocketClient {
|
||||
|
||||
public void connect(InetAddress host, int port, InetAddress localAddr, int localPort)
|
||||
throws IOException {
|
||||
this.port = port;
|
||||
socket = socketFactory.createSocket();
|
||||
socket.bind(new InetSocketAddress(localAddr, localPort));
|
||||
socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
||||
@@ -174,15 +199,15 @@ public abstract class SocketClient {
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return (socket != null) && socket.isConnected();
|
||||
return tunneled || ((socket != null) && socket.isConnected());
|
||||
}
|
||||
|
||||
public int getLocalPort() {
|
||||
return socket.getLocalPort();
|
||||
return tunneled ? 65536 : socket.getLocalPort();
|
||||
}
|
||||
|
||||
public InetAddress getLocalAddress() {
|
||||
return socket.getLocalAddress();
|
||||
return socket == null ? null : socket.getLocalAddress();
|
||||
}
|
||||
|
||||
public String getRemoteHostname() {
|
||||
@@ -190,11 +215,11 @@ public abstract class SocketClient {
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return socket.getPort();
|
||||
return socket == null ? this.port : socket.getPort();
|
||||
}
|
||||
|
||||
public InetAddress getRemoteAddress() {
|
||||
return socket.getInetAddress();
|
||||
return socket == null ? null : socket.getInetAddress();
|
||||
}
|
||||
|
||||
public void setSocketFactory(SocketFactory factory) {
|
||||
@@ -238,9 +263,11 @@ public abstract class SocketClient {
|
||||
}
|
||||
|
||||
void onConnect() throws IOException {
|
||||
socket.setSoTimeout(timeout);
|
||||
input = socket.getInputStream();
|
||||
output = socket.getOutputStream();
|
||||
if (socket != null) {
|
||||
socket.setSoTimeout(timeout);
|
||||
input = socket.getInputStream();
|
||||
output = socket.getOutputStream();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -372,6 +372,7 @@ public class Buffer<T extends Buffer<T>> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T putUInt64Unchecked(long uint64) {
|
||||
ensureCapacity(8);
|
||||
data[wpos++] = (byte) (uint64 >> 56);
|
||||
data[wpos++] = (byte) (uint64 >> 48);
|
||||
data[wpos++] = (byte) (uint64 >> 40);
|
||||
@@ -452,11 +453,14 @@ public class Buffer<T extends Buffer<T>> {
|
||||
public T putSensitiveString(char[] str) {
|
||||
if (str == null)
|
||||
return putString("");
|
||||
putUInt32(str.length);
|
||||
ensureCapacity(str.length);
|
||||
for (char c : str)
|
||||
data[wpos++] = (byte) c;
|
||||
Arrays.fill(str, ' ');
|
||||
// RFC 4252, Section 8 says: passwords should be encoded as UTF-8.
|
||||
// RFC 4256, Section 3.4 says: keyboard-interactive information responses should be encoded as UTF-8.
|
||||
byte[] utf8 = ByteArrayUtils.encodeSensitiveStringToUtf8(str);
|
||||
putUInt32(utf8.length);
|
||||
ensureCapacity(utf8.length);
|
||||
for (byte c : utf8)
|
||||
data[wpos++] = c;
|
||||
Arrays.fill(utf8, (byte) 0);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Utility functions for byte arrays. */
|
||||
public class ByteArrayUtils {
|
||||
|
||||
@@ -124,4 +130,26 @@ public class ByteArrayUtils {
|
||||
}
|
||||
throw new IllegalArgumentException("Digit '" + c + "' out of bounds [0-9a-fA-F]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a char-array to UTF-8 byte-array and then blanks out source array and all intermediate arrays.
|
||||
* <p/>
|
||||
* This is useful when a plaintext password needs to be encoded as UTF-8.
|
||||
*
|
||||
* @param str A not-null string as a character array.
|
||||
*
|
||||
* @return UTF-8 bytes of the string
|
||||
*/
|
||||
public static byte[] encodeSensitiveStringToUtf8(char[] str) {
|
||||
CharsetEncoder charsetEncoder = Charset.forName("UTF-8").newEncoder();
|
||||
ByteBuffer utf8Buffer = ByteBuffer.allocate((int) (str.length * charsetEncoder.maxBytesPerChar()));
|
||||
assert utf8Buffer.hasArray();
|
||||
charsetEncoder.encode(CharBuffer.wrap(str), utf8Buffer, true);
|
||||
Arrays.fill(str, ' ');
|
||||
|
||||
byte[] utf8Bytes = new byte[utf8Buffer.position()];
|
||||
System.arraycopy(utf8Buffer.array(), 0, utf8Bytes, 0, utf8Bytes.length);
|
||||
Arrays.fill(utf8Buffer.array(), (byte) 0);
|
||||
return utf8Bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,8 +315,8 @@ public enum KeyType {
|
||||
builder.type(buf.readUInt32());
|
||||
builder.id(buf.readString());
|
||||
builder.validPrincipals(unpackList(buf.readBytes()));
|
||||
builder.validAfter(dateFromEpoch(buf.readUInt64()));
|
||||
builder.validBefore(dateFromEpoch(buf.readUInt64()));
|
||||
builder.validAfter(dateFromEpoch(buf.readUInt64AsBigInteger()));
|
||||
builder.validBefore(dateFromEpoch(buf.readUInt64AsBigInteger()));
|
||||
builder.critOptions(unpackMap(buf.readBytes()));
|
||||
builder.extensions(unpackMap(buf.readBytes()));
|
||||
buf.readString(); // reserved
|
||||
@@ -364,8 +364,13 @@ public enum KeyType {
|
||||
return ((Certificate<PublicKey>) key);
|
||||
}
|
||||
|
||||
private static Date dateFromEpoch(long seconds) {
|
||||
return new Date(seconds * 1000);
|
||||
private static Date dateFromEpoch(BigInteger seconds) {
|
||||
BigInteger maxValue = BigInteger.valueOf(Long.MAX_VALUE / 1000);
|
||||
if (seconds.compareTo(maxValue) > 0) {
|
||||
return new Date(maxValue.longValue() * 1000);
|
||||
} else {
|
||||
return new Date(seconds.longValue() * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private static long epochFromDate(Date date) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.slf4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class StreamCopier {
|
||||
|
||||
@@ -125,7 +126,7 @@ public class StreamCopier {
|
||||
long count = 0;
|
||||
int read = 0;
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long startTime = System.nanoTime();
|
||||
|
||||
if (length == -1) {
|
||||
while ((read = in.read(buf)) != -1) {
|
||||
@@ -140,7 +141,7 @@ public class StreamCopier {
|
||||
if (!keepFlushing)
|
||||
out.flush();
|
||||
|
||||
final double timeSeconds = (System.currentTimeMillis() - startTime) / 1000.0;
|
||||
final double timeSeconds = TimeUnit.NANOSECONDS.toMillis (System.nanoTime() - startTime) / 1000.0;
|
||||
final double sizeKiB = count / 1024.0;
|
||||
log.debug(String.format("%1$,.1f KiB transferred in %2$,.1f seconds (%3$,.2f KiB/s)", sizeKiB, timeSeconds, (sizeKiB / timeSeconds)));
|
||||
|
||||
|
||||
@@ -130,6 +130,9 @@ public class ConnectionImpl
|
||||
getChannel(buf).handle(msg, buf);
|
||||
} else if (msg.in(80, 90)) {
|
||||
switch (msg) {
|
||||
case GLOBAL_REQUEST:
|
||||
gotGlobalRequest(buf);
|
||||
break;
|
||||
case REQUEST_SUCCESS:
|
||||
gotGlobalReqResponse(buf);
|
||||
break;
|
||||
@@ -259,6 +262,20 @@ public class ConnectionImpl
|
||||
channels.clear();
|
||||
}
|
||||
|
||||
private void gotGlobalRequest(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
try {
|
||||
final String requestName = buf.readString();
|
||||
boolean wantReply = buf.readBoolean();
|
||||
log.debug("Received GLOBAL_REQUEST `{}`; want reply: {}", requestName, wantReply);
|
||||
if (wantReply) {
|
||||
trans.write(new SSHPacket(Message.REQUEST_FAILURE));
|
||||
}
|
||||
} catch (Buffer.BufferException be) {
|
||||
throw new ConnectionException(be);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeoutMs(int timeoutMs) {
|
||||
this.timeoutMs = timeoutMs;
|
||||
|
||||
@@ -102,7 +102,8 @@ public abstract class AbstractChannel
|
||||
|
||||
protected void init(int recipient, long remoteWinSize, long remoteMaxPacketSize) {
|
||||
this.recipient = recipient;
|
||||
rwin = new Window.Remote(remoteWinSize, (int) Math.min(remoteMaxPacketSize, REMOTE_MAX_PACKET_SIZE_CEILING), loggerFactory);
|
||||
rwin = new Window.Remote(remoteWinSize, (int) Math.min(remoteMaxPacketSize, REMOTE_MAX_PACKET_SIZE_CEILING),
|
||||
conn.getTimeoutMs(), loggerFactory);
|
||||
out = new ChannelOutputStream(this, trans, rwin);
|
||||
log.debug("Initialized - {}", this);
|
||||
}
|
||||
|
||||
@@ -39,15 +39,21 @@ public class SocketStreamCopyMonitor
|
||||
new SocketStreamCopyMonitor(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
for (Event<IOException> ev = x;
|
||||
!ev.tryAwait(frequency, unit);
|
||||
ev = (ev == x) ? y : x) {
|
||||
}
|
||||
await(x);
|
||||
await(y);
|
||||
} catch (IOException ignored) {
|
||||
} finally {
|
||||
IOUtils.closeQuietly(channel, asCloseable(socket));
|
||||
}
|
||||
}
|
||||
|
||||
private void await(final Event<IOException> event) throws IOException {
|
||||
while(true){
|
||||
if(event.tryAwait(frequency, unit)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class Window {
|
||||
|
||||
protected final Logger log;
|
||||
@@ -73,17 +75,23 @@ public abstract class Window {
|
||||
/** Controls how much data we can send before an adjustment notification from remote end is required. */
|
||||
public static final class Remote
|
||||
extends Window {
|
||||
private final long timeoutMs;
|
||||
|
||||
public Remote(long initialWinSize, int maxPacketSize, LoggerFactory loggerFactory) {
|
||||
public Remote(long initialWinSize, int maxPacketSize, long timeoutMs, LoggerFactory loggerFactory) {
|
||||
super(initialWinSize, maxPacketSize, loggerFactory);
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
public long awaitExpansion(long was) throws ConnectionException {
|
||||
synchronized (lock) {
|
||||
long end = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
|
||||
while (size <= was) {
|
||||
log.debug("Waiting, need size to grow from {} bytes", was);
|
||||
try {
|
||||
lock.wait();
|
||||
lock.wait(timeoutMs);
|
||||
if ((size <= was) && ((System.nanoTime() - end) > 0)) {
|
||||
throw new ConnectionException("Timeout when trying to expand the window size");
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new ConnectionException(ie);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.direct;
|
||||
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
|
||||
/** A channel for creating a direct TCP/IP connection from the server to a remote address. */
|
||||
public class DirectConnection extends DirectTCPIPChannel {
|
||||
public static final String LOCALHOST = "localhost";
|
||||
public static final int LOCALPORT = 65536;
|
||||
|
||||
public DirectConnection(Connection conn, String remoteHost, int remotePort) {
|
||||
super(conn, new Parameters(LOCALHOST, LOCALPORT, remoteHost, remotePort));
|
||||
}
|
||||
|
||||
public String getRemoteHost() {
|
||||
return parameters.getRemoteHost();
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return parameters.getRemotePort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.direct;
|
||||
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
|
||||
public class DirectTCPIPChannel extends AbstractDirectChannel {
|
||||
protected final Parameters parameters;
|
||||
|
||||
protected DirectTCPIPChannel(Connection conn, Parameters parameters) {
|
||||
super(conn, "direct-tcpip");
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SSHPacket buildOpenReq() {
|
||||
return super.buildOpenReq()
|
||||
.putString(parameters.getRemoteHost())
|
||||
.putUInt32(parameters.getRemotePort())
|
||||
.putString(parameters.getLocalHost())
|
||||
.putUInt32(parameters.getLocalPort());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package net.schmizz.sshj.connection.channel.direct;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.StreamCopier;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
import net.schmizz.sshj.connection.channel.SocketStreamCopyMonitor;
|
||||
@@ -34,48 +33,14 @@ import static com.hierynomus.sshj.backport.Sockets.asCloseable;
|
||||
|
||||
public class LocalPortForwarder {
|
||||
|
||||
public static class Parameters {
|
||||
|
||||
private final String localHost;
|
||||
private final int localPort;
|
||||
private final String remoteHost;
|
||||
private final int remotePort;
|
||||
|
||||
public Parameters(String localHost, int localPort, String remoteHost, int remotePort) {
|
||||
this.localHost = localHost;
|
||||
this.localPort = localPort;
|
||||
this.remoteHost = remoteHost;
|
||||
this.remotePort = remotePort;
|
||||
}
|
||||
|
||||
public String getRemoteHost() {
|
||||
return remoteHost;
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return remotePort;
|
||||
}
|
||||
|
||||
public String getLocalHost() {
|
||||
return localHost;
|
||||
}
|
||||
|
||||
public int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class DirectTCPIPChannel
|
||||
extends AbstractDirectChannel {
|
||||
public static class ForwardedChannel
|
||||
extends DirectTCPIPChannel {
|
||||
|
||||
protected final Socket socket;
|
||||
protected final Parameters parameters;
|
||||
|
||||
public DirectTCPIPChannel(Connection conn, Socket socket, Parameters parameters) {
|
||||
super(conn, "direct-tcpip");
|
||||
public ForwardedChannel(Connection conn, Socket socket, Parameters parameters) {
|
||||
super(conn, parameters);
|
||||
this.socket = socket;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
protected void start()
|
||||
@@ -90,16 +55,6 @@ public class LocalPortForwarder {
|
||||
.spawnDaemon("chan2soc");
|
||||
SocketStreamCopyMonitor.monitor(5, TimeUnit.SECONDS, soc2chan, chan2soc, this, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SSHPacket buildOpenReq() {
|
||||
return super.buildOpenReq()
|
||||
.putString(parameters.getRemoteHost())
|
||||
.putUInt32(parameters.getRemotePort())
|
||||
.putString(parameters.getLocalHost())
|
||||
.putUInt32(parameters.getLocalPort());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final LoggerFactory loggerFactory;
|
||||
@@ -118,7 +73,7 @@ public class LocalPortForwarder {
|
||||
}
|
||||
|
||||
private void startChannel(Socket socket) throws IOException {
|
||||
DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, socket, parameters);
|
||||
ForwardedChannel chan = new ForwardedChannel(conn, socket, parameters);
|
||||
try {
|
||||
chan.open();
|
||||
chan.start();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.direct;
|
||||
|
||||
public class Parameters {
|
||||
|
||||
private final String localHost;
|
||||
private final int localPort;
|
||||
private final String remoteHost;
|
||||
private final int remotePort;
|
||||
|
||||
public Parameters(String localHost, int localPort, String remoteHost, int remotePort) {
|
||||
this.localHost = localHost;
|
||||
this.localPort = localPort;
|
||||
this.remoteHost = remoteHost;
|
||||
this.remotePort = remotePort;
|
||||
}
|
||||
|
||||
public String getRemoteHost() {
|
||||
return remoteHost;
|
||||
}
|
||||
|
||||
public int getRemotePort() {
|
||||
return remotePort;
|
||||
}
|
||||
|
||||
public String getLocalHost() {
|
||||
return localHost;
|
||||
}
|
||||
|
||||
public int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,8 +18,14 @@ package net.schmizz.sshj.sftp;
|
||||
public class PathComponents {
|
||||
|
||||
static String adjustForParent(String parent, String path, String pathSep) {
|
||||
return (path.startsWith(pathSep)) ? path // Absolute path, nothing to adjust
|
||||
: (parent + (parent.endsWith(pathSep) ? "" : pathSep) + path); // Relative path
|
||||
if (path.startsWith(pathSep)) {
|
||||
return path; // Absolute path, nothing to adjust
|
||||
} else if (parent.endsWith(pathSep)) {
|
||||
return parent + path; // Relative path, parent endsWith '/'
|
||||
} else if (parent.isEmpty()) {
|
||||
return path;
|
||||
}
|
||||
return parent + pathSep + path; // Relative path
|
||||
}
|
||||
|
||||
static String trimTrailingSeparator(String somePath, String pathSep) {
|
||||
@@ -33,7 +39,8 @@ public class PathComponents {
|
||||
public PathComponents(String parent, String name, String pathSep) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.path = trimTrailingSeparator(adjustForParent(parent, name, pathSep), pathSep);
|
||||
String adjusted = adjustForParent(parent, name, pathSep);
|
||||
this.path = !pathSep.equals(adjusted) ? trimTrailingSeparator(adjusted, pathSep) : adjusted;
|
||||
}
|
||||
|
||||
public String getParent() {
|
||||
|
||||
@@ -70,16 +70,25 @@ public class PathHelper {
|
||||
*/
|
||||
public PathComponents getComponents(final String path)
|
||||
throws IOException {
|
||||
if (path.equals(pathSep))
|
||||
return getComponents("", "");
|
||||
if (path.equals(pathSep)) {
|
||||
return getComponents("", "/");
|
||||
}
|
||||
|
||||
if (path.isEmpty() || ".".equals(path) || ("." + pathSep).equals(path))
|
||||
if (path.isEmpty() || ".".equals(path) || ("." + pathSep).equals(path)) {
|
||||
return getComponents(getDotDir());
|
||||
}
|
||||
|
||||
final String withoutTrailSep = trimTrailingSeparator(path);
|
||||
final int lastSep = withoutTrailSep.lastIndexOf(pathSep);
|
||||
final String parent = (lastSep == -1) ? "" : withoutTrailSep.substring(0, lastSep);
|
||||
final String name = (lastSep == -1) ? withoutTrailSep : withoutTrailSep.substring(lastSep + pathSep.length());
|
||||
String parent;
|
||||
String name;
|
||||
if (lastSep == -1) {
|
||||
parent = "";
|
||||
name = withoutTrailSep;
|
||||
} else {
|
||||
parent = lastSep == 0 ? "/" : withoutTrailSep.substring(0, lastSep);
|
||||
name = withoutTrailSep.substring(lastSep + pathSep.length());
|
||||
}
|
||||
|
||||
if (".".equals(name) || "..".equals(name)) {
|
||||
return getComponents(canonicalizer.canonicalize(path));
|
||||
@@ -87,5 +96,4 @@ public class PathHelper {
|
||||
return getComponents(parent, name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,6 +68,9 @@ public final class Response
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private final int protocolVersion;
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Date;
|
||||
|
||||
/** RSA {@link Signature} */
|
||||
public class SignatureRSA
|
||||
@@ -30,7 +34,7 @@ public class SignatureRSA
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureRSA();
|
||||
return new SignatureRSA(KeyType.RSA.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,8 +44,42 @@ public class SignatureRSA
|
||||
|
||||
}
|
||||
|
||||
public SignatureRSA() {
|
||||
/** A named factory for RSA {@link Signature} */
|
||||
public static class FactoryCERT
|
||||
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureRSA(KeyType.RSA_CERT.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.RSA_CERT.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String keyTypeName;
|
||||
|
||||
|
||||
public SignatureRSA(String keyTypeName) {
|
||||
super("SHA1withRSA");
|
||||
this.keyTypeName = keyTypeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initVerify(PublicKey publicKey) {
|
||||
try {
|
||||
if (this.keyTypeName.equals(KeyType.RSA_CERT.toString()) && publicKey instanceof Certificate) {
|
||||
signature.initVerify(((Certificate<PublicKey>) publicKey).getKey());
|
||||
} else {
|
||||
signature.initVerify(publicKey);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,12 +89,11 @@ public class SignatureRSA
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
sig = extractSig(sig, "ssh-rsa");
|
||||
sig = extractSig(sig, KeyType.RSA.toString());
|
||||
try {
|
||||
return signature.verify(sig);
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ abstract class Converter {
|
||||
protected int cipherSize = 8;
|
||||
protected long seq = -1;
|
||||
protected boolean authed;
|
||||
protected boolean etm;
|
||||
|
||||
long getSequenceNumber() {
|
||||
return seq;
|
||||
@@ -56,6 +57,7 @@ abstract class Converter {
|
||||
if (compression != null)
|
||||
compression.init(getCompressionType());
|
||||
this.cipherSize = cipher.getIVSize();
|
||||
this.etm = mac.isEtm();
|
||||
}
|
||||
|
||||
void setAuthenticated() {
|
||||
|
||||
@@ -21,7 +21,9 @@ import net.schmizz.sshj.transport.compression.Compression;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/** Decodes packets from the SSH binary protocol per the current algorithms. */
|
||||
/**
|
||||
* Decodes packets from the SSH binary protocol per the current algorithms.
|
||||
*/
|
||||
final class Decoder
|
||||
extends Converter {
|
||||
|
||||
@@ -29,16 +31,26 @@ final class Decoder
|
||||
|
||||
private final Logger log;
|
||||
|
||||
/** What we pass decoded packets to */
|
||||
/**
|
||||
* What we pass decoded packets to
|
||||
*/
|
||||
private final SSHPacketHandler packetHandler;
|
||||
/** Buffer where as-yet undecoded data lives */
|
||||
/**
|
||||
* Buffer where as-yet undecoded data lives
|
||||
*/
|
||||
private final SSHPacket inputBuffer = new SSHPacket();
|
||||
/** Used in case compression is active to store the uncompressed data */
|
||||
/**
|
||||
* Used in case compression is active to store the uncompressed data
|
||||
*/
|
||||
private final SSHPacket uncompressBuffer = new SSHPacket();
|
||||
/** MAC result is stored here */
|
||||
/**
|
||||
* MAC result is stored here
|
||||
*/
|
||||
private byte[] macResult;
|
||||
|
||||
/** -1 if packet length not yet been decoded, else the packet length */
|
||||
/**
|
||||
* -1 if packet length not yet been decoded, else the packet length
|
||||
*/
|
||||
private int packetLength = -1;
|
||||
|
||||
/**
|
||||
@@ -60,53 +72,97 @@ final class Decoder
|
||||
*/
|
||||
private int decode()
|
||||
throws SSHException {
|
||||
|
||||
if (etm) {
|
||||
return decodeEtm();
|
||||
} else {
|
||||
return decodeMte();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an Encrypt-Then-Mac packet.
|
||||
*/
|
||||
private int decodeEtm() throws SSHException {
|
||||
int bytesNeeded;
|
||||
while (true) {
|
||||
if (packetLength == -1) {
|
||||
assert inputBuffer.rpos() == 0 : "buffer cleared";
|
||||
bytesNeeded = 4 - inputBuffer.available();
|
||||
if (bytesNeeded <= 0) {
|
||||
// In Encrypt-Then-Mac, the packetlength is sent unencrypted.
|
||||
packetLength = inputBuffer.readUInt32AsInt();
|
||||
checkPacketLength(packetLength);
|
||||
} else {
|
||||
// Needs more data
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
assert inputBuffer.rpos() == 4 : "packet length read";
|
||||
bytesNeeded = packetLength + mac.getBlockSize() - inputBuffer.available();
|
||||
if (bytesNeeded <= 0) {
|
||||
seq = seq + 1 & 0xffffffffL;
|
||||
checkMAC(inputBuffer.array());
|
||||
decryptBuffer(4, packetLength);
|
||||
inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte());
|
||||
final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer;
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Received packet #{}: {}", seq, plain.printHex());
|
||||
}
|
||||
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet
|
||||
inputBuffer.clear();
|
||||
packetLength = -1;
|
||||
} else {
|
||||
// Needs more data
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytesNeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Mac-Then-Encrypt packet
|
||||
* @return
|
||||
* @throws SSHException
|
||||
*/
|
||||
private int decodeMte() throws SSHException {
|
||||
int need;
|
||||
|
||||
/* Decoding loop */
|
||||
for (; ; )
|
||||
|
||||
if (packetLength == -1) // Waiting for beginning of packet
|
||||
{
|
||||
|
||||
if (packetLength == -1) { // Waiting for beginning of packet
|
||||
assert inputBuffer.rpos() == 0 : "buffer cleared";
|
||||
|
||||
need = cipherSize - inputBuffer.available();
|
||||
if (need <= 0)
|
||||
if (need <= 0) {
|
||||
packetLength = decryptLength();
|
||||
else
|
||||
} else {
|
||||
// Need more data
|
||||
break;
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
assert inputBuffer.rpos() == 4 : "packet length read";
|
||||
|
||||
need = packetLength + (mac != null ? mac.getBlockSize() : 0) - inputBuffer.available();
|
||||
if (need <= 0) {
|
||||
|
||||
decryptPayload(inputBuffer.array());
|
||||
|
||||
decryptBuffer(cipherSize, packetLength + 4 - cipherSize); // Decrypt the rest of the payload
|
||||
seq = seq + 1 & 0xffffffffL;
|
||||
|
||||
if (mac != null)
|
||||
if (mac != null) {
|
||||
checkMAC(inputBuffer.array());
|
||||
|
||||
}
|
||||
// Exclude the padding & MAC
|
||||
inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte());
|
||||
|
||||
final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer;
|
||||
|
||||
if (log.isTraceEnabled())
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Received packet #{}: {}", seq, plain.printHex());
|
||||
|
||||
}
|
||||
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet
|
||||
|
||||
inputBuffer.clear();
|
||||
packetLength = -1;
|
||||
|
||||
} else
|
||||
} else {
|
||||
// Need more data
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return need;
|
||||
@@ -118,8 +174,9 @@ final class Decoder
|
||||
mac.update(data, 0, packetLength + 4); // packetLength+4 = entire packet w/o mac
|
||||
mac.doFinal(macResult, 0); // compute
|
||||
// Check against the received MAC
|
||||
if (!ByteArrayUtils.equals(macResult, 0, data, packetLength + 4, mac.getBlockSize()))
|
||||
if (!ByteArrayUtils.equals(macResult, 0, data, packetLength + 4, mac.getBlockSize())) {
|
||||
throw new TransportException(DisconnectReason.MAC_ERROR, "MAC Error");
|
||||
}
|
||||
}
|
||||
|
||||
private SSHPacket decompressed()
|
||||
@@ -131,7 +188,7 @@ final class Decoder
|
||||
|
||||
private int decryptLength()
|
||||
throws TransportException {
|
||||
cipher.update(inputBuffer.array(), 0, cipherSize);
|
||||
decryptBuffer(0, cipherSize);
|
||||
|
||||
final int len; // Read packet length
|
||||
try {
|
||||
@@ -140,22 +197,26 @@ final class Decoder
|
||||
throw new TransportException(be);
|
||||
}
|
||||
|
||||
if (isInvalidPacketLength(len)) { // Check packet length validity
|
||||
log.error("Error decoding packet (invalid length) {}", inputBuffer.printHex());
|
||||
throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "invalid packet length: " + len);
|
||||
}
|
||||
checkPacketLength(len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
private static boolean isInvalidPacketLength(int len) {
|
||||
return len < 5 || len > MAX_PACKET_LEN;
|
||||
private void decryptBuffer(int offset, int length) {
|
||||
cipher.update(inputBuffer.array(), offset, length);
|
||||
}
|
||||
|
||||
private void decryptPayload(final byte[] data) {
|
||||
cipher.update(data, cipherSize, packetLength + 4 - cipherSize);
|
||||
private void checkPacketLength(int len) throws TransportException {
|
||||
if (len < 5 || len > MAX_PACKET_LEN) { // Check packet length validity
|
||||
log.error("Error decoding packet (invalid length) {}", inputBuffer.printHex());
|
||||
throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "invalid packet length: " + len);
|
||||
}
|
||||
}
|
||||
|
||||
// private void decryptPayload(final byte[] data, int offset, int length) {
|
||||
// cipher.update(data, cipherSize, packetLength + 4 - cipherSize);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Adds {@code len} bytes from {@code b} to the decoder buffer. When a packet has been successfully decoded, hooks
|
||||
* in to {@link SSHPacketHandler#handle} of the {@link SSHPacketHandler} this decoder was initialized with.
|
||||
|
||||
@@ -67,36 +67,58 @@ final class Encoder
|
||||
log.trace("Encoding packet #{}: {}", seq + 1, buffer.printHex());
|
||||
}
|
||||
|
||||
if (usingCompression())
|
||||
if (usingCompression()) {
|
||||
compress(buffer);
|
||||
}
|
||||
|
||||
final int payloadSize = buffer.available();
|
||||
int lengthWithoutPadding;
|
||||
if (etm) {
|
||||
// in Encrypt-Then-Mac mode, the length field is not encrypted, so we should keep it out of the
|
||||
// padding length calculation
|
||||
lengthWithoutPadding = 1 + payloadSize; // padLength (1 byte) + payload
|
||||
} else {
|
||||
lengthWithoutPadding = 4 + 1 + payloadSize; // packetLength (4 bytes) + padLength (1 byte) + payload
|
||||
}
|
||||
|
||||
// Compute padding length
|
||||
int padLen = -(payloadSize + 5) & cipherSize - 1;
|
||||
if (padLen < cipherSize)
|
||||
int padLen = cipherSize - (lengthWithoutPadding % cipherSize);
|
||||
if (padLen < 4) {
|
||||
padLen += cipherSize;
|
||||
}
|
||||
|
||||
final int startOfPacket = buffer.rpos() - 5;
|
||||
final int packetLen = payloadSize + 1 + padLen;
|
||||
int packetLen = 1 + payloadSize + padLen; // packetLength = padLen (1 byte) + payload + padding
|
||||
|
||||
if (packetLen < 16) {
|
||||
padLen += cipherSize;
|
||||
packetLen = 1 + payloadSize + padLen;
|
||||
}
|
||||
|
||||
final int endOfPadding = startOfPacket + 4 + packetLen;
|
||||
|
||||
// Put packet header
|
||||
buffer.wpos(startOfPacket);
|
||||
buffer.putUInt32(packetLen);
|
||||
buffer.putByte((byte) padLen);
|
||||
|
||||
// Now wpos will mark end of padding
|
||||
buffer.wpos(startOfPacket + 5 + payloadSize + padLen);
|
||||
buffer.wpos(endOfPadding);
|
||||
|
||||
// Fill padding
|
||||
prng.fill(buffer.array(), buffer.wpos() - padLen, padLen);
|
||||
prng.fill(buffer.array(), endOfPadding - padLen, padLen);
|
||||
|
||||
seq = seq + 1 & 0xffffffffL;
|
||||
|
||||
if (mac != null)
|
||||
putMAC(buffer, startOfPacket, buffer.wpos());
|
||||
|
||||
cipher.update(buffer.array(), startOfPacket, 4 + packetLen);
|
||||
if (etm) {
|
||||
cipher.update(buffer.array(), startOfPacket + 4, packetLen);
|
||||
putMAC(buffer, startOfPacket, endOfPadding);
|
||||
} else {
|
||||
if (mac != null) {
|
||||
putMAC(buffer, startOfPacket, endOfPadding);
|
||||
}
|
||||
|
||||
cipher.update(buffer.array(), startOfPacket, 4 + packetLen);
|
||||
}
|
||||
buffer.rpos(startOfPacket); // Make ready-to-read
|
||||
|
||||
return seq;
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.security.GeneralSecurityException;
|
||||
|
||||
public class Curve25519SHA256 extends AbstractDHG {
|
||||
/** Named factory for Curve25519SHA256 key exchange */
|
||||
public static class Factory
|
||||
public static class FactoryLibSsh
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
@@ -35,6 +35,21 @@ public class Curve25519SHA256 extends AbstractDHG {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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";
|
||||
}
|
||||
}
|
||||
|
||||
public Curve25519SHA256() {
|
||||
super(new Curve25519DH(), new SHA256());
|
||||
}
|
||||
|
||||
@@ -30,12 +30,18 @@ public class BaseMAC
|
||||
private final int defbsize;
|
||||
private final int bsize;
|
||||
private final byte[] tmp;
|
||||
private final boolean etm;
|
||||
private javax.crypto.Mac mac;
|
||||
|
||||
public BaseMAC(String algorithm, int bsize, int defbsize) {
|
||||
this(algorithm, bsize, defbsize, false);
|
||||
}
|
||||
|
||||
public BaseMAC(String algorithm, int bsize, int defbsize, boolean isEtm) {
|
||||
this.algorithm = algorithm;
|
||||
this.bsize = bsize;
|
||||
this.defbsize = defbsize;
|
||||
this.etm = isEtm;
|
||||
tmp = new byte[defbsize];
|
||||
}
|
||||
|
||||
@@ -112,4 +118,8 @@ public class BaseMAC
|
||||
update(tmp, 0, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEtm() {
|
||||
return etm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.mac;
|
||||
|
||||
/** Message Authentication Code for use in SSH. It usually wraps a javax.crypto.Mac class. */
|
||||
/**
|
||||
* Message Authentication Code for use in SSH. It usually wraps a javax.crypto.Mac class.
|
||||
*/
|
||||
public interface MAC {
|
||||
|
||||
byte[] doFinal();
|
||||
@@ -33,4 +35,40 @@ public interface MAC {
|
||||
void update(byte[] foo, int start, int len);
|
||||
|
||||
void update(long foo);
|
||||
|
||||
/**
|
||||
* Indicates that an Encrypt-Then-Mac algorithm was selected.
|
||||
* <p>
|
||||
* This has the following implementation details.
|
||||
* 1.5 transport: Protocol 2 Encrypt-then-MAC MAC algorithms
|
||||
* <p>
|
||||
* OpenSSH supports MAC algorithms, whose names contain "-etm", that
|
||||
* perform the calculations in a different order to that defined in RFC
|
||||
* 4253. These variants use the so-called "encrypt then MAC" ordering,
|
||||
* calculating the MAC over the packet ciphertext rather than the
|
||||
* plaintext. This ordering closes a security flaw in the SSH transport
|
||||
* protocol, where decryption of unauthenticated ciphertext provided a
|
||||
* "decryption oracle" that could, in conjunction with cipher flaws, reveal
|
||||
* session plaintext.
|
||||
* <p>
|
||||
* Specifically, the "-etm" MAC algorithms modify the transport protocol
|
||||
* to calculate the MAC over the packet ciphertext and to send the packet
|
||||
* length unencrypted. This is necessary for the transport to obtain the
|
||||
* length of the packet and location of the MAC tag so that it may be
|
||||
* verified without decrypting unauthenticated data.
|
||||
* <p>
|
||||
* As such, the MAC covers:
|
||||
* <p>
|
||||
* mac = MAC(key, sequence_number || packet_length || encrypted_packet)
|
||||
* <p>
|
||||
* where "packet_length" is encoded as a uint32 and "encrypted_packet"
|
||||
* contains:
|
||||
* <p>
|
||||
* byte padding_length
|
||||
* byte[n1] payload; n1 = packet_length - padding_length - 1
|
||||
* byte[n2] random padding; n2 = padding_length
|
||||
*
|
||||
* @return Whether the MAC algorithm is an Encrypt-Then-Mac algorithm
|
||||
*/
|
||||
boolean isEtm();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import net.schmizz.sshj.common.Base64;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
|
||||
public class FingerprintVerifier implements HostKeyVerifier {
|
||||
private static final Pattern MD5_FINGERPRINT_PATTERN = Pattern.compile("[0-9a-f]{2}+(:[0-9a-f]{2}+){15}+");
|
||||
@@ -121,4 +120,8 @@ public class FingerprintVerifier implements HostKeyVerifier {
|
||||
return Arrays.equals(fingerprintData, digestData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FingerprintVerifier{digestAlgorithm='" + digestAlgorithm + "'}";
|
||||
}
|
||||
}
|
||||
@@ -199,17 +199,22 @@ public class OpenSSHKnownHosts
|
||||
return new CommentEntry(line);
|
||||
}
|
||||
|
||||
final String[] split = line.split(" ");
|
||||
|
||||
final String trimmed = line.trim();
|
||||
int minComponents = 3;
|
||||
if (trimmed.startsWith("@")) {
|
||||
minComponents = 4;
|
||||
}
|
||||
String[] split = trimmed.split("\\s+", minComponents + 1); // Add 1 for optional comments
|
||||
if(split.length < minComponents) {
|
||||
log.error("Error reading entry `{}`", line);
|
||||
return new BadHostEntry(line);
|
||||
}
|
||||
int i = 0;
|
||||
|
||||
final Marker marker = Marker.fromString(split[i]);
|
||||
if (marker != null) {
|
||||
i++;
|
||||
}
|
||||
if(split.length < 3) {
|
||||
log.error("Error reading entry `{}`", line);
|
||||
return new BadHostEntry(line);
|
||||
}
|
||||
final String hostnames = split[i++];
|
||||
final String sType = split[i++];
|
||||
|
||||
@@ -227,6 +232,9 @@ public class OpenSSHKnownHosts
|
||||
}
|
||||
} else if (isBits(sType)) {
|
||||
type = KeyType.RSA;
|
||||
minComponents += 1;
|
||||
// re-split
|
||||
split = trimmed.split("\\s+", minComponents + 1); // Add 1 for optional comments
|
||||
// int bits = Integer.valueOf(sType);
|
||||
final BigInteger e = new BigInteger(split[i++]);
|
||||
final BigInteger n = new BigInteger(split[i++]);
|
||||
@@ -242,7 +250,13 @@ public class OpenSSHKnownHosts
|
||||
return new BadHostEntry(line);
|
||||
}
|
||||
|
||||
return new HostEntry(marker, hostnames, type, key);
|
||||
final String comment;
|
||||
if (i < split.length) {
|
||||
comment = split[i++];
|
||||
} else {
|
||||
comment = null;
|
||||
}
|
||||
return new HostEntry(marker, hostnames, type, key, comment);
|
||||
}
|
||||
|
||||
private boolean isBits(String type) {
|
||||
@@ -323,13 +337,19 @@ public class OpenSSHKnownHosts
|
||||
private final String hostPart;
|
||||
protected final KeyType type;
|
||||
protected final PublicKey key;
|
||||
private final String comment;
|
||||
private final KnownHostMatchers.HostMatcher matcher;
|
||||
|
||||
public HostEntry(Marker marker, String hostPart, KeyType type, PublicKey key) throws SSHException {
|
||||
this(marker, hostPart, type, key, "");
|
||||
}
|
||||
|
||||
public HostEntry(Marker marker, String hostPart, KeyType type, PublicKey key, String comment) throws SSHException {
|
||||
this.marker = marker;
|
||||
this.hostPart = hostPart;
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.comment = comment;
|
||||
this.matcher = KnownHostMatchers.createMatcher(hostPart);
|
||||
}
|
||||
|
||||
@@ -366,6 +386,9 @@ public class OpenSSHKnownHosts
|
||||
line.append(getHostPart());
|
||||
line.append(" ").append(type.toString());
|
||||
line.append(" ").append(getKeyString(key));
|
||||
|
||||
if (!comment.isEmpty()) line.append(" ").append(comment);
|
||||
|
||||
return line.toString();
|
||||
}
|
||||
|
||||
@@ -377,6 +400,10 @@ public class OpenSSHKnownHosts
|
||||
protected String getHostPart() {
|
||||
return hostPart;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BadHostEntry implements KnownHostEntry {
|
||||
@@ -440,4 +467,10 @@ public class OpenSSHKnownHosts
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OpenSSHKnownHosts{khFile='" + khFile + "'}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class KeyProviderUtil {
|
||||
|
||||
private static KeyFormat keyFormatFromHeader(String header, boolean separatePubKey) {
|
||||
if (header.startsWith("-----BEGIN") && header.endsWith("PRIVATE KEY-----")) {
|
||||
if (separatePubKey && header.contains(OpenSSHKeyV1KeyFile.OPENSSH_PRIVATE_KEY)) {
|
||||
if (header.contains(OpenSSHKeyV1KeyFile.OPENSSH_PRIVATE_KEY)) {
|
||||
return KeyFormat.OpenSSHv1;
|
||||
} else if (separatePubKey) {
|
||||
// Can delay asking for password since have unencrypted pubkey
|
||||
|
||||
@@ -93,13 +93,21 @@ public class OpenSSHKeyFile
|
||||
private void initPubKey(Reader publicKey) throws IOException {
|
||||
final BufferedReader br = new BufferedReader(publicKey);
|
||||
try {
|
||||
final String keydata = br.readLine();
|
||||
if (keydata != null) {
|
||||
String[] parts = keydata.trim().split(" ");
|
||||
assert parts.length >= 2;
|
||||
type = KeyType.fromString(parts[0]);
|
||||
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
|
||||
String keydata;
|
||||
while ((keydata = br.readLine()) != null) {
|
||||
keydata = keydata.trim();
|
||||
if (!keydata.isEmpty()) {
|
||||
String[] parts = keydata.trim().split("\\s+");
|
||||
if (parts.length >= 2) {
|
||||
type = KeyType.fromString(parts[0]);
|
||||
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
|
||||
} else {
|
||||
throw new IOException("Got line with only one column");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IOException("Public key file is blank");
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,11 @@ public class LoggingTransferListener
|
||||
public void reportProgress(long transferred)
|
||||
throws IOException {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("transferred {}% of `{}`", ((transferred * 100) / size), path);
|
||||
long percent = 100;
|
||||
if (size > 0) {
|
||||
percent = (transferred * 100)/size;
|
||||
}
|
||||
log.trace("transferred {}% of `{}`", percent, path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package com.hierynomus.sshj.connection.channel.direct
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture
|
||||
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder
|
||||
import net.schmizz.sshj.connection.channel.direct.Parameters
|
||||
import org.junit.Rule
|
||||
import spock.lang.Specification
|
||||
import spock.util.concurrent.PollingConditions
|
||||
@@ -33,7 +33,7 @@ class LocalPortForwarderSpec extends Specification {
|
||||
def client = tunnelFixture.setupConnectedDefaultClient()
|
||||
client.authPassword("test", "test")
|
||||
def socket = new ServerSocket(0)
|
||||
def lpf = client.newLocalPortForwarder(new LocalPortForwarder.Parameters("localhost", socket.getLocalPort(), "localhost", realServer.server.port), socket)
|
||||
def lpf = client.newLocalPortForwarder(new Parameters("localhost", socket.getLocalPort(), "localhost", realServer.server.port), socket)
|
||||
def thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
void run() {
|
||||
|
||||
@@ -18,6 +18,7 @@ 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.FileMode
|
||||
import net.schmizz.sshj.sftp.SFTPClient
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@@ -167,6 +168,40 @@ class SFTPClientSpec extends Specification {
|
||||
!new File(dest, "totototo.txt").exists()
|
||||
}
|
||||
|
||||
def "should mkdirs with existing parent path"() {
|
||||
given:
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient()
|
||||
sshClient.authPassword("test", "test")
|
||||
SFTPClient ftp = sshClient.newSFTPClient()
|
||||
ftp.mkdir("dir1")
|
||||
|
||||
when:
|
||||
ftp.mkdirs("dir1/dir2/dir3/dir4")
|
||||
|
||||
then:
|
||||
ftp.statExistence("dir1/dir2/dir3/dir4") != null
|
||||
|
||||
cleanup:
|
||||
["dir1/dir2/dir3/dir4", "dir1/dir2/dir3", "dir1/dir2", "dir1"].each {
|
||||
ftp.rmdir(it)
|
||||
}
|
||||
ftp.close()
|
||||
sshClient.disconnect()
|
||||
}
|
||||
|
||||
def "should stat root"() {
|
||||
given:
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient()
|
||||
sshClient.authPassword("test", "test")
|
||||
SFTPClient ftp = sshClient.newSFTPClient()
|
||||
|
||||
when:
|
||||
def attrs = ftp.statExistence("/")
|
||||
|
||||
then:
|
||||
attrs.type == FileMode.Type.DIRECTORY
|
||||
}
|
||||
|
||||
private void doUpload(File src, File dest) throws IOException {
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient()
|
||||
sshClient.authPassword("test", "test")
|
||||
|
||||
@@ -116,6 +116,36 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL
|
||||
s << ["\n", "#comment\n"]
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "should contain comment at end of line"() {
|
||||
given:
|
||||
def f = knownHosts(host)
|
||||
when:
|
||||
OpenSSHKnownHosts knownHosts = new OpenSSHKnownHosts(f)
|
||||
|
||||
then:
|
||||
knownHosts.entries().size() == 1
|
||||
def entry = knownHosts.entries().get(0)
|
||||
entry instanceof OpenSSHKnownHosts.HostEntry
|
||||
entry.comment == comment
|
||||
|
||||
where:
|
||||
host << [
|
||||
"|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ== this is a comment",
|
||||
"test.com,1.1.1.1 2048 35 22017496617994656680820635966392838863613340434802393112245951008866692373218840197754553998457793202561151141246686162285550121243768846314646395880632789308110750881198697743542374668273149584280424505890648953477691795864456749782348425425954366277600319096366690719901119774784695056100331902394094537054256611668966698242432417382422091372756244612839068092471592121759862971414741954991375710930168229171638843329213652899594987626853020377726482288618521941129157643483558764875338089684351824791983007780922947554898825663693324944982594850256042689880090306493029526546183035567296830604572253312294059766327 single",
|
||||
"schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==",
|
||||
"schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ== ",
|
||||
"schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ== extra space"
|
||||
]
|
||||
comment << [
|
||||
"this is a comment",
|
||||
"single",
|
||||
null,
|
||||
null,
|
||||
"extra space"
|
||||
]
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "should match any host name from multi-host line"() {
|
||||
given:
|
||||
@@ -132,6 +162,59 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL
|
||||
h << ["schmizz.net", "69.163.155.180"]
|
||||
}
|
||||
|
||||
def "should produce meaningful toString()"() {
|
||||
given:
|
||||
def f = knownHosts("schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==")
|
||||
|
||||
when:
|
||||
def knownhosts = new OpenSSHKnownHosts(f)
|
||||
|
||||
def toStringValue = knownhosts.toString()
|
||||
then:
|
||||
toStringValue == "OpenSSHKnownHosts{khFile='" + f + "'}"
|
||||
}
|
||||
|
||||
def "should forgive redundant spaces like OpenSSH does"() {
|
||||
given:
|
||||
def key = "AAAAC3NzaC1lZDI1NTE5AAAAIIRsJi92NJJTQwXHZiRiARoEy4n1jYsNTQePHFTSl7tG"
|
||||
def f = knownHosts("""
|
||||
|host1 ssh-ed25519 $key
|
||||
|
|
||||
| host2 ssh-ed25519 $key ,./gargage\\.,
|
||||
|\t\t\t\t\t
|
||||
|\t@revoked host3\tssh-ed25519\t \t$key\t
|
||||
""".stripMargin())
|
||||
def pk = new Buffer.PlainBuffer(Base64.decode(key)).readPublicKey()
|
||||
|
||||
when:
|
||||
def knownhosts = new OpenSSHKnownHosts(f)
|
||||
|
||||
then:
|
||||
["host1", "host2", "host3"].forEach {
|
||||
knownhosts.verify(it, 22, pk)
|
||||
}
|
||||
}
|
||||
|
||||
def "should not throw errors while parsing corrupted records"() {
|
||||
given:
|
||||
def key = "AAAAC3NzaC1lZDI1NTE5AAAAIIRsJi92NJJTQwXHZiRiARoEy4n1jYsNTQePHFTSl7tG"
|
||||
def f = knownHosts(
|
||||
"\n" // empty line
|
||||
+ " \n" // blank line
|
||||
+ "bad-host1\n" // absent key type and key contents
|
||||
+ "bad-host2 ssh-ed25519\n" // absent key contents
|
||||
+ " bad-host3 ssh-ed25519\n" // absent key contents, with leading spaces
|
||||
+ "@revoked bad-host5 ssh-ed25519\n" // absent key contents, with marker
|
||||
+ "good-host ssh-ed25519 $key" // the only good host at the end
|
||||
)
|
||||
|
||||
when:
|
||||
def knownhosts = new OpenSSHKnownHosts(f)
|
||||
|
||||
then:
|
||||
knownhosts.verify("good-host", 22, new Buffer.PlainBuffer(Base64.decode(key)).readPublicKey())
|
||||
}
|
||||
|
||||
def knownHosts(String s) {
|
||||
def f = temp.newFile("known_hosts")
|
||||
f.write(s)
|
||||
|
||||
62
src/test/groovy/net/schmizz/sshj/sftp/PathHelperSpec.groovy
Normal file
62
src/test/groovy/net/schmizz/sshj/sftp/PathHelperSpec.groovy
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.sftp
|
||||
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
class PathHelperSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
def pathHelper = new PathHelper(new PathHelper.Canonicalizer() {
|
||||
/**
|
||||
* Very basic, it does not try to canonicalize relative bits in the middle of a path.
|
||||
*/
|
||||
@Override
|
||||
String canonicalize(String path)
|
||||
throws IOException {
|
||||
if ("" == path || "." == path || "./" == path)
|
||||
return "/home/me"
|
||||
if (".." == path || "../" == path)
|
||||
return "/home"
|
||||
return path
|
||||
}
|
||||
}, "/")
|
||||
|
||||
|
||||
@Unroll
|
||||
def "should correctly componentize path \"#input\""() {
|
||||
given:
|
||||
def components = pathHelper.getComponents(input)
|
||||
|
||||
expect:
|
||||
components.getName() == name
|
||||
components.getParent() == parent
|
||||
components.getPath() == path
|
||||
|
||||
where:
|
||||
input || name | path | parent
|
||||
"" || "me" | "/home/me" | "/home"
|
||||
"/" || "/" | "/" | ""
|
||||
"." || "me" | "/home/me" | "/home"
|
||||
".." || "home" | "/home" | "/"
|
||||
"somefile" || "somefile" | "somefile" | ""
|
||||
"dir1/dir2" || "dir2" | "dir1/dir2" | "dir1"
|
||||
"/home/me/../somedir/somefile" || "somefile" | "/home/me/../somedir/somefile" | "/home/me/../somedir"
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.sftp
|
||||
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
class ResponseStatusCodeSpec extends Specification {
|
||||
|
||||
@Unroll
|
||||
def "status #status should have status code #code"() {
|
||||
expect:
|
||||
code == status.getCode()
|
||||
|
||||
where:
|
||||
status || code
|
||||
Response.StatusCode.UNKNOWN || -1
|
||||
Response.StatusCode.OK || 0
|
||||
Response.StatusCode.EOF || 1
|
||||
Response.StatusCode.NO_SUCH_FILE || 2
|
||||
Response.StatusCode.PERMISSION_DENIED || 3
|
||||
Response.StatusCode.FAILURE || 4
|
||||
Response.StatusCode.BAD_MESSAGE || 5
|
||||
Response.StatusCode.NO_CONNECTION || 6
|
||||
Response.StatusCode.CONNECITON_LOST || 7
|
||||
Response.StatusCode.OP_UNSUPPORTED || 8
|
||||
Response.StatusCode.INVALID_HANDLE || 9
|
||||
Response.StatusCode.NO_SUCH_PATH || 10
|
||||
Response.StatusCode.FILE_ALREADY_EXISTS || 11
|
||||
Response.StatusCode.WRITE_PROTECT || 12
|
||||
Response.StatusCode.NO_MEDIA || 13
|
||||
Response.StatusCode.NO_SPACE_ON_FILESYSTEM || 14
|
||||
Response.StatusCode.QUOTA_EXCEEDED || 15
|
||||
Response.StatusCode.UNKNOWN_PRINCIPAL || 16
|
||||
Response.StatusCode.LOCK_CONFLICT || 17
|
||||
Response.StatusCode.DIR_NOT_EMPTY || 18
|
||||
Response.StatusCode.NOT_A_DIRECTORY || 19
|
||||
Response.StatusCode.INVALID_FILENAME || 20
|
||||
Response.StatusCode.LINK_LOOP || 21
|
||||
Response.StatusCode.CANNOT_DELETE || 22
|
||||
Response.StatusCode.INVALID_PARAMETER || 23
|
||||
Response.StatusCode.FILE_IS_A_DIRECTORY || 24
|
||||
Response.StatusCode.BYTE_RANGE_LOCK_CONFLICT || 25
|
||||
Response.StatusCode.BYTE_RANGE_LOCK_REFUSED || 26
|
||||
Response.StatusCode.DELETE_PENDING || 27
|
||||
Response.StatusCode.FILE_CORRUPT || 28
|
||||
Response.StatusCode.OWNER_INVALID || 29
|
||||
Response.StatusCode.GROUP_INVALID || 30
|
||||
Response.StatusCode.NO_MATCHING_BYTE_RANGE_LOCK || 31
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,16 @@ class FingerprintVerifierSpec extends Specification {
|
||||
|
||||
}
|
||||
|
||||
def "should produce meaningful toString()"() {
|
||||
given:
|
||||
def verifier = FingerprintVerifier.getInstance("SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak")
|
||||
|
||||
when:
|
||||
def toStringValue = verifier.toString()
|
||||
|
||||
then:
|
||||
toStringValue == "FingerprintVerifier{digestAlgorithm='SHA-1'}"
|
||||
}
|
||||
|
||||
def getPublicKey() {
|
||||
def lines = new File("src/test/resources/keytypes/test_ed25519.pub").readLines()
|
||||
|
||||
@@ -18,6 +18,7 @@ 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 net.schmizz.sshj.sftp.SFTPClient;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -46,6 +47,10 @@ public class CommandTest {
|
||||
exec.join();
|
||||
assertThat("File should exist", file.exists());
|
||||
assertThat("File should be directory", file.isDirectory());
|
||||
|
||||
SFTPClient sftpClient = sshClient.newSFTPClient();
|
||||
if (sftpClient.statExistence("&") != null) {
|
||||
sftpClient.rmdir("&");
|
||||
// TODO fail here when this is fixed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class RemotePortForwarderTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
fixture.getServer().setTcpipForwardingFilter(new AcceptAllForwardingFilter());
|
||||
fixture.getServer().setForwardingFilter(new AcceptAllForwardingFilter());
|
||||
File file = httpServer.getDocRoot().newFile("index.html");
|
||||
FileUtil.writeToFile(file, "<html><head/><body><h1>Hi!</h1></body></html>");
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.hierynomus.sshj.test;
|
||||
import net.schmizz.sshj.Config;
|
||||
import net.schmizz.sshj.DefaultConfig;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.transport.random.JCERandom;
|
||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
@@ -32,6 +34,8 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
public abstract class BaseAlgorithmTest {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private SingletonRandomFactory randomFactory = new SingletonRandomFactory(new JCERandom.Factory());
|
||||
private DefaultConfig config = new DefaultConfig();
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture(false);
|
||||
|
||||
@@ -42,11 +46,12 @@ public abstract class BaseAlgorithmTest {
|
||||
|
||||
@Test
|
||||
public void shouldVerifyAlgorithm() throws IOException {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
logger.info("--> Attempt {}", i);
|
||||
configureServer(fixture.getServer());
|
||||
fixture.start();
|
||||
Config config = getClientConfig(new DefaultConfig());
|
||||
config.setRandomFactory(randomFactory);
|
||||
Config config = getClientConfig(this.config);
|
||||
SSHClient sshClient = fixture.connectClient(fixture.setupClient(config));
|
||||
assertThat("should be connected", sshClient.isConnected());
|
||||
sshClient.disconnect();
|
||||
|
||||
@@ -20,12 +20,11 @@ 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.SecurityUtils;
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.CommandFactory;
|
||||
import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.command.Command;
|
||||
import org.apache.sshd.server.command.CommandFactory;
|
||||
import org.apache.sshd.server.scp.ScpCommandFactory;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.shell.ProcessShellFactory;
|
||||
@@ -35,7 +34,6 @@ import org.junit.rules.ExternalResource;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
@@ -108,8 +106,7 @@ public class SshFixture extends ExternalResource {
|
||||
private SshServer defaultSshServer() {
|
||||
SshServer sshServer = SshServer.setUpDefaultServer();
|
||||
sshServer.setPort(randomPort());
|
||||
AbstractClassLoadableResourceKeyPairProvider fileKeyPairProvider = SecurityUtils.createClassLoadableResourceKeyPairProvider();
|
||||
fileKeyPairProvider.setResources(Collections.singletonList(hostkey));
|
||||
ClassLoadableResourceKeyPairProvider fileKeyPairProvider = new ClassLoadableResourceKeyPairProvider(hostkey);
|
||||
sshServer.setKeyPairProvider(fileKeyPairProvider);
|
||||
sshServer.setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ 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.Curve25519SHA256;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
||||
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
||||
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
||||
@@ -38,15 +39,21 @@ import java.util.Collections;
|
||||
@RunWith(Parameterized.class)
|
||||
public class KeyExchangeTest extends BaseAlgorithmTest {
|
||||
|
||||
@Parameterized.Parameters
|
||||
@Parameterized.Parameters(name = "algorithm={0}")
|
||||
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()}
|
||||
{DHGServer.newFactory(BuiltinDHFactories.ecdhp521), new ECDHNistP.Factory521()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg1), DHGroups.Group1SHA1()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg14), DHGroups.Group14SHA1()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg14_256), DHGroups.Group14SHA256()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg15_512), DHGroups.Group15SHA512()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg16_512), DHGroups.Group16SHA512()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg17_512), DHGroups.Group17SHA512()},
|
||||
{DHGServer.newFactory(BuiltinDHFactories.dhg18_512), DHGroups.Group18SHA512()},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hierynomus.sshj.userauth.method;
|
||||
|
||||
import com.hierynomus.sshj.test.SshFixture;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||
import net.schmizz.sshj.userauth.method.ChallengeResponseProvider;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.server.auth.UserAuth;
|
||||
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractive;
|
||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class AuthKeyboardInteractiveTest {
|
||||
@Rule
|
||||
public SshFixture fixture = new SshFixture(false);
|
||||
|
||||
@Before
|
||||
public void setKeyboardInteractiveAuthenticator() throws IOException {
|
||||
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return UserAuthKeyboardInteractiveFactory.NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuth get() {
|
||||
return new UserAuthKeyboardInteractive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuth create() {
|
||||
return get();
|
||||
}
|
||||
}));
|
||||
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, ServerSession session) {
|
||||
return password.equals(username);
|
||||
}
|
||||
});
|
||||
fixture.getServer().start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEncodePasswordsAsUtf8() throws IOException {
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||
final String userAndPassword = "øæå";
|
||||
sshClient.auth(userAndPassword, new AuthKeyboardInteractive(new ChallengeResponseProvider() {
|
||||
@Override
|
||||
public List<String> getSubmethods() {
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Resource resource, String name, String instruction) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] getResponse(String prompt, boolean echo) {
|
||||
return userAndPassword.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry() {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
assertThat("Should have been authenticated", sshClient.isAuthenticated());
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.apache.sshd.server.auth.UserAuth;
|
||||
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
|
||||
import org.apache.sshd.server.auth.password.UserAuthPassword;
|
||||
import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -50,20 +51,24 @@ public class AuthPasswordTest {
|
||||
@Before
|
||||
public void setPasswordAuthenticator() throws IOException {
|
||||
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "password";
|
||||
return UserAuthPasswordFactory.NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuth get() {
|
||||
return new UserAuthPassword() {
|
||||
@Override
|
||||
protected Boolean handleClientPasswordChangeRequest(Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword) throws Exception {
|
||||
return session.getPasswordAuthenticator().authenticate(username, newPassword, session);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuth create() {
|
||||
return new UserAuthPassword() {
|
||||
@Override
|
||||
protected Boolean handleClientPasswordChangeRequest(Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword) throws Exception {
|
||||
return checkPassword(buffer, session, username, newPassword);
|
||||
}
|
||||
};
|
||||
return get();
|
||||
}
|
||||
}));
|
||||
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
|
||||
@@ -139,6 +144,14 @@ public class AuthPasswordTest {
|
||||
assertThat("Should have been authenticated", sshClient.isAuthenticated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEncodePasswordsAsUtf8() throws IOException {
|
||||
SSHClient sshClient = fixture.setupConnectedDefaultClient();
|
||||
String userAndPassword = "øæå";
|
||||
sshClient.authPassword(userAndPassword, userAndPassword);
|
||||
assertThat("Should have been authenticated", sshClient.isAuthenticated());
|
||||
}
|
||||
|
||||
private static class StaticPasswordUpdateProvider implements PasswordUpdateProvider {
|
||||
private Stack<String> newPasswords = new Stack<String>();
|
||||
|
||||
|
||||
@@ -146,4 +146,28 @@ public class BufferTest {
|
||||
assertArrayEquals("Value: " + value, bytesLong, bytesBigInt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldExpandCapacityOfUInt32(){
|
||||
PlainBuffer buf = new PlainBuffer();
|
||||
for(int i=0;i<Buffer.DEFAULT_SIZE+1;i+=4) {
|
||||
buf.putUInt32(1l);
|
||||
}
|
||||
/* Buffer should have been expanded at this point*/
|
||||
assertEquals(Buffer.DEFAULT_SIZE*2,buf.data.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldExpandCapacityOfUInt64(){
|
||||
BigInteger bigUint64 = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
PlainBuffer buf = new PlainBuffer();
|
||||
assertEquals(Buffer.DEFAULT_SIZE,buf.data.length);
|
||||
for(int i=0;i<Buffer.DEFAULT_SIZE+1;i+=8) {
|
||||
buf.putUInt64(bigUint64.longValue());
|
||||
}
|
||||
/* Buffer should have been expanded at this point*/
|
||||
assertEquals(Buffer.DEFAULT_SIZE*2,buf.data.length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
49
src/test/java/net/schmizz/sshj/common/StreamCopierTest.java
Normal file
49
src/test/java/net/schmizz/sshj/common/StreamCopierTest.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
|
||||
public class StreamCopierTest {
|
||||
|
||||
@Test
|
||||
public void copy() throws IOException {
|
||||
Random random = new Random();
|
||||
byte[] data = new byte[1024];
|
||||
random.nextBytes(data);
|
||||
InputStream inputStream = new ByteArrayInputStream(data);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
LoggerFactory loggerFactory = mock(LoggerFactory.class);
|
||||
|
||||
org.slf4j.Logger logger = mock(org.slf4j.Logger.class);
|
||||
when(loggerFactory.getLogger(StreamCopier.class)).thenReturn(logger);
|
||||
StreamCopier streamCopier = new StreamCopier(inputStream, outputStream, loggerFactory);
|
||||
|
||||
long copied = streamCopier.copy();
|
||||
assertThat(copied, is(1024L));
|
||||
|
||||
verify(logger).debug(contains("1.0 KiB transferred"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.concurrent.ExceptionChainer;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class SocketStreamCopyMonitorTest {
|
||||
|
||||
@Test
|
||||
public void shouldNotCloseChannelIfOnlyFirstEventSet() throws Exception {
|
||||
final Channel channel = mock(Channel.class);
|
||||
final Socket socket = mock(Socket.class);
|
||||
final Event<IOException> xEvent = createEvent();
|
||||
final Event<IOException> yEvent = createEvent();
|
||||
SocketStreamCopyMonitor.monitor(1, TimeUnit.MILLISECONDS, xEvent, yEvent, channel, socket);
|
||||
|
||||
xEvent.set();
|
||||
|
||||
waitForMonitorThreadToCloseTheChannel();
|
||||
|
||||
verify(channel, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotCloseChannelIfOnlySecondEventSet() throws Exception {
|
||||
final Channel channel = mock(Channel.class);
|
||||
final Socket socket = mock(Socket.class);
|
||||
final Event<IOException> xEvent = createEvent();
|
||||
final Event<IOException> yEvent = createEvent();
|
||||
SocketStreamCopyMonitor.monitor(1, TimeUnit.MILLISECONDS, xEvent, yEvent, channel, socket);
|
||||
|
||||
yEvent.set();
|
||||
|
||||
waitForMonitorThreadToCloseTheChannel();
|
||||
|
||||
verify(channel, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCloseChannelIfBothEventsSet() throws Exception {
|
||||
final Channel channel = mock(Channel.class);
|
||||
final Socket socket = mock(Socket.class);
|
||||
final Event<IOException> xEvent = createEvent();
|
||||
final Event<IOException> yEvent = createEvent();
|
||||
SocketStreamCopyMonitor.monitor(1, TimeUnit.MILLISECONDS, xEvent, yEvent, channel, socket);
|
||||
|
||||
xEvent.set();
|
||||
yEvent.set();
|
||||
|
||||
waitForMonitorThreadToCloseTheChannel();
|
||||
|
||||
verify(channel, times(1)).close();
|
||||
}
|
||||
|
||||
private void waitForMonitorThreadToCloseTheChannel() throws InterruptedException {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
|
||||
private Event<IOException> createEvent() {
|
||||
return new Event<IOException>("event", new ExceptionChainer<IOException>() {
|
||||
@Override
|
||||
public IOException chain(Throwable t) {
|
||||
return new IOException(t);
|
||||
}
|
||||
}, LoggerFactory.DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -18,19 +18,26 @@ package net.schmizz.sshj.keyprovider;
|
||||
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||
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.util.KeyUtil;
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
@@ -96,6 +103,9 @@ public class OpenSSHKeyFileTest {
|
||||
}
|
||||
};
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void blankingOut()
|
||||
throws IOException, GeneralSecurityException {
|
||||
@@ -189,12 +199,37 @@ public class OpenSSHKeyFileTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLoadProtectedED25519PrivateKey() throws IOException {
|
||||
public void shouldLoadProtectedED25519PrivateKeyAes256CTR() throws IOException {
|
||||
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_protected", "sshjtest");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLoadProtectedED25519PrivateKeyAes256CBC() throws IOException {
|
||||
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256cbc.pem", "foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLoadRSAPrivateKeyAsOpenSSHV1() throws IOException {
|
||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/ed25519_protected"), new PasswordFinder() {
|
||||
keyFile.init(new File("src/test/resources/keyformats/rsa_opensshv1"));
|
||||
PrivateKey aPrivate = keyFile.getPrivate();
|
||||
assertThat(aPrivate.getAlgorithm(), equalTo("RSA"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException {
|
||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||
keyFile.init(new File("src/test/resources/keyformats/ecdsa_opensshv1"));
|
||||
PrivateKey aPrivate = keyFile.getPrivate();
|
||||
assertThat(aPrivate.getAlgorithm(), equalTo("ECDSA"));
|
||||
}
|
||||
|
||||
private void checkOpenSSHKeyV1(String key, final String password) throws IOException {
|
||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||
keyFile.init(new File(key), new PasswordFinder() {
|
||||
@Override
|
||||
public char[] reqPassword(Resource<?> resource) {
|
||||
return "sshjtest".toCharArray();
|
||||
return password.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,6 +275,19 @@ public class OpenSSHKeyFileTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSuccessfullyLoadSignedRSAPublicKeyWithMaxDate() throws IOException {
|
||||
FileKeyProvider keyFile = new OpenSSHKeyFile();
|
||||
keyFile.init(new File("src/test/resources/keytypes/certificate/test_rsa_max_date"),
|
||||
PasswordUtils.createOneOff(correctPassphrase));
|
||||
PublicKey pubKey = keyFile.getPublic();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Certificate<RSAPublicKey> certificate = (Certificate<RSAPublicKey>) pubKey;
|
||||
|
||||
assertTrue(parseDate("9999-04-11 18:09:27 -0400").before(certificate.getValidBefore()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSuccessfullyLoadSignedDSAPublicKey() throws IOException {
|
||||
FileKeyProvider keyFile = new OpenSSHKeyFile();
|
||||
@@ -268,6 +316,47 @@ public class OpenSSHKeyFileTest {
|
||||
assertEquals("", certificate.getExtensions().get("permit-pty"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes users copy-pastes private and public keys in text editors. It leads to redundant
|
||||
* spaces and newlines. OpenSSH can easily read such keys, so users expect from SSHJ the same.
|
||||
*/
|
||||
@Test
|
||||
public void notTrimmedKeys() throws IOException {
|
||||
File initialPrivateKey = new File("src/test/resources/id_rsa");
|
||||
File initialPublicKey = new File("src/test/resources/id_rsa.pub");
|
||||
File corruptedPrivateKey = new File(temporaryFolder.newFolder(), "id_rsa");
|
||||
File corruptedPublicKey = new File(corruptedPrivateKey.getParent(), "id_rsa.pub");
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(initialPrivateKey)));
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(corruptedPrivateKey)));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
writer.write(line);
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.write("\n\n");
|
||||
reader.close();
|
||||
writer.close();
|
||||
|
||||
reader = new BufferedReader(new InputStreamReader(new FileInputStream(initialPublicKey)));
|
||||
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(corruptedPublicKey)));
|
||||
writer.write("\n\n \t ");
|
||||
writer.write(reader.readLine().replace(" ", " \t "));
|
||||
writer.write("\n\n");
|
||||
reader.close();
|
||||
writer.close();
|
||||
|
||||
FileKeyProvider initialKeyFile = new OpenSSHKeyFile();
|
||||
FileKeyProvider corruptedKeyFile = new OpenSSHKeyFile();
|
||||
initialKeyFile.init(initialPrivateKey);
|
||||
corruptedKeyFile.init(corruptedPrivateKey);
|
||||
|
||||
assertEquals(initialKeyFile.getPrivate(),
|
||||
corruptedKeyFile.getPrivate());
|
||||
assertEquals(initialKeyFile.getPublic(),
|
||||
corruptedKeyFile.getPublic());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void checkBCRegistration() {
|
||||
if (!SecurityUtils.isBouncyCastleRegistered()) {
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
public class PathHelperTest {
|
||||
|
||||
private final PathHelper helper = new PathHelper(new PathHelper.Canonicalizer() {
|
||||
/**
|
||||
* Very basic, it does not try to canonicalize relative bits in the middle of a path.
|
||||
*/
|
||||
@Override
|
||||
public String canonicalize(String path)
|
||||
throws IOException {
|
||||
if ("".equals(path) || ".".equals(path) || "./".equals(path))
|
||||
return "/home/me";
|
||||
if ("..".equals(path) || "../".equals(path))
|
||||
return "/home";
|
||||
return path;
|
||||
}
|
||||
}, "/");
|
||||
|
||||
@Test
|
||||
public void empty()
|
||||
throws IOException {
|
||||
final PathComponents comp = helper.getComponents("");
|
||||
assertEquals("me", comp.getName());
|
||||
assertEquals("/home", comp.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void root()
|
||||
throws IOException {
|
||||
final PathComponents comp = helper.getComponents("/");
|
||||
assertEquals("", comp.getName());
|
||||
assertEquals("", comp.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dot()
|
||||
throws IOException {
|
||||
final PathComponents comp = helper.getComponents(".");
|
||||
assertEquals("me", comp.getName());
|
||||
assertEquals("/home", comp.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dotDot()
|
||||
throws IOException {
|
||||
final PathComponents comp = helper.getComponents("..");
|
||||
assertEquals("home", comp.getName());
|
||||
assertEquals("", comp.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileInHomeDir()
|
||||
throws IOException {
|
||||
final PathComponents comp = helper.getComponents("somefile");
|
||||
assertEquals("somefile", comp.getName());
|
||||
assertEquals("", comp.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileSomeLevelsDeep()
|
||||
throws IOException {
|
||||
final PathComponents comp = helper.getComponents("/home/me/../somedir/somefile");
|
||||
assertEquals("somefile", comp.getName());
|
||||
assertEquals("/home/me/../somedir", comp.getParent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,15 +30,21 @@ public class SFTPClientTest {
|
||||
@Before
|
||||
public void setPathHelper() throws Exception {
|
||||
PathHelper helper = new PathHelper(new PathHelper.Canonicalizer() {
|
||||
/**
|
||||
* Very basic, it does not try to canonicalize relative bits in the middle of a path.
|
||||
*/
|
||||
@Override
|
||||
public String canonicalize(String path)
|
||||
throws IOException {
|
||||
if (path.equals("."))
|
||||
return "/workingdirectory";
|
||||
if ("".equals(path) || ".".equals(path) || "./".equals(path))
|
||||
return "/home/me";
|
||||
if ("..".equals(path) || "../".equals(path))
|
||||
return "/home";
|
||||
return path;
|
||||
}
|
||||
}, DEFAULT_PATH_SEPARATOR);
|
||||
when(sftpEngine.getPathHelper()).thenReturn(helper);
|
||||
when(sftpEngine.stat("/")).thenReturn(new FileAttributes.Builder().withType(FileMode.Type.DIRECTORY).build());
|
||||
when(sftpEngine.getLoggerFactory()).thenReturn(LoggerFactory.DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
|
||||
public class SignatureRSATest {
|
||||
|
||||
@Test
|
||||
public void testRSACERTVerifies() throws Buffer.BufferException {
|
||||
byte[] certBytes = fromString("0, 0, 0, 28, 115, 115, 104, 45, 114, 115, 97, 45, 99, 101, 114, 116, 45, 118, 48, 49, 64, 111, 112, 101, 110, 115, 115, 104, 46, 99, 111, 109, 0, 0, 0, 32, 111, 79, -117, -106, 111, -73, 4, -86, -70, -97, -106, 40, 49, -22, 72, -41, -14, 42, -89, -123, -89, 72, 86, 24, 110, -103, -67, 99, 72, -53, 108, -109, 0, 0, 0, 3, 1, 0, 1, 0, 0, 1, 1, 0, -59, -10, 91, 117, -120, 1, -103, 88, 106, -115, -76, -92, 116, -28, 116, 76, 75, 36, 82, 27, 105, 98, -48, 122, -69, -33, 101, 54, -55, 15, 76, -103, 100, -128, 19, 9, 109, 5, 114, 11, 9, 74, -45, -98, 66, 78, 88, -125, 24, -56, -90, 56, 99, 109, -118, -7, -94, 22, 50, 105, -55, 116, 79, 44, 122, 23, -39, -23, -33, -96, -49, 109, 50, 103, -55, 7, -99, -48, 123, 10, 114, -89, -22, -116, 97, -22, 69, -71, 52, 1, -78, 123, 121, 3, 71, -51, 60, 47, -51, -6, 83, 101, 113, 48, -56, 41, 76, 127, -27, 6, 46, 61, -45, -37, 74, 127, -85, -64, 99, 106, -88, 27, -14, -42, -31, -5, 69, 89, -57, -12, -115, 78, -44, -14, 92, 37, -68, -98, 38, -116, -97, 75, 119, -109, -101, 104, -109, -102, -42, 29, -44, 68, 117, -87, -119, 123, -110, 107, -14, 69, 85, 51, 21, 3, 63, 25, -18, 30, -95, 94, -100, 43, -70, -3, -121, 66, 82, -119, 26, -51, -102, -60, -2, -11, -92, 100, 122, 86, 120, -74, 124, 54, 120, -111, 105, -7, 116, -26, -17, -40, -32, 57, 84, -72, -109, 15, -26, 25, -75, 13, 30, 116, 1, 10, 57, 28, -124, 77, -96, -125, -62, 0, -96, 95, -118, -115, -63, -47, -124, -4, 13, -5, 115, 80, 122, 43, -85, 70, 102, -75, -122, -115, 118, -113, 0, 42, 78, 22, 60, 35, 85, 88, 68, 35, -60, -49, -128, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 7, 116, 101, 115, 116, 114, 115, 97, 0, 0, 0, 20, 0, 0, 0, 6, 106, 101, 114, 111, 101, 110, 0, 0, 0, 6, 110, 111, 98, 111, 100, 121, 0, 0, 0, 0, 88, -19, 76, -72, 0, 0, 0, 0, 88, -19, 84, 23, 0, 0, 0, 0, 0, 0, 0, -126, 0, 0, 0, 21, 112, 101, 114, 109, 105, 116, 45, 88, 49, 49, 45, 102, 111, 114, 119, 97, 114, 100, 105, 110, 103, 0, 0, 0, 0, 0, 0, 0, 23, 112, 101, 114, 109, 105, 116, 45, 97, 103, 101, 110, 116, 45, 102, 111, 114, 119, 97, 114, 100, 105, 110, 103, 0, 0, 0, 0, 0, 0, 0, 22, 112, 101, 114, 109, 105, 116, 45, 112, 111, 114, 116, 45, 102, 111, 114, 119, 97, 114, 100, 105, 110, 103, 0, 0, 0, 0, 0, 0, 0, 10, 112, 101, 114, 109, 105, 116, 45, 112, 116, 121, 0, 0, 0, 0, 0, 0, 0, 14, 112, 101, 114, 109, 105, 116, 45, 117, 115, 101, 114, 45, 114, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 23, 0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 0, 3, 1, 0, 1, 0, 0, 1, 1, 0, -39, -47, -83, 61, 114, -97, -95, -80, -82, -59, -27, -24, 56, 122, -36, 30, -103, 108, -119, -116, 15, 123, 46, 84, -79, 2, -59, -114, -60, -45, 78, -78, 67, 98, -40, 80, -21, -123, -128, -87, 49, -24, -21, -42, -73, 107, 7, 92, -121, -53, 117, 24, -108, 109, 0, 27, 118, -26, -51, 125, 116, -69, 38, -99, -41, -65, -64, 30, 82, -81, 52, -60, 33, -22, -68, -84, 109, -18, -125, 14, 7, 79, 57, 84, 45, 102, -2, 84, 8, 43, -81, 60, 57, -25, 80, 9, -2, -7, 37, -22, -59, -122, -17, 84, 84, -118, -29, -64, 73, 93, -28, 100, -38, 98, -85, -100, 45, -94, 90, -55, -55, 9, 111, 53, -113, -46, 2, 3, 100, -72, 44, 75, 88, 19, 64, -30, 97, 26, -52, -39, 68, -3, -107, 72, 38, 115, 12, 75, 26, -94, -6, 38, 69, -101, 37, 49, 126, -28, -94, -14, 70, -13, -58, -75, -67, 58, 82, -114, 74, 57, -47, 101, -54, 49, 21, 103, -115, -5, -113, -4, 13, 78, -71, -10, -75, -76, 9, 104, -60, -7, -87, -93, 110, -121, -125, -14, -15, 101, 123, 104, 41, -41, -17, 118, 31, 36, 119, 107, -100, 38, -31, 56, -3, 76, 5, 127, 103, 79, -25, 105, 50, -62, -40, 94, 112, -94, -48, -81, -53, 44, 34, -119, 33, 105, -4, -56, -36, 49, 62, 106, -44, 115, 67, -92, -88, -26, 16, 89, -72, -58, -112, -87, -10, 118, -33, -9, 0, 0, 1, 15, 0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 1, 0, -46, -20, -128, -63, 96, -123, 52, 81, 25, 92, -69, -78, -41, -23, 112, 119, -43, 109, -128, 39, 2, -25, 65, -54, 51, 33, -14, -64, -84, -28, -56, -87, 67, 33, -69, 10, 108, 34, -36, 64, 110, -8, 127, 73, -61, 63, -73, 62, -14, 48, 5, 68, 44, 75, 125, -95, -103, -25, -108, -104, 94, 67, -24, 92, -61, -113, -88, 91, 37, -97, -74, 47, -119, -48, -69, -98, 82, -95, 23, 100, 59, 22, 3, -127, 119, 78, 18, -94, -72, -128, -66, 45, 76, -37, -127, 0, 19, -26, 61, 45, 76, -6, -119, -46, -60, -71, -24, 23, -73, 125, -100, -95, -69, -2, 126, -91, 74, 68, 22, 116, 87, -91, 41, 13, -50, 86, -25, -49, -6, -108, 57, 54, 96, -61, 78, 33, -76, 16, 55, 45, -8, 45, -3, 71, -88, -120, 113, 105, 96, -70, -90, 105, -45, -34, -77, -36, 103, 41, 111, 100, -114, -16, 63, -97, 3, 118, 18, 39, 90, -127, 21, 49, -106, -80, 103, 73, -76, 63, 45, -126, -40, -10, -5, -52, -20, 5, 99, -51, 103, 94, -25, 2, 29, 71, -62, 47, -123, 39, -128, -35, 80, 56, -72, 61, -1, 75, -16, 25, -103, -108, 86, 68, -25, 62, -58, -34, 31, -88, 28, 6, -124, 24, -23, 119, 99, 108, -16, 124, 28, -1, -38, -43, 72, 43, -37, 11, 19, -56, 82, 74, 117, -51, 126, -26, -65, -54, -75, -64, -117, -119, 119, -75, 74, 105, -121, 64");
|
||||
byte[] msg = fromString("116, 101, 115, 116, 10");
|
||||
byte[] sig = fromString("0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 1, 0, 121, 15, 34, -11, 125, 124, 91, 62, -47, 36, 77, -44, 13, -29, -36, -100, 65, -87, -67, -64, 11, 31, -24, -35, -53, 74, 18, 65, 12, 119, -33, -105, 65, 21, -55, -21, 89, 49, -97, -98, -48, 49, -20, -75, 88, 3, -84, 105, -51, -83, 92, 117, -118, -63, -5, -119, -69, -25, 22, -62, 83, -106, -112, -3, -104, -95, 89, -72, -59, -6, -18, 114, 113, 43, 32, 31, -11, 27, -39, -58, 75, -42, -30, -48, -81, 15, -92, 71, -85, -45, -101, 101, -60, 54, 7, -69, -120, -69, -3, 125, -52, -29, 44, 120, -50, -29, -38, -50, 7, 51, -68, -16, -87, -114, -105, 22, 31, 104, 110, 89, -50, -35, 124, 79, 26, 82, -26, 121, 47, 80, 26, -80, 65, 6, 35, -90, 87, -30, 63, -24, -82, -79, 79, -88, -26, 55, 120, 91, 119, 56, -26, 44, -106, -89, 118, -105, 88, -98, -46, -115, -6, -69, 121, -63, 100, -55, -126, -106, 79, -6, -56, 26, -26, 96, -113, -95, -121, -16, 44, -122, 6, 90, -56, -7, 99, 91, -67, 33, -53, -48, 63, 85, -29, 95, 35, 110, -89, 67, 84, -116, 85, -85, -21, -26, 19, 15, 36, 99, 22, 80, -99, -41, -119, 89, -36, -26, 113, -53, -39, 94, 74, 5, -96, -96, -121, -56, -61, 62, -14, 20, 69, 10, 80, -102, -68, -19, -59, 30, -76, -2, 89, -118, -48, 104, -46, 45, -63, 112, -110, 68, -108, 48, 61, 73, 103, 62");
|
||||
|
||||
PublicKey hostKey = new Buffer.PlainBuffer(certBytes).readPublicKey();
|
||||
|
||||
Signature signature = new SignatureRSA.FactoryCERT().create();
|
||||
signature.initVerify(hostKey);
|
||||
signature.update(msg, 0, msg.length);
|
||||
|
||||
Assert.assertTrue("RSACERT signature verifies", signature.verify(sig));
|
||||
}
|
||||
|
||||
private byte[] fromString(String string) {
|
||||
String[] split = string.split(", ");
|
||||
byte[] res = new byte[split.length];
|
||||
|
||||
for (int i = 0; i < split.length; i++)
|
||||
res[i] = Byte.parseByte(split[i]);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import java.nio.charset.Charset;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class BaseMacTest {
|
||||
private static final Charset CHARSET = Charset.forName("US-ASCII");
|
||||
@@ -40,11 +41,9 @@ public class BaseMacTest {
|
||||
|
||||
@Test(expected = SSHRuntimeException.class)
|
||||
public void testUnknownAlgorithm() {
|
||||
// hopefully a algorithm with that name won't be created :-)
|
||||
BaseMAC hmac = new BaseMAC("AlgorithmThatDoesNotExist", 20, 20);
|
||||
BaseMAC hmac = new BaseMAC("AlgorithmThatDoesNotExist", 20, 20, false);
|
||||
hmac.init((KEY + "foo").getBytes(CHARSET));
|
||||
hmac.update(PLAIN_TEXT);
|
||||
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
|
||||
fail("Should not initialize a non-existent MAC");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
9
src/test/resources/keyformats/ecdsa_opensshv1
Normal file
9
src/test/resources/keyformats/ecdsa_opensshv1
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR0ImZtMAW5iPIKIQPzfYq9TvnoIpC+
|
||||
kvRY2UvBh28eK0xyNVfr218cdjvWxVrXqdTxW+IqMLWZMX+oL0YxpC+jAAAAsD+6Oow/uj
|
||||
qMAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8goh
|
||||
A/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6
|
||||
MAAAAgXNC11pInVAOd3xNphiHMoISeitf6h1IKbDM+niLrL5kAAAAXYWp2YW5lcnBASGVp
|
||||
bWRhbGwubG9jYWwB
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
src/test/resources/keyformats/ecdsa_opensshv1.pub
Normal file
1
src/test/resources/keyformats/ecdsa_opensshv1.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0wBbmI8gohA/N9ir1O+egikL6S9FjZS8GHbx4rTHI1V+vbXxx2O9bFWtep1PFb4iowtZkxf6gvRjGkL6M= ajvanerp@Heimdall.local
|
||||
49
src/test/resources/keyformats/rsa_opensshv1
Normal file
49
src/test/resources/keyformats/rsa_opensshv1
Normal file
@@ -0,0 +1,49 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
src/test/resources/keyformats/rsa_opensshv1.pub
Normal file
1
src/test/resources/keyformats/rsa_opensshv1.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local
|
||||
30
src/test/resources/keytypes/certificate/test_rsa_max_date
Normal file
30
src/test/resources/keytypes/certificate/test_rsa_max_date
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,5F1AEDBE1E9E4D83EBD9C7B71CD84DB5
|
||||
|
||||
N8SuKdWK3dEA7K6dNodjE++qqXPpVXJlDhiJbrXm1vbs2u+hckzZflK9OQZU1SsY
|
||||
hs6ZyFg/jagdMNEKKrCEK60QMcMg1iPlkMvTwEvQmdWSWtMiTFes9Ec4njUbcbgN
|
||||
qHzixGfWjpTXCRq3QC2Bydq7+njvIEUeLIjTaCIXqwoZoMcBc0N6Aj529gbgDD/i
|
||||
JC0auaSfejSZQm4Ppn+OuO1JKgNkwsjoTrrZjbKHuayYQaioaR5OcAKgSSs5YDdo
|
||||
SJI+PDeIOVb6aMcXwYP5J7C9zzkZU1Ml3dGHk2JMs0Omu2gICRm3mXXmoHRkiBKj
|
||||
2h7EsghiV/Be4lkOF+T3HN19F4G44EIUj7DotbtqNPEFRZa+SSpN3OZmLJQSwBXD
|
||||
A0zek5BxDu7M4VfbLryxNWcHinLe8fBKLQJe3F2/c5ZbMA4Z9098FIL1xWOqvbzS
|
||||
SjiuWhIDxdYVeVe579mUlWjPrMF3NnD4BP9oQlYmbSTxL7B486Ckh7ACFzQhUXVV
|
||||
t2JNZC2FcxM2WU7cHABvGUQNeq/Y2pB5Xq8AmzdpdAs2VZLHtgOvmnPjfo+Q3s1Y
|
||||
rlRe5sCkyn0Ju4M09sMBIQfwszVKa3/l/ZVrG5LCOEHkF+35ex6Gz8ePRlm5jhnU
|
||||
ZQVuLjxbnjnDMaJ0c0IoW7s44cdEDkv3fDzfQHrPzM50oFUN5WOKKWFnT4V1DBLJ
|
||||
9YW8AcNL6bSlgZNkLntn5Wvl3BBKbf5SMmqegfR3eqz50KNwzKKQU8dcIwkJsK2s
|
||||
Z5fNWbydgNDZhrebJL/jpuXpXcvfO0cVMja7y/O0bQ6M37Arpk8bQhTBKZZSaXeF
|
||||
I0Bx3IOxsodWpA9O3Yum8fqqPGrCxsAsP/mWNG0Ov29Myi3MeZ0zgQH283En0Pnx
|
||||
8hvDlw8vNcFe22Jycgg4846FlIWzkT7VfDVxDoYCyjlMTZ2ASys8llH5in4i1w4m
|
||||
j0ZREJE/+evCXBHLoTO96Rgnjt7hp9g62FHJ+ivRlCQVY6sTB03GNv//4v7xGMPF
|
||||
S4eBB9gnGKEEe8zRcOPgUEhUOrFv9cUpdhYz/SLuTZIPPiNwkAMZ6zM77FMsrdWw
|
||||
18wiemOhizCd/JZDlH9COue6EXnr7rexmTp7rUsJOTq8q3rpYmz2s0rGJbqxyxlq
|
||||
BiCjJqN004ZmtFCTD0wCGuNsVLiNyWSspIfzWXkfCyO0SbiH8QjlkUZVr456/5sI
|
||||
cvCQ3ltAFfxR8wMZlfgtT3mEu8JAQCml89yMbttisdfz48XnLeXAzZuZzEr4OllN
|
||||
AMQ8RINlZC2fxqV25jJi+E1da0yvwM9x0NPaM/ZUQqGzMf+dGldcVWdlNFBsthx0
|
||||
wULaGfTYmmn6rBdbeGlt0JKGGL8Ak8+adNSHHxaJuL9W/5wLH41HsE89ZldlTHuI
|
||||
GeHM1lDA/DwO85c4Fk7Ai0Ny44PjdGn27pLNhAfoT2HivF7zMNV+SUXU57iL/qdR
|
||||
yJYGZJyXdreK4qaSCx7BdaK0AFvhBTXW/uRXJCX/XW6zdzdqy5/wkqWt1sWHKTxf
|
||||
Z0CBKWd/se6JCQoixG2Cpo87SeLZsJEscK8eDdLDQwJdKdmIqgw6LddF7RPpq/5D
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb0+Llm+3BKq6n5YoMepI1/Iqp4WnSFYYbpm9Y0jLbJMAAAADAQABAAABAQDF9lt1iAGZWGqNtKR05HRMSyRSG2li0Hq732U2yQ9MmWSAEwltBXILCUrTnkJOWIMYyKY4Y22K+aIWMmnJdE8sehfZ6d+gz20yZ8kHndB7CnKn6oxh6kW5NAGye3kDR808L836U2VxMMgpTH/lBi4909tKf6vAY2qoG/LW4ftFWcf0jU7U8lwlvJ4mjJ9Ld5ObaJOa1h3URHWpiXuSa/JFVTMVAz8Z7h6hXpwruv2HQlKJGs2axP71pGR6Vni2fDZ4kWn5dObv2OA5VLiTD+YZtQ0edAEKORyETaCDwgCgX4qNwdGE/A37c1B6K6tGZrWGjXaPACpOFjwjVVhEI8TPgAAAAAAAAAEAAAABAAAAB3Rlc3Ryc2EAAAAUAAAABmplcm9lbgAAAAZub2JvZHkAAAAAWO1MuP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBANnRrT1yn6GwrsXl6Dh63B6ZbImMD3suVLECxY7E006yQ2LYUOuFgKkx6OvWt2sHXIfLdRiUbQAbdubNfXS7Jp3Xv8AeUq80xCHqvKxt7oMOB085VC1m/lQIK688OedQCf75JerFhu9UVIrjwEld5GTaYqucLaJayckJbzWP0gIDZLgsS1gTQOJhGszZRP2VSCZzDEsaovomRZslMX7kovJG88a1vTpSjko50WXKMRVnjfuP/A1Oufa1tAloxPmpo26Hg/LxZXtoKdfvdh8kd2ucJuE4/UwFf2dP52kywthecKLQr8ssIokhafzI3DE+atRzQ6So5hBZuMaQqfZ23/cAAAEPAAAAB3NzaC1yc2EAAAEA0uyAwWCFNFEZXLuy1+lwd9VtgCcC50HKMyHywKzkyKlDIbsKbCLcQG74f0nDP7c+8jAFRCxLfaGZ55SYXkPoXMOPqFsln7YvidC7nlKhF2Q7FgOBd04SoriAvi1M24EAE+Y9LUz6idLEuegXt32cobv+fqVKRBZ0V6UpDc5W58/6lDk2YMNOIbQQNy34Lf1HqIhxaWC6pmnT3rPcZylvZI7wP58DdhInWoEVMZawZ0m0Py2C2Pb7zOwFY81nXucCHUfCL4UngN1QOLg9/0vwGZmUVkTnPsbeH6gcBoQY6XdjbPB8HP/a1Ugr2wsTyFJKdc1+5r/KtcCLiXe1SmmHQA== adami@computer
|
||||
8
src/test/resources/keytypes/ed25519_aes256cbc.pem
Normal file
8
src/test/resources/keytypes/ed25519_aes256cbc.pem
Normal file
@@ -0,0 +1,8 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABBLQVXV9f
|
||||
Wpw8AL9RTpAr//AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdk
|
||||
jTTBDF1GNz+228nuWprPV+NbQauAAAAAoGHEO7x3fSRBohvrIR52U4XD3uqRnhrPYm01k1
|
||||
f4HHNNv46m92Zw6JKIB9Trrvp0sdMI8MVb79bN45rbn6mvpABtWl6T5TOTyMnKzDfAOx9c
|
||||
FTaasWFmgtgkXOsu5pLrYBAQgCHWbzjjz6KoV1DmD4SAn9Ojf9Oh+YdAEKZcsvklgpu+Kj
|
||||
nzN/DR0jt7Nzep2kNCLAS24QEkvQeATVSDiL8=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
src/test/resources/keytypes/ed25519_aes256cbc.pem.pub
Normal file
1
src/test/resources/keytypes/ed25519_aes256cbc.pem.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local
|
||||
Reference in New Issue
Block a user