mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e26f4a7f | ||
|
|
aa53effce8 | ||
|
|
76e6e572b4 | ||
|
|
2003a9f8c9 | ||
|
|
84a7677a62 | ||
|
|
3bcd3530cf | ||
|
|
a63f9ee8fd | ||
|
|
4be5a98ea3 | ||
|
|
26df2f3c23 | ||
|
|
39b72eed62 | ||
|
|
d55eb6d02e | ||
|
|
265e9d2916 | ||
|
|
0b6552654b | ||
|
|
dabe43dfdc | ||
|
|
0f67fa2541 | ||
|
|
54018a4a81 | ||
|
|
ca81c2eea4 | ||
|
|
048f84b42a | ||
|
|
8ca6451d5d | ||
|
|
5e1be8b1b0 | ||
|
|
bc4da2ea8e | ||
|
|
09fb2b9dc2 | ||
|
|
4045d5a7ef | ||
|
|
d0daa2c12f | ||
|
|
64a2a4f779 | ||
|
|
7cb1f8b11c | ||
|
|
73bc785ab4 | ||
|
|
9d697ede12 | ||
|
|
2b62492caf | ||
|
|
a0f1aa7e2c | ||
|
|
0e981f7656 | ||
|
|
a014567c9e | ||
|
|
8454cf1a0c | ||
|
|
663f118d0f | ||
|
|
47d73a9381 | ||
|
|
c4552d5f3d | ||
|
|
7a884d0938 | ||
|
|
661f63eab7 | ||
|
|
a71a7d7d33 | ||
|
|
d2e0f50d0c | ||
|
|
b41f0acd19 | ||
|
|
a1f501a027 | ||
|
|
fef9cfaf79 | ||
|
|
c67ae242f2 | ||
|
|
823f1e5759 | ||
|
|
f046a41750 | ||
|
|
c161fe26f6 | ||
|
|
ec46a7a489 | ||
|
|
762d088388 | ||
|
|
99c85672b8 | ||
|
|
28d57840ab | ||
|
|
2984291d84 | ||
|
|
bdbd9d7eb5 | ||
|
|
9ac55de26c | ||
|
|
a9928c2882 | ||
|
|
c6c9a3f6a8 | ||
|
|
0918bc626f | ||
|
|
aa7748395d | ||
|
|
cf077e2a4f | ||
|
|
c58c7c7c60 | ||
|
|
0b548d9d13 | ||
|
|
eb1629f250 | ||
|
|
8856aaea61 | ||
|
|
1f6615b57a | ||
|
|
e5084ed8db | ||
|
|
3729119e23 | ||
|
|
aed3decf1d | ||
|
|
303c03061c | ||
|
|
5e3a08a637 | ||
|
|
d0800058e8 | ||
|
|
ad9c2d5411 | ||
|
|
ed65176b68 | ||
|
|
28f3280a84 | ||
|
|
d69f722908 | ||
|
|
1d7cb8c2c6 | ||
|
|
6ad6242ed1 | ||
|
|
3310530d42 | ||
|
|
3685f9dc36 | ||
|
|
f8cad120a6 | ||
|
|
56dd4e4af4 | ||
|
|
9f8cf1f298 | ||
|
|
a51270791d | ||
|
|
d43fc4551e | ||
|
|
93bf6c0089 | ||
|
|
7b535a8db3 | ||
|
|
9d4f8fc46a | ||
|
|
2b21ec6032 | ||
|
|
8e15a8bd7d | ||
|
|
531eb97767 | ||
|
|
e36fd0fb3d | ||
|
|
382321deca | ||
|
|
7b75fb3d53 | ||
|
|
4d84d3f67c | ||
|
|
8eb7d1a2ad | ||
|
|
a03fa9ac63 | ||
|
|
bcb15e6ccd | ||
|
|
d85b22fe8d | ||
|
|
f4b71941a3 | ||
|
|
636f896850 | ||
|
|
56c0baf814 | ||
|
|
edfb069f2a | ||
|
|
65b3003e72 | ||
|
|
fbee0b3956 | ||
|
|
fd60139b98 | ||
|
|
0b397bc3d7 | ||
|
|
40f956b4b6 | ||
|
|
ef3f7a2eaf | ||
|
|
8134113510 | ||
|
|
c883c87963 | ||
|
|
920537dac9 | ||
|
|
356ec9ed08 | ||
|
|
aa47b0c5f7 | ||
|
|
d3ed3cfe0f | ||
|
|
786734ce26 | ||
|
|
9cb5bf4e10 | ||
|
|
0e3f7c2bbf | ||
|
|
66d4b34eba | ||
|
|
aafb9942a3 | ||
|
|
d1dff550ce | ||
|
|
ac2720becd | ||
|
|
48dd1fdc41 | ||
|
|
9826a71d2b | ||
|
|
936eb26e9e | ||
|
|
9438157b93 | ||
|
|
7d326e5ae4 | ||
|
|
f038b5ce2b | ||
|
|
20879a4aa5 | ||
|
|
516abb0282 | ||
|
|
0ad51709c2 | ||
|
|
c9c68f019e | ||
|
|
fc75f9796c | ||
|
|
61af500c3e | ||
|
|
56553ea086 | ||
|
|
86e6631b1e | ||
|
|
b6f437a932 | ||
|
|
9e3b9f7c24 | ||
|
|
766ab916ee | ||
|
|
cdca43a848 | ||
|
|
3ce7c2ebfb | ||
|
|
ca4e0bf2d7 | ||
|
|
2ca2bbd633 | ||
|
|
256e65dea4 | ||
|
|
1feb7fe9a6 | ||
|
|
d95b4db930 | ||
|
|
677f482a69 | ||
|
|
179b30ef4e | ||
|
|
f59bbccc5f | ||
|
|
bf34072c3a | ||
|
|
771751ca4c | ||
|
|
968d4284a0 | ||
|
|
63927a3e2b | ||
|
|
ac262f8086 | ||
|
|
6e56cd9d0a | ||
|
|
2f6025d9ba | ||
|
|
275e98e55b | ||
|
|
655d070571 | ||
|
|
c9775ca2c7 | ||
|
|
a2fb4fbd98 | ||
|
|
6185ac4db8 | ||
|
|
e420593fa9 | ||
|
|
68b924863e | ||
|
|
613ace1864 | ||
|
|
78e0ecd264 | ||
|
|
64085e62f4 | ||
|
|
8c7d2fa8d0 | ||
|
|
766d292bad | ||
|
|
a40957fffc | ||
|
|
8ffd852e67 | ||
|
|
b90be512e7 | ||
|
|
c0d49cf6b3 | ||
|
|
1b5b2b25b7 | ||
|
|
1dad19ca6e | ||
|
|
90fa26925d | ||
|
|
9425300262 | ||
|
|
f2bfe9bfcf | ||
|
|
71498ad961 | ||
|
|
7b8b1cfdf5 | ||
|
|
3f29879eca | ||
|
|
79c1ae2bb0 | ||
|
|
819d411cf1 | ||
|
|
6579f6f710 | ||
|
|
cf5830eda5 | ||
|
|
36ad389ccf | ||
|
|
f63a88ec9f | ||
|
|
7379a89268 | ||
|
|
219901211e | ||
|
|
8c1329036a | ||
|
|
733c19350c | ||
|
|
e5ec84c06a | ||
|
|
ed0156c985 | ||
|
|
6321685881 | ||
|
|
f6b4d47945 | ||
|
|
d198ef121c | ||
|
|
7786468875 | ||
|
|
0847e8460a | ||
|
|
62b8726807 | ||
|
|
a6af27ae91 | ||
|
|
628cbf5eba | ||
|
|
e134e00574 | ||
|
|
1caa7ac722 | ||
|
|
4183776adb | ||
|
|
791f112752 | ||
|
|
233f3765c9 | ||
|
|
5f292d398f | ||
|
|
ba347f927d | ||
|
|
6f9ecf69e4 | ||
|
|
e78ae4dbeb | ||
|
|
618f2fd111 | ||
|
|
8503046302 | ||
|
|
c6cde27e4b | ||
|
|
113aa0aebd | ||
|
|
e7c50165c7 | ||
|
|
09616c4834 | ||
|
|
df710d8dc9 | ||
|
|
df82774ea3 | ||
|
|
caa6cca665 | ||
|
|
9a5ccefb5d |
8
.bettercodehub.yml
Normal file
8
.bettercodehub.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
exclude:
|
||||
- /build-publishing.gradle
|
||||
- /build.gradle
|
||||
- /settings.gradle
|
||||
component_depth: 1
|
||||
languages:
|
||||
- groovy
|
||||
- java
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -10,8 +10,13 @@
|
||||
.settings/
|
||||
|
||||
# Output dirs
|
||||
out/
|
||||
target/
|
||||
classes/
|
||||
build/
|
||||
docs/
|
||||
.gradle/
|
||||
sshj.jar
|
||||
|
||||
|
||||
# MacOS X
|
||||
.DS_Store
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -1,2 +1,30 @@
|
||||
language: java
|
||||
sudo: false
|
||||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- openjdk8
|
||||
- oraclejdk9
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
before_install:
|
||||
- pip install --user codecov
|
||||
|
||||
script:
|
||||
- ./gradlew check
|
||||
- ./gradlew integrationTest
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
|
||||
67
README.adoc
67
README.adoc
@@ -1,10 +1,14 @@
|
||||
= sshj - SSHv2 library for Java
|
||||
Jeroen van Erp
|
||||
:sshj_groupid: com.hierynomus
|
||||
:sshj_version: 0.17.2
|
||||
:sshj_version: 0.24.0
|
||||
:source-highlighter: pygments
|
||||
|
||||
image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"]
|
||||
image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"]
|
||||
image:https://api.codacy.com/project/badge/Grade/14a0a316bb9149739b5ea26dbfa8da8a["Codacy code quality", link="https://www.codacy.com/app/jeroen_2/sshj?utm_source=github.com&utm_medium=referral&utm_content=hierynomus/sshj&utm_campaign=Badge_Grade"]
|
||||
image:https://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"]
|
||||
|
||||
@@ -36,7 +40,7 @@ If you're building your project using Maven, you can add the following dependenc
|
||||
If your project is built using another build tool that uses the Maven Central repository, translate this dependency into the format used by your build tool.
|
||||
|
||||
== Building SSHJ
|
||||
. Clone the Overthere repository.
|
||||
. Clone the SSHJ repository.
|
||||
. Ensure you have Java6 installed with the http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html[Unlimited strength Java Cryptography Extensions (JCE)].
|
||||
. Run the command `./gradlew clean build`.
|
||||
|
||||
@@ -65,20 +69,25 @@ ciphers::
|
||||
SSHJ also supports the following extended (non official) ciphers: `camellia{128,192,256}-{cbc,ctr}`, `camellia{128,192,256}-{cbc,ctr}@openssh.org`
|
||||
|
||||
key exchange::
|
||||
`diffie-hellman-group1-sha1`, `diffie-hellman-group14-sha1`, `diffie-hellman-group-exchange-sha1`, `diffie-hellman-group-exchange-sha256`,
|
||||
`diffie-hellman-group1-sha1`, `diffie-hellman-group14-sha1`,
|
||||
`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`
|
||||
|
||||
signatures::
|
||||
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ssh-ed25519`
|
||||
`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-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`, `hmac-ripemd160`
|
||||
|
||||
compression::
|
||||
`zlib` and `zlib@openssh.com` (delayed zlib)
|
||||
|
||||
private key files::
|
||||
`pkcs8` encoded (what openssh uses)
|
||||
`pkcs5`, `pkcs8`, `openssh-key-v1`, `ssh-rsa-cert-v01@openssh.com`, `ssh-dsa-cert-v01@openssh.com`
|
||||
|
||||
If you need something that is not included, it shouldn't be too hard to add (do contribute it!)
|
||||
|
||||
@@ -98,6 +107,52 @@ Google Group: http://groups.google.com/group/sshj-users
|
||||
Fork away!
|
||||
|
||||
== Release history
|
||||
SSHJ 0.24.0 (2018-??-??)::
|
||||
* Added support for hmac-ripemd160
|
||||
|
||||
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
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/365[#365]: Added support for new-style OpenSSH fingerprints of server keys
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/356[#356]: Fixed key type detection for ECDSA public keys
|
||||
* Made SSHJ Java9 compatible
|
||||
SSHJ 0.22.0 (2017-08-24)::
|
||||
* Fixed https://github.com/hierynomus/sshj/pulls/341[#341]: Fixed path walking during recursive copy
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/338[#338]: Added ConsolePasswordFinder to read password from stdin
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/336[#336]: Added support for ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 signatures
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/331[#331]: Added support for wildcards in known_hosts file
|
||||
SSHJ 0.21.1 (2017-04-25)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/322[#322]: Fix regression from 40f956b (invalid length parameter on outputstream)
|
||||
SSHJ 0.21.0 (2017-04-14)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/319[#319]: Added support for `ssh-rsa-cert-v01@openssh.com` and `ssh-dsa-cert-v01@openssh.com` certificate key files
|
||||
* Upgraded Gradle to 3.4.1
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/305[#305]: Added support for custom string encoding
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/312[#312]: Upgraded BouncyCastle to 1.56
|
||||
SSHJ 0.20.0 (2017-02-09)::
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/294[#294]: Reference ED25519 by constant instead of name
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/293[#293], https://github.com/hierynomus/sshj/pulls/295[#295] and https://github.com/hierynomus/sshj/pulls/301[#301]: Fixed OSGi packaging
|
||||
* Added new Diffie Hellman groups 15-18 for stronger KeyExchange algorithms
|
||||
SSHJ 0.19.1 (2016-12-30)::
|
||||
* Enabled PKCS5 Key files in DefaultConfig
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/291[#291]: Fixed sshj.properties loading and chained exception messages
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/284[#284]: Correctly catch interrupt in keepalive thread
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/292[#292]: Pass the configured RandomFactory to Diffie Hellmann KEX
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/256[#256]: SSHJ now builds if no git repository present
|
||||
* LocalPortForwarder now correctly interrupts its own thread on close()
|
||||
SSHJ 0.19.0 (2016-11-25)::
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/276[#276]: Add support for ed-25519 and new OpenSSH key format
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/280[#280]: Read version from a generated sshj.properties file to correctly output version during negotiation
|
||||
SSHJ 0.18.0 (2016-09-30)::
|
||||
* Fixed Android compatibility
|
||||
* Upgrade to Gradle 3.0
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/271[#271]: Load known_hosts without requiring BouncyCastle
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/269[#269]: Brought back Java6 support by popular demand
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/267[#267]: Added support for per connection logging (Fixes https://github.com/hierynomus/sshj/issues/264[#264])
|
||||
* Merged https://github.com/hierynomus/sshj/pulls/262[#262], https://github.com/hierynomus/sshj/pulls/265[#265] and https://github.com/hierynomus/sshj/pulls/266[#266]: Added PKCS5 key file support
|
||||
* Fixed toString of sftp FileAttributes (Fixes https://github.com/hierynomus/sshj/pulls/258[#258])
|
||||
* Fixed https://github.com/hierynomus/sshj/issues/255[#255]: No longer depending on 'privately marked' classes in `net.i2p.crypto.eddsa.math` package, fixes OSGI dependencies
|
||||
SSHJ 0.17.2 (2016-07-07)::
|
||||
* Treating SSH Server identification line ending in '\n' instead of '\r\n' leniently.
|
||||
SSHJ 0.17.1 (2016-07-06)::
|
||||
* Improved parsing of the SSH Server identification. Too long header lines now no longer break the protocol.
|
||||
SSHJ 0.17.0 (2016-07-05)::
|
||||
|
||||
427
build.gradle
427
build.gradle
@@ -1,211 +1,284 @@
|
||||
import java.text.SimpleDateFormat
|
||||
import com.bmuschko.gradle.docker.tasks.container.*
|
||||
import com.bmuschko.gradle.docker.tasks.image.*
|
||||
|
||||
plugins {
|
||||
id "java"
|
||||
id "groovy"
|
||||
id "maven"
|
||||
id "idea"
|
||||
id "signing"
|
||||
id "osgi"
|
||||
id "org.ajoberstar.release-opinion" version "1.4.2"
|
||||
id "com.github.hierynomus.license" version "0.12.1"
|
||||
id "java"
|
||||
id "groovy"
|
||||
id "jacoco"
|
||||
id "osgi"
|
||||
id "maven-publish"
|
||||
id "com.bmuschko.docker-remote-api" version "3.2.1"
|
||||
id 'pl.allegro.tech.build.axion-release' version '1.8.1'
|
||||
id "com.github.hierynomus.license" version "0.12.1"
|
||||
id "com.jfrog.bintray" version "1.7"
|
||||
id 'ru.vyarus.java-lib' version '1.0.5'
|
||||
// id 'ru.vyarus.pom' version '1.0.3'
|
||||
id 'ru.vyarus.github-info' version '1.1.0'
|
||||
id 'ru.vyarus.animalsniffer' version '1.4.2'
|
||||
}
|
||||
|
||||
group = "com.hierynomus"
|
||||
|
||||
defaultTasks "build"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://dl.bintray.com/mockito/maven/"
|
||||
}
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
targetCompatibility = 1.7
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
configurations.compile.transitive = false
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
def bouncycastleVersion = "1.57"
|
||||
|
||||
dependencies {
|
||||
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||
|
||||
compile "org.slf4j:slf4j-api:1.7.7"
|
||||
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
|
||||
compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
|
||||
compile "com.jcraft:jzlib:1.1.3"
|
||||
|
||||
compile "net.i2p.crypto:eddsa:0.2.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'
|
||||
|
||||
}
|
||||
|
||||
license {
|
||||
mapping {
|
||||
java = 'SLASHSTAR_STYLE'
|
||||
}
|
||||
header rootProject.file('LICENSE_HEADER')
|
||||
strictCheck true
|
||||
header rootProject.file('LICENSE_HEADER')
|
||||
strictCheck true
|
||||
mapping {
|
||||
java = 'SLASHSTAR_STYLE'
|
||||
}
|
||||
excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java'])
|
||||
}
|
||||
|
||||
release {
|
||||
grgit = org.ajoberstar.grgit.Grgit.open(project.projectDir)
|
||||
scmVersion {
|
||||
tag {
|
||||
prefix = 'v'
|
||||
versionSeparator = ''
|
||||
}
|
||||
hooks {
|
||||
pre 'fileUpdate', [file: 'README.adoc', pattern: { v, c -> /:sshj_version: .*/}, replacement: { v, c -> ":sshj_version: $v" }]
|
||||
pre 'commit'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
include "**/*Test.*"
|
||||
include "**/*Spec.*"
|
||||
if (!project.hasProperty("allTests")) {
|
||||
useJUnit {
|
||||
excludeCategories 'com.hierynomus.sshj.test.SlowTests'
|
||||
excludeCategories 'com.hierynomus.sshj.test.KnownFailingTests'
|
||||
}
|
||||
}
|
||||
|
||||
afterSuite { descriptor, result ->
|
||||
if (descriptor.className != null) {
|
||||
def indicator = "\u001B[32m✓\u001b[0m"
|
||||
if (result.failedTestCount > 0) {
|
||||
indicator = "\u001B[31m✘\u001b[0m"
|
||||
}
|
||||
logger.lifecycle("$indicator Test ${descriptor.name}; Executed: ${result.testCount}/\u001B[32m${result.successfulTestCount}\u001B[0m/\u001B[31m${result.failedTestCount}\u001B[0m")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bouncycastleVersion = "1.51"
|
||||
|
||||
dependencies {
|
||||
compile "org.slf4j:slf4j-api:1.7.7"
|
||||
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
|
||||
compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
|
||||
compile "com.jcraft:jzlib:1.1.3"
|
||||
|
||||
compile "net.vrallev.ecc:ecc-25519-java:1.0.1"
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
testCompile "org.apache.sshd:sshd-core:1.1.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'
|
||||
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
instruction "Bundle-Description", "SSHv2 library for Java"
|
||||
instruction "Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
instruction "Import-Package", "!net.schmizz.*"
|
||||
instruction "Import-Package", "javax.crypto*"
|
||||
instruction "Import-Package", "net.i2p*"
|
||||
instruction "Import-Package", "com.jcraft.jzlib*;version=\"[1.1,2)\";resolution:=optional"
|
||||
instruction "Import-Package", "org.slf4j*;version=\"[1.7,5)\""
|
||||
instruction "Import-Package", "org.bouncycastle*"
|
||||
instruction "Import-Package", "*"
|
||||
instruction "Export-Package", "net.schmizz.*"
|
||||
}
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
manifest {
|
||||
attributes(
|
||||
// Add the needed OSGI attributes
|
||||
"Bundle-ManifestVersion": "2",
|
||||
"Bundle-Name": "${project.jar.manifest.name} Source",
|
||||
"Bundle-Version": project.jar.manifest.version,
|
||||
"Eclipse-SourceBundle": "${project.jar.manifest.symbolicName};version=\"${project.jar.manifest.version}\";roots:=\".\"",
|
||||
"Bundle-SymbolicName": "${project.jar.manifest.symbolicName}.source"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar, sourcesJar
|
||||
}
|
||||
|
||||
signing {
|
||||
required { !version.toString().contains("SNAPSHOT") && gradle.taskGraph.hasTask("uploadArchives") }
|
||||
sign configurations.archives
|
||||
}
|
||||
project.version = scmVersion.version
|
||||
|
||||
// This disables the pedantic doclint feature of JDK8
|
||||
if (JavaVersion.current().isJava8Compatible()) {
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
||||
}
|
||||
|
||||
uploadArchives {
|
||||
if (project.hasProperty('sonatypeUsername')) {
|
||||
repositories.mavenDeployer {
|
||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
||||
task writeSshjVersionProperties {
|
||||
doLast {
|
||||
project.file("${project.buildDir}/resources/main").mkdirs()
|
||||
project.file("${project.buildDir}/resources/main/sshj.properties").withWriter { w ->
|
||||
w.append("sshj.version=${version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration = configurations.archives
|
||||
jar.dependsOn writeSshjVersionProperties
|
||||
jar {
|
||||
manifest {
|
||||
// please see http://bnd.bndtools.org/chapters/390-wrapping.html
|
||||
instruction "Bundle-Description", "SSHv2 library for Java"
|
||||
instruction "Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
instruction "Import-Package", "!net.schmizz.*"
|
||||
instruction "Import-Package", "!com.hierynomus.sshj.*"
|
||||
instruction "Import-Package", "javax.crypto*"
|
||||
instruction "Import-Package", "!net.i2p.crypto.eddsa.math"
|
||||
instruction "Import-Package", "net.i2p*"
|
||||
instruction "Import-Package", "com.jcraft.jzlib*;version=\"[1.1,2)\";resolution:=optional"
|
||||
instruction "Import-Package", "org.slf4j*;version=\"[1.7,5)\""
|
||||
instruction "Import-Package", "org.bouncycastle*;resolution:=optional"
|
||||
instruction "Import-Package", "org.bouncycastle.jce.provider;resolution:=optional"
|
||||
instruction "Import-Package", "*"
|
||||
instruction "Export-Package", "com.hierynomus.sshj.*;version=\"${project.jar.manifest.version}\""
|
||||
instruction "Export-Package", "net.schmizz.*;version=\"${project.jar.manifest.version}\""
|
||||
}
|
||||
}
|
||||
|
||||
repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') {
|
||||
authentication(userName: sonatypeUsername, password: sonatypePassword)
|
||||
}
|
||||
snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') {
|
||||
authentication(userName: sonatypeUsername, password: sonatypePassword)
|
||||
}
|
||||
sourcesJar {
|
||||
manifest {
|
||||
attributes(
|
||||
// Add the needed OSGI attributes
|
||||
"Bundle-ManifestVersion": "2",
|
||||
"Bundle-Name": "${project.jar.manifest.name} Source",
|
||||
"Bundle-Version": project.jar.manifest.version,
|
||||
"Eclipse-SourceBundle": "${project.jar.manifest.symbolicName};version=\"${project.jar.manifest.version}\";roots:=\".\"",
|
||||
"Bundle-SymbolicName": "${project.jar.manifest.symbolicName}.source"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pom.project {
|
||||
name "sshj"
|
||||
description "SSHv2 library for Java"
|
||||
url "https://github.com/hierynomus/sshj"
|
||||
inceptionYear "2009"
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
issueManagement {
|
||||
system "github"
|
||||
url "https://github.com/hierynomus/sshj/issues"
|
||||
}
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
groovy {
|
||||
compileClasspath += sourceSets.main.output + sourceSets.test.output
|
||||
runtimeClasspath += sourceSets.main.output + sourceSets.test.output
|
||||
srcDir file('src/itest/groovy')
|
||||
}
|
||||
resources.srcDir file('src/itest/resources')
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
connection "scm:git:git://github.com/hierynomus/sshj.git"
|
||||
developerConnection "scm:git:git@github.com:hierynomus/sshj.git"
|
||||
url "https://github.com/hierynomus/sshj.git"
|
||||
}
|
||||
task integrationTest(type: Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name "Apache 2"
|
||||
url "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
distribution "repo"
|
||||
}
|
||||
}
|
||||
tasks.withType(Test) {
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
include "**/*Test.*"
|
||||
include "**/*Spec.*"
|
||||
if (!project.hasProperty("allTests")) {
|
||||
useJUnit {
|
||||
excludeCategories 'com.hierynomus.sshj.test.SlowTests'
|
||||
excludeCategories 'com.hierynomus.sshj.test.KnownFailingTests'
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id "hierynomus"
|
||||
name "Jeroen van Erp"
|
||||
email "jeroen@javadude.nl"
|
||||
roles {
|
||||
role "Lead developer"
|
||||
}
|
||||
}
|
||||
developer {
|
||||
id "shikhar"
|
||||
name "Shikhar Bhushan"
|
||||
email "shikhar@schmizz.net"
|
||||
url "http://schmizz.net"
|
||||
roles {
|
||||
role "Previous lead developer"
|
||||
}
|
||||
}
|
||||
developer {
|
||||
id "iterate"
|
||||
name "David Kocher"
|
||||
email "dkocher@iterate.ch"
|
||||
organization "iterage GmbH"
|
||||
organizationUrl "https://iterate.ch"
|
||||
roles {
|
||||
role "Developer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
afterSuite { descriptor, result ->
|
||||
if (descriptor.className != null) {
|
||||
def indicator = "\u001B[32m✓\u001b[0m"
|
||||
if (result.failedTestCount > 0) {
|
||||
indicator = "\u001B[31m✘\u001b[0m"
|
||||
}
|
||||
logger.lifecycle("$indicator Test ${descriptor.name}; Executed: ${result.testCount}/\u001B[32m${result.successfulTestCount}\u001B[0m/\u001B[31m${result.failedTestCount}\u001B[0m")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.tasks.compileGroovy.onlyIf { false }
|
||||
|
||||
github {
|
||||
user 'hierynomus'
|
||||
license 'Apache'
|
||||
}
|
||||
|
||||
pom {
|
||||
description "SSHv2 library for Java"
|
||||
url "https://github.com/hierynomus/sshj"
|
||||
inceptionYear "2009"
|
||||
developers {
|
||||
developer {
|
||||
id "hierynomus"
|
||||
name "Jeroen van Erp"
|
||||
email "jeroen@javadude.nl"
|
||||
roles {
|
||||
role "Lead developer"
|
||||
}
|
||||
}
|
||||
developer {
|
||||
id "shikhar"
|
||||
name "Shikhar Bhushan"
|
||||
email "shikhar@schmizz.net"
|
||||
url "http://schmizz.net"
|
||||
roles {
|
||||
role "Previous lead developer"
|
||||
}
|
||||
}
|
||||
developer {
|
||||
id "iterate"
|
||||
name "David Kocher"
|
||||
email "dkocher@iterate.ch"
|
||||
organization "iterage GmbH"
|
||||
organizationUrl "https://iterate.ch"
|
||||
roles {
|
||||
role "Developer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty("bintrayUsername") && project.hasProperty("bintrayApiKey")) {
|
||||
bintray {
|
||||
user = project.property("bintrayUsername")
|
||||
key = project.property("bintrayApiKey")
|
||||
publish = true
|
||||
publications = ["maven"]
|
||||
pkg {
|
||||
repo = "maven"
|
||||
name = project.name
|
||||
licenses = ["Apache-2.0"]
|
||||
vcsUrl = "https://github.com/hierynomus/sshj.git"
|
||||
labels = ["ssh", "sftp", "secure-shell", "network", "file-transfer"]
|
||||
githubRepo = "hierynomus/sshj"
|
||||
version {
|
||||
name = project.version.toString()
|
||||
vcsTag = "v${project.version}"
|
||||
released = new SimpleDateFormat('yyyy-MM-dd\'T\'HH:mm:ss.SSSZZ').format(new Date())
|
||||
gpg {
|
||||
sign = true
|
||||
passphrase = project.property("signing.password")
|
||||
}
|
||||
mavenCentralSync {
|
||||
sync = true
|
||||
user = project.property("sonatypeUsername")
|
||||
password = project.property("sonatypePassword")
|
||||
close = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled true
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileGroovy.onlyIf { false }
|
||||
tasks.release.dependsOn 'build', 'uploadArchives'
|
||||
|
||||
task buildItestImage(type: DockerBuildImage) {
|
||||
inputDir = file('src/itest/docker-image')
|
||||
tag = 'sshj/sshd-itest'
|
||||
}
|
||||
|
||||
task createItestContainer(type: DockerCreateContainer) {
|
||||
dependsOn buildItestImage
|
||||
targetImageId { buildItestImage.getImageId() }
|
||||
portBindings = ['2222:22']
|
||||
}
|
||||
|
||||
task startItestContainer(type: DockerStartContainer) {
|
||||
dependsOn createItestContainer
|
||||
targetContainerId { createItestContainer.getContainerId() }
|
||||
}
|
||||
|
||||
task stopItestContainer(type: DockerStopContainer) {
|
||||
targetContainerId { createItestContainer.getContainerId() }
|
||||
}
|
||||
|
||||
project.tasks.integrationTest.dependsOn(startItestContainer)
|
||||
project.tasks.integrationTest.finalizedBy(stopItestContainer)
|
||||
|
||||
project.tasks.release.dependsOn([project.tasks.integrationTest, project.tasks.build])
|
||||
project.tasks.release.finalizedBy(project.tasks.bintrayUpload)
|
||||
project.tasks.jacocoTestReport.dependsOn(project.tasks.test)
|
||||
project.tasks.check.dependsOn(project.tasks.jacocoTestReport)
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<groupId>com.hierynomus</groupId>
|
||||
<artifactId>sshj-examples</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.14.0</version>
|
||||
<version>0.19.1</version>
|
||||
|
||||
<name>sshj-examples</name>
|
||||
<description>Examples for SSHv2 library for Java</description>
|
||||
@@ -55,7 +55,7 @@
|
||||
<dependency>
|
||||
<groupId>com.hierynomus</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<version>0.15.0</version>
|
||||
<version>0.19.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -3,14 +3,11 @@ package net.schmizz.sshj.examples;
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.DefaultConfig;
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session.Command;
|
||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** This examples demonstrates how to setup keep-alive to detect connection dropping. */
|
||||
public class KeepAlive {
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
|
||||
/** A very rudimentary psuedo-terminal based on console I/O. */
|
||||
class RudimentaryPTY {
|
||||
@@ -33,18 +34,18 @@ class RudimentaryPTY {
|
||||
|
||||
final Shell shell = session.startShell();
|
||||
|
||||
new StreamCopier(shell.getInputStream(), System.out)
|
||||
new StreamCopier(shell.getInputStream(), System.out, LoggerFactory.DEFAULT)
|
||||
.bufSize(shell.getLocalMaxPacketSize())
|
||||
.spawn("stdout");
|
||||
|
||||
new StreamCopier(shell.getErrorStream(), System.err)
|
||||
new StreamCopier(shell.getErrorStream(), System.err, LoggerFactory.DEFAULT)
|
||||
.bufSize(shell.getLocalMaxPacketSize())
|
||||
.spawn("stderr");
|
||||
|
||||
// Now make System.in act as stdin. To exit, hit Ctrl+D (since that results in an EOF on System.in)
|
||||
// This is kinda messy because java only allows console input after you hit return
|
||||
// But this is just an example... a GUI app could implement a proper PTY
|
||||
new StreamCopier(System.in, shell.getOutputStream())
|
||||
new StreamCopier(System.in, shell.getOutputStream(), LoggerFactory.DEFAULT)
|
||||
.bufSize(shell.getRemoteMaxPacketSize())
|
||||
.copy();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.schmizz.sshj.common.StreamCopier;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session.Command;
|
||||
import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -42,8 +43,8 @@ public class X11 {
|
||||
|
||||
final Command cmd = sess.exec("/usr/X11/bin/xcalc");
|
||||
|
||||
new StreamCopier(cmd.getInputStream(), System.out).spawn("stdout");
|
||||
new StreamCopier(cmd.getErrorStream(), System.err).spawn("stderr");
|
||||
new StreamCopier(cmd.getInputStream(), System.out, LoggerFactory.DEFAULT).spawn("stdout");
|
||||
new StreamCopier(cmd.getErrorStream(), System.err, LoggerFactory.DEFAULT).spawn("stderr");
|
||||
|
||||
// Wait for session & X11 channel to get closed
|
||||
ssh.getConnection().join();
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
|
||||
|
||||
17
src/itest/docker-image/Dockerfile
Normal file
17
src/itest/docker-image/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM sickp/alpine-sshd:7.5
|
||||
|
||||
ADD id_rsa.pub /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/sshd_config /etc/ssh/sshd_config
|
||||
|
||||
RUN \
|
||||
echo "root:smile" | chpasswd && \
|
||||
adduser -D -s /bin/ash sshj && \
|
||||
passwd -u sshj && \
|
||||
chmod 600 /home/sshj/.ssh/authorized_keys && \
|
||||
chmod 600 /etc/ssh/ssh_host_ecdsa_key && \
|
||||
chmod 644 /etc/ssh/ssh_host_ecdsa_key.pub && \
|
||||
chown -R sshj:sshj /home/sshj
|
||||
|
||||
1
src/itest/docker-image/id_rsa.pub
Normal file
1
src/itest/docker-image/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase
|
||||
5
src/itest/docker-image/test-container/ssh_host_ecdsa_key
Normal file
5
src/itest/docker-image/test-container/ssh_host_ecdsa_key
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIOpOBFjqe0hjK/hs4WZ3dZqnzanq1L3/JbvV1TCkbe4ToAoGCCqGSM49
|
||||
AwEHoUQDQgAEVzkrS7Yj0nXML7A3mE08YDthfBR/ZbyYJDIq1vTzcqs6KTaCT529
|
||||
swNXWLHO+mbHviZcRiI57ULXHZ1emom/Jw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFc5K0u2I9J1zC+wN5hNPGA7YXwUf2W8mCQyKtb083KrOik2gk+dvbMDV1ixzvpmx74mXEYiOe1C1x2dXpqJvyc= root@404b27be2bf4
|
||||
132
src/itest/docker-image/test-container/sshd_config
Normal file
132
src/itest/docker-image/test-container/sshd_config
Normal file
@@ -0,0 +1,132 @@
|
||||
# $OpenBSD: sshd_config,v 1.101 2017/03/14 07:19:07 djm Exp $
|
||||
|
||||
# This is the sshd server system-wide configuration file. See
|
||||
# sshd_config(5) for more information.
|
||||
|
||||
# This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin
|
||||
|
||||
# The strategy used for options in the default sshd_config shipped with
|
||||
# OpenSSH is to specify options with their default value where
|
||||
# possible, but leave them commented. Uncommented options override the
|
||||
# default value.
|
||||
|
||||
#Port 22
|
||||
#AddressFamily any
|
||||
#ListenAddress 0.0.0.0
|
||||
#ListenAddress ::
|
||||
|
||||
#HostKey /etc/ssh/ssh_host_rsa_key
|
||||
#HostKey /etc/ssh/ssh_host_dsa_key
|
||||
#HostKey /etc/ssh/ssh_host_ecdsa_key
|
||||
#HostKey /etc/ssh/ssh_host_ed25519_key
|
||||
|
||||
# Ciphers and keying
|
||||
#RekeyLimit default none
|
||||
|
||||
# Logging
|
||||
#SyslogFacility AUTH
|
||||
#LogLevel INFO
|
||||
|
||||
# Authentication:
|
||||
|
||||
#LoginGraceTime 2m
|
||||
PermitRootLogin yes
|
||||
#StrictModes yes
|
||||
#MaxAuthTries 6
|
||||
#MaxSessions 10
|
||||
|
||||
#PubkeyAuthentication yes
|
||||
|
||||
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
|
||||
# but this is overridden so installations will only check .ssh/authorized_keys
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
|
||||
#AuthorizedPrincipalsFile none
|
||||
|
||||
#AuthorizedKeysCommand none
|
||||
#AuthorizedKeysCommandUser nobody
|
||||
|
||||
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
|
||||
#HostbasedAuthentication no
|
||||
# Change to yes if you don't trust ~/.ssh/known_hosts for
|
||||
# HostbasedAuthentication
|
||||
#IgnoreUserKnownHosts no
|
||||
# Don't read the user's ~/.rhosts and ~/.shosts files
|
||||
#IgnoreRhosts yes
|
||||
|
||||
# To disable tunneled clear text passwords, change to no here!
|
||||
#PasswordAuthentication yes
|
||||
#PermitEmptyPasswords no
|
||||
|
||||
# Change to no to disable s/key passwords
|
||||
#ChallengeResponseAuthentication yes
|
||||
|
||||
# Kerberos options
|
||||
#KerberosAuthentication no
|
||||
#KerberosOrLocalPasswd yes
|
||||
#KerberosTicketCleanup yes
|
||||
#KerberosGetAFSToken no
|
||||
|
||||
# GSSAPI options
|
||||
#GSSAPIAuthentication no
|
||||
#GSSAPICleanupCredentials yes
|
||||
|
||||
# Set this to 'yes' to enable PAM authentication, account processing,
|
||||
# and session processing. If this is enabled, PAM authentication will
|
||||
# be allowed through the ChallengeResponseAuthentication and
|
||||
# PasswordAuthentication. Depending on your PAM configuration,
|
||||
# PAM authentication via ChallengeResponseAuthentication may bypass
|
||||
# the setting of "PermitRootLogin without-password".
|
||||
# If you just want the PAM account and session checks to run without
|
||||
# PAM authentication, then enable this but set PasswordAuthentication
|
||||
# and ChallengeResponseAuthentication to 'no'.
|
||||
#UsePAM no
|
||||
|
||||
#AllowAgentForwarding yes
|
||||
#AllowTcpForwarding yes
|
||||
#GatewayPorts no
|
||||
#X11Forwarding no
|
||||
#X11DisplayOffset 10
|
||||
#X11UseLocalhost yes
|
||||
#PermitTTY yes
|
||||
#PrintMotd yes
|
||||
#PrintLastLog yes
|
||||
#TCPKeepAlive yes
|
||||
#UseLogin no
|
||||
#PermitUserEnvironment no
|
||||
#Compression delayed
|
||||
#ClientAliveInterval 0
|
||||
#ClientAliveCountMax 3
|
||||
#UseDNS no
|
||||
#PidFile /run/sshd.pid
|
||||
#MaxStartups 10:30:100
|
||||
#PermitTunnel no
|
||||
#ChrootDirectory none
|
||||
#VersionAddendum none
|
||||
|
||||
# no default banner path
|
||||
#Banner none
|
||||
|
||||
# override default of no subsystems
|
||||
Subsystem sftp /usr/lib/ssh/sftp-server
|
||||
|
||||
# the following are HPN related configuration options
|
||||
# tcp receive buffer polling. disable in non autotuning kernels
|
||||
#TcpRcvBufPoll yes
|
||||
|
||||
# disable hpn performance boosts
|
||||
#HPNDisabled no
|
||||
|
||||
# buffer size for hpn to non-hpn connections
|
||||
#HPNBufferSize 2048
|
||||
|
||||
|
||||
# Example of overriding settings on a per-user basis
|
||||
#Match User anoncvs
|
||||
# X11Forwarding no
|
||||
# AllowTcpForwarding no
|
||||
# PermitTTY no
|
||||
# ForceCommand cvs server
|
||||
|
||||
|
||||
macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C)2009 - SSHJ Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hierynomus.sshj
|
||||
|
||||
import net.schmizz.sshj.Config
|
||||
import net.schmizz.sshj.DefaultConfig
|
||||
import net.schmizz.sshj.SSHClient
|
||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
|
||||
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 final static String SERVER_IP = System.getProperty("serverIP", "127.0.0.1")
|
||||
|
||||
protected static SSHClient getConnectedClient(Config config) {
|
||||
SSHClient sshClient = new SSHClient(config)
|
||||
sshClient.addHostKeyVerifier(new PromiscuousVerifier())
|
||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
||||
|
||||
return sshClient
|
||||
}
|
||||
|
||||
protected static SSHClient getConnectedClient() throws IOException {
|
||||
return getConnectedClient(new DefaultConfig())
|
||||
}
|
||||
|
||||
}
|
||||
71
src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy
Normal file
71
src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import net.schmizz.sshj.DefaultConfig
|
||||
import net.schmizz.sshj.SSHClient
|
||||
import net.schmizz.sshj.transport.TransportException
|
||||
import net.schmizz.sshj.userauth.UserAuthException
|
||||
|
||||
class IntegrationSpec extends IntegrationBaseSpec {
|
||||
|
||||
def "should accept correct key"() {
|
||||
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
|
||||
|
||||
when:
|
||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
||||
|
||||
then:
|
||||
sshClient.isConnected()
|
||||
}
|
||||
|
||||
def "should decline wrong key"() throws IOException {
|
||||
given:
|
||||
SSHClient sshClient = new SSHClient(new DefaultConfig())
|
||||
sshClient.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3")
|
||||
|
||||
when:
|
||||
sshClient.connect(SERVER_IP, DOCKER_PORT)
|
||||
|
||||
then:
|
||||
thrown(TransportException.class)
|
||||
}
|
||||
|
||||
def "should authenticate"() {
|
||||
given:
|
||||
SSHClient client = getConnectedClient()
|
||||
|
||||
when:
|
||||
client.authPublickey(USERNAME, KEYFILE)
|
||||
|
||||
then:
|
||||
client.isAuthenticated()
|
||||
}
|
||||
|
||||
def "should not authenticate with wrong key"() {
|
||||
given:
|
||||
SSHClient client = getConnectedClient()
|
||||
|
||||
when:
|
||||
client.authPublickey("sshj", "src/test/resources/id_dsa")
|
||||
|
||||
then:
|
||||
thrown(UserAuthException.class)
|
||||
!client.isAuthenticated()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.sftp
|
||||
|
||||
import com.hierynomus.sshj.IntegrationBaseSpec
|
||||
import net.schmizz.sshj.SSHClient
|
||||
import net.schmizz.sshj.sftp.OpenMode
|
||||
import net.schmizz.sshj.sftp.RemoteFile
|
||||
import net.schmizz.sshj.sftp.SFTPClient
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
|
||||
|
||||
class FileWriteSpec extends IntegrationBaseSpec {
|
||||
|
||||
def "should append to file (GH issue #390)"() {
|
||||
given:
|
||||
SSHClient client = getConnectedClient()
|
||||
client.authPublickey("sshj", "src/test/resources/id_rsa")
|
||||
SFTPClient sftp = client.newSFTPClient()
|
||||
def file = "/home/sshj/test.txt"
|
||||
def initialText = "This is the initial text.\n".getBytes(StandardCharsets.UTF_16)
|
||||
def appendText = "And here's the appended text.\n".getBytes(StandardCharsets.UTF_16)
|
||||
|
||||
when:
|
||||
withCloseable(sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT))) { RemoteFile initial ->
|
||||
initial.write(0, initialText, 0, initialText.length)
|
||||
}
|
||||
|
||||
then:
|
||||
withCloseable(sftp.open(file, EnumSet.of(OpenMode.READ))) { RemoteFile read ->
|
||||
def bytes = new byte[initialText.length]
|
||||
read.read(0, bytes, 0, bytes.length)
|
||||
bytes == initialText
|
||||
}
|
||||
|
||||
when:
|
||||
withCloseable(sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.APPEND))) { RemoteFile append ->
|
||||
append.write(0, appendText, 0, appendText.length)
|
||||
}
|
||||
|
||||
then:
|
||||
withCloseable(sftp.open(file, EnumSet.of(OpenMode.READ))) { RemoteFile read ->
|
||||
def bytes = new byte[initialText.length + appendText.length]
|
||||
read.read(0, bytes, 0, bytes.length)
|
||||
Arrays.copyOfRange(bytes, 0, initialText.length) == initialText
|
||||
Arrays.copyOfRange(bytes, initialText.length, initialText.length + appendText.length) == appendText
|
||||
}
|
||||
|
||||
cleanup:
|
||||
sftp.close()
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.mac
|
||||
|
||||
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.Unroll
|
||||
|
||||
class MacSpec extends IntegrationBaseSpec {
|
||||
|
||||
@Unroll
|
||||
def "should correctly connect with #mac MAC"() {
|
||||
given:
|
||||
def cfg = new DefaultConfig()
|
||||
cfg.setMACFactories(macFactory)
|
||||
def client = getConnectedClient(cfg)
|
||||
|
||||
when:
|
||||
client.authPublickey(USERNAME, KEYFILE)
|
||||
|
||||
then:
|
||||
client.authenticated
|
||||
|
||||
where:
|
||||
macFactory << [new HMACSHA2256.Factory(), new HMACRIPEMD160.Factory()]
|
||||
mac = macFactory.name
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
package com.hierynomus.sshj.backport;
|
||||
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.*;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Jdk7HttpProxySocket extends Socket {
|
||||
|
||||
@@ -48,7 +49,7 @@ public class Jdk7HttpProxySocket extends Socket {
|
||||
}
|
||||
InetSocketAddress isa = (InetSocketAddress) endpoint;
|
||||
String httpConnect = "CONNECT " + isa.getHostName() + ":" + isa.getPort() + " HTTP/1.0\n\n";
|
||||
getOutputStream().write(httpConnect.getBytes(Charset.forName("UTF-8")));
|
||||
getOutputStream().write(httpConnect.getBytes(IOUtils.UTF8));
|
||||
checkAndFlushProxyResponse();
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ public class Jdk7HttpProxySocket extends Socket {
|
||||
throw new SocketException("Empty response from proxy");
|
||||
}
|
||||
|
||||
String proxyResponse = new String(tmpBuffer, 0, len, "UTF-8");
|
||||
String proxyResponse = new String(tmpBuffer, 0, len, IOUtils.UTF8);
|
||||
|
||||
// Expecting HTTP/1.x 200 OK
|
||||
if (proxyResponse.contains("200")) {
|
||||
|
||||
@@ -32,7 +32,7 @@ public class Ed25519PublicKey extends EdDSAPublicKey {
|
||||
public Ed25519PublicKey(EdDSAPublicKeySpec spec) {
|
||||
super(spec);
|
||||
|
||||
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512");
|
||||
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
|
||||
if (!spec.getParams().getCurve().equals(ed25519.getCurve())) {
|
||||
throw new SSHRuntimeException("Cannot create Ed25519 Public Key from wrong spec");
|
||||
}
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
package com.hierynomus.sshj.signature;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.signature.AbstractSignature;
|
||||
import net.schmizz.sshj.signature.Signature;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SignatureException;
|
||||
|
||||
public class SignatureEdDSA implements Signature {
|
||||
public class SignatureEdDSA extends AbstractSignature {
|
||||
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
@@ -37,54 +39,18 @@ public class SignatureEdDSA implements Signature {
|
||||
}
|
||||
}
|
||||
|
||||
final EdDSAEngine engine;
|
||||
SignatureEdDSA() {
|
||||
super(getEngine());
|
||||
}
|
||||
|
||||
protected SignatureEdDSA() {
|
||||
private static EdDSAEngine getEngine() {
|
||||
try {
|
||||
engine = new EdDSAEngine(MessageDigest.getInstance("SHA-512"));
|
||||
return new EdDSAEngine(MessageDigest.getInstance("SHA-512"));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PublicKey pubkey, PrivateKey prvkey) {
|
||||
try {
|
||||
if (pubkey != null) {
|
||||
engine.initVerify(pubkey);
|
||||
}
|
||||
|
||||
if (prvkey != null) {
|
||||
engine.initSign(prvkey);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] H) {
|
||||
update(H, 0, H.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] H, int off, int len) {
|
||||
try {
|
||||
engine.update(H, off, len);
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign() {
|
||||
try {
|
||||
return engine.sign();
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(byte[] signature) {
|
||||
return signature;
|
||||
@@ -93,17 +59,9 @@ public class SignatureEdDSA implements Signature {
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
try {
|
||||
Buffer.PlainBuffer plainBuffer = new Buffer.PlainBuffer(sig);
|
||||
String algo = plainBuffer.readString();
|
||||
if (!"ssh-ed25519".equals(algo)) {
|
||||
throw new SSHRuntimeException("Expected 'ssh-ed25519' key algorithm, but was: " + algo);
|
||||
}
|
||||
byte[] bytes = plainBuffer.readBytes();
|
||||
return engine.verify(bytes);
|
||||
return signature.verify(extractSig(sig, "ssh-ed25519"));
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
} catch (Buffer.BufferException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,25 @@ package com.hierynomus.sshj.transport;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class IdentificationStringParser {
|
||||
private static final Logger logger = LoggerFactory.getLogger(IdentificationStringParser.class);
|
||||
private final Logger log;
|
||||
private final Buffer.PlainBuffer buffer;
|
||||
|
||||
private byte[] EXPECTED_START_BYTES = new byte[] {'S', 'S', 'H', '-'};
|
||||
|
||||
public IdentificationStringParser(Buffer.PlainBuffer buffer) {
|
||||
this(buffer, LoggerFactory.DEFAULT);
|
||||
}
|
||||
|
||||
public IdentificationStringParser(Buffer.PlainBuffer buffer, LoggerFactory loggerFactory) {
|
||||
this.log = loggerFactory.getLogger(IdentificationStringParser.class);
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@@ -57,24 +62,27 @@ public class IdentificationStringParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void logHeaderLine(Buffer.PlainBuffer lineBuffer) {
|
||||
|
||||
private void logHeaderLine(Buffer.PlainBuffer lineBuffer) throws Buffer.BufferException {
|
||||
byte[] bytes = new byte[lineBuffer.available()];
|
||||
lineBuffer.readRawBytes(bytes);
|
||||
String header = new String(bytes, 0, bytes.length - 1);
|
||||
log.debug("Received header: {}", header);
|
||||
}
|
||||
|
||||
private String readIdentification(Buffer.PlainBuffer lineBuffer) throws Buffer.BufferException, TransportException {
|
||||
byte[] bytes = new byte[lineBuffer.available()];
|
||||
lineBuffer.readRawBytes(bytes);
|
||||
if (bytes.length > 255) {
|
||||
logger.error("Incorrect identification String received, line was longer than expected: {}", new String(bytes));
|
||||
logger.error("Just for good measure, bytes were: {}", ByteArrayUtils.printHex(bytes, 0, bytes.length));
|
||||
log.error("Incorrect identification String received, line was longer than expected: {}", new String(bytes));
|
||||
log.error("Just for good measure, bytes were: {}", ByteArrayUtils.printHex(bytes, 0, bytes.length));
|
||||
throw new TransportException("Incorrect identification: line too long: " + ByteArrayUtils.printHex(bytes, 0, bytes.length));
|
||||
}
|
||||
if (bytes[bytes.length - 2] != '\r') {
|
||||
String ident = new String(bytes, 0, bytes.length - 1);
|
||||
logger.warn("Server identification has bad line ending, was expecting a '\\r\\n' however got: '{}' (hex: {})", (char) (bytes[bytes.length - 2] & 0xFF), Integer.toHexString(bytes[bytes.length - 2] & 0xFF));
|
||||
logger.warn("Will treat the identification of this server '{}' leniently", ident);
|
||||
log.warn("Server identification has bad line ending, was expecting a '\\r\\n' however got: '{}' (hex: {})", (char) (bytes[bytes.length - 2] & 0xFF), Integer.toHexString(bytes[bytes.length - 2] & 0xFF));
|
||||
log.warn("Will treat the identification of this server '{}' leniently", ident);
|
||||
return ident;
|
||||
// logger.error("Data received up til here was: {}", new String(bytes));
|
||||
// log.error("Data received up til here was: {}", new String(bytes));
|
||||
// throw new TransportException("Incorrect identification: bad line ending: " + ByteArrayUtils.toHex(bytes, 0, bytes.length));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,23 +19,46 @@ import net.schmizz.sshj.transport.cipher.BlockCipher;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
|
||||
/**
|
||||
* All BlockCiphers supported by SSH according to the following RFCs
|
||||
* All BlockCiphers supported by SSH according to the following RFCs:
|
||||
*
|
||||
* - https://tools.ietf.org/html/rfc4344#section-3.1
|
||||
* - https://tools.ietf.org/html/rfc4253#section-6.3
|
||||
* <ul>
|
||||
* <li>https://tools.ietf.org/html/rfc4344#section-3.1</li>
|
||||
* <li>https://tools.ietf.org/html/rfc4253#section-6.3</li>
|
||||
* <li>TODO: https://tools.ietf.org/html/rfc5647</li>
|
||||
* </ul>
|
||||
*
|
||||
* TODO: https://tools.ietf.org/html/rfc5647
|
||||
*
|
||||
* Some of the Ciphers are still implemented in net.schmizz.sshj.transport.cipher.*. These are scheduled to be migrated to here.
|
||||
* Some of the Ciphers are still implemented in net.schmizz.sshj.transport.cipher.*. These are deprecated and scheduled to be removed.
|
||||
*/
|
||||
@SuppressWarnings("PMD.MethodNamingConventions")
|
||||
public class BlockCiphers {
|
||||
|
||||
public static final String COUNTER_MODE = "CTR";
|
||||
public static final String CIPHER_BLOCK_CHAINING_MODE = "CBC";
|
||||
|
||||
public static Factory AES128CTR() {
|
||||
return new Factory(16, 128, "aes128-ctr", "AES", COUNTER_MODE);
|
||||
}
|
||||
public static Factory AES192CTR() {
|
||||
return new Factory(16, 192, "aes192-ctr", "AES", COUNTER_MODE);
|
||||
}
|
||||
public static Factory AES256CTR() {
|
||||
return new Factory(16, 256, "aes256-ctr", "AES", COUNTER_MODE);
|
||||
}
|
||||
public static Factory AES128CBC() {
|
||||
return new Factory(16, 128, "aes128-cbc", "AES", CIPHER_BLOCK_CHAINING_MODE);
|
||||
}
|
||||
public static Factory AES192CBC() {
|
||||
return new Factory(16, 192, "aes192-cbc", "AES", CIPHER_BLOCK_CHAINING_MODE);
|
||||
}
|
||||
public static Factory AES256CBC() {
|
||||
return new Factory(16, 256, "aes256-cbc", "AES", CIPHER_BLOCK_CHAINING_MODE);
|
||||
}
|
||||
public static Factory BlowfishCTR() {
|
||||
return new Factory(8, 256, "blowfish-ctr", "Blowfish", COUNTER_MODE);
|
||||
}
|
||||
public static Factory BlowfishCBC() {
|
||||
return new Factory(8, 128, "blowfish-cbc", "Blowfish", CIPHER_BLOCK_CHAINING_MODE);
|
||||
}
|
||||
public static Factory Twofish128CTR() {
|
||||
return new Factory(16, 128, "twofish128-ctr", "Twofish", COUNTER_MODE);
|
||||
}
|
||||
@@ -90,6 +113,9 @@ public class BlockCiphers {
|
||||
public static Factory TripleDESCTR() {
|
||||
return new Factory(8, 192, "3des-ctr", "DESede", COUNTER_MODE);
|
||||
}
|
||||
public static Factory TripleDESCBC() {
|
||||
return new Factory(8, 192, "3des-cbc", "DESede", CIPHER_BLOCK_CHAINING_MODE);
|
||||
}
|
||||
|
||||
/** Named factory for BlockCipher */
|
||||
public static class Factory
|
||||
|
||||
@@ -25,6 +25,7 @@ import static com.hierynomus.sshj.transport.cipher.BlockCiphers.COUNTER_MODE;
|
||||
*
|
||||
* - http://tools.ietf.org/id/draft-kanno-secsh-camellia-01.txt
|
||||
*/
|
||||
@SuppressWarnings("PMD.MethodNamingConventions")
|
||||
public class ExtendedBlockCiphers {
|
||||
public static BlockCiphers.Factory Camellia128CTR() {
|
||||
return new BlockCiphers.Factory(16, 128, "camellia128-ctr", "Camellia", COUNTER_MODE);
|
||||
|
||||
@@ -19,7 +19,6 @@ import net.schmizz.sshj.transport.cipher.BaseCipher;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class StreamCipher extends BaseCipher {
|
||||
|
||||
@@ -29,6 +28,6 @@ public class StreamCipher extends BaseCipher {
|
||||
|
||||
@Override
|
||||
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
cipher.init(getMode(mode), getKeySpec(key), new SecureRandom());
|
||||
cipher.init(getMode(mode), getKeySpec(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
* - https://tools.ietf.org/html/rfc4253#section-6.3
|
||||
* - https://tools.ietf.org/html/rfc4345
|
||||
*/
|
||||
@SuppressWarnings("PMD.MethodNamingConventions")
|
||||
public class StreamCiphers {
|
||||
|
||||
public static Factory Arcfour() {
|
||||
|
||||
44
src/main/java/com/hierynomus/sshj/transport/kex/DHG.java
Normal file
44
src/main/java/com/hierynomus/sshj/transport/kex/DHG.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 net.schmizz.sshj.transport.digest.Digest;
|
||||
import net.schmizz.sshj.transport.kex.AbstractDHG;
|
||||
import net.schmizz.sshj.transport.kex.DH;
|
||||
import net.schmizz.sshj.transport.kex.DHBase;
|
||||
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class DHG extends AbstractDHG {
|
||||
private BigInteger group;
|
||||
private BigInteger generator;
|
||||
|
||||
public DHG(BigInteger group, BigInteger generator, Digest digest) {
|
||||
super(new DH(), digest);
|
||||
this.group = group;
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DHBase dh) throws GeneralSecurityException {
|
||||
dh.init(new DHParameterSpec(group, generator), trans.getConfig().getRandomFactory());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 net.schmizz.sshj.transport.digest.Digest;
|
||||
import net.schmizz.sshj.transport.digest.SHA1;
|
||||
import net.schmizz.sshj.transport.digest.SHA256;
|
||||
import net.schmizz.sshj.transport.digest.SHA512;
|
||||
import net.schmizz.sshj.transport.kex.KeyExchange;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import static net.schmizz.sshj.transport.kex.DHGroupData.*;
|
||||
|
||||
/**
|
||||
* Factory methods for Diffie Hellmann KEX algorithms based on MODP groups / Oakley Groups
|
||||
*
|
||||
* - https://tools.ietf.org/html/rfc4253
|
||||
* - https://tools.ietf.org/html/draft-ietf-curdle-ssh-modp-dh-sha2-01
|
||||
*/
|
||||
@SuppressWarnings("PMD.MethodNamingConventions")
|
||||
public class DHGroups {
|
||||
|
||||
public static DHGroups.Factory Group1SHA1() {
|
||||
return new DHGroups.Factory("diffie-hellman-group1-sha1", P1, G, new SHA1.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group14SHA1() {
|
||||
return new DHGroups.Factory("diffie-hellman-group14-sha1", P14, G, new SHA1.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group14SHA256() {
|
||||
return new DHGroups.Factory("diffie-hellman-group14-sha256", P14, G, new SHA256.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group15SHA512() {
|
||||
return new DHGroups.Factory("diffie-hellman-group15-sha512", P15, G, new SHA512.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group16SHA512() {
|
||||
return new DHGroups.Factory("diffie-hellman-group16-sha512", P16, G, new SHA512.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group17SHA512() {
|
||||
return new DHGroups.Factory("diffie-hellman-group17-sha512", P17, G, new SHA512.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group18SHA512() {
|
||||
return new DHGroups.Factory("diffie-hellman-group18-sha512", P18, G, new SHA512.Factory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Named factory for DHG1 key exchange
|
||||
*/
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
private String name;
|
||||
private BigInteger group;
|
||||
private BigInteger generator;
|
||||
private Factory.Named<Digest> digestFactory;
|
||||
|
||||
public Factory(String name, BigInteger group, BigInteger generator, Named<Digest> digestFactory) {
|
||||
this.name = name;
|
||||
this.group = group;
|
||||
this.generator = generator;
|
||||
this.digestFactory = digestFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new DHG(group, generator, digestFactory.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 net.schmizz.sshj.transport.digest.SHA256;
|
||||
import net.schmizz.sshj.transport.digest.SHA384;
|
||||
import net.schmizz.sshj.transport.digest.SHA512;
|
||||
|
||||
import static net.schmizz.sshj.transport.kex.DHGroupData.*;
|
||||
|
||||
/**
|
||||
* Set of KEX methods that are not in official RFCs but are supported by some SSH servers.
|
||||
*/
|
||||
@SuppressWarnings("PMD.MethodNamingConventions")
|
||||
public class ExtendedDHGroups {
|
||||
public static DHGroups.Factory Group14SHA256AtSSH() {
|
||||
return new DHGroups.Factory("diffie-hellman-group14-sha256@ssh.com", P14, G, new SHA256.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group15SHA256() {
|
||||
return new DHGroups.Factory("diffie-hellman-group15-sha256", P15, G, new SHA256.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group15SHA256AtSSH() {
|
||||
return new DHGroups.Factory("diffie-hellman-group15-sha256@ssh.com", P15, G, new SHA256.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group15SHA384AtSSH() {
|
||||
return new DHGroups.Factory("diffie-hellman-group15-sha384@ssh.com", P15, G, new SHA384.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group16SHA256() {
|
||||
return new DHGroups.Factory("diffie-hellman-group16-sha256", P16, G, new SHA256.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group16SHA384AtSSH() {
|
||||
return new DHGroups.Factory("diffie-hellman-group16-sha384@ssh.com", P16, G, new SHA384.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group16SHA512AtSSH() {
|
||||
return new DHGroups.Factory("diffie-hellman-group16-sha512@ssh.com", P16, G, new SHA512.Factory());
|
||||
}
|
||||
|
||||
public static DHGroups.Factory Group18SHA512AtSSH() {
|
||||
return new DHGroups.Factory("diffie-hellman-group18-sha512@ssh.com", P18, G, new SHA512.Factory());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class KnownHostMatchers {
|
||||
|
||||
public static HostMatcher createMatcher(String hostEntry) throws SSHException {
|
||||
if (hostEntry.contains(",")) {
|
||||
return new AnyHostMatcher(hostEntry);
|
||||
}
|
||||
if (hostEntry.startsWith("!")) {
|
||||
return new NegateHostMatcher(hostEntry);
|
||||
}
|
||||
if (hostEntry.startsWith("|1|")) {
|
||||
return new HashedHostMatcher(hostEntry);
|
||||
}
|
||||
if (hostEntry.contains("*") || hostEntry.contains("?")) {
|
||||
return new WildcardHostMatcher(hostEntry);
|
||||
}
|
||||
|
||||
return new EquiHostMatcher(hostEntry);
|
||||
}
|
||||
|
||||
public interface HostMatcher {
|
||||
boolean match(String hostname) throws IOException;
|
||||
}
|
||||
|
||||
private static class EquiHostMatcher implements HostMatcher {
|
||||
private String host;
|
||||
|
||||
public EquiHostMatcher(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) {
|
||||
return host.equals(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HashedHostMatcher implements HostMatcher {
|
||||
private final MAC sha1 = new HMACSHA1();
|
||||
private final String hash;
|
||||
private final String salt;
|
||||
private byte[] saltyBytes;
|
||||
|
||||
HashedHostMatcher(String hash) throws SSHException {
|
||||
this.hash = hash;
|
||||
final String[] hostParts = hash.split("\\|");
|
||||
if (hostParts.length != 4) {
|
||||
throw new SSHException("Unrecognized format for hashed hostname");
|
||||
}
|
||||
salt = hostParts[2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
return hash.equals(hashHost(hostname));
|
||||
}
|
||||
|
||||
private String hashHost(String host) throws IOException {
|
||||
sha1.init(getSaltyBytes());
|
||||
return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
|
||||
}
|
||||
|
||||
private byte[] getSaltyBytes() throws IOException {
|
||||
if (saltyBytes == null) {
|
||||
saltyBytes = Base64.decode(salt);
|
||||
}
|
||||
return saltyBytes;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class AnyHostMatcher implements HostMatcher {
|
||||
private final List<HostMatcher> matchers;
|
||||
|
||||
AnyHostMatcher(String hostEntry) throws SSHException {
|
||||
matchers = new ArrayList<HostMatcher>();
|
||||
for (String subEntry : hostEntry.split(",")) {
|
||||
matchers.add(KnownHostMatchers.createMatcher(subEntry));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
for (HostMatcher matcher : matchers) {
|
||||
if (matcher.match(hostname)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NegateHostMatcher implements HostMatcher {
|
||||
private final HostMatcher matcher;
|
||||
|
||||
NegateHostMatcher(String hostEntry) throws SSHException {
|
||||
this.matcher = createMatcher(hostEntry.substring(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
return !matcher.match(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WildcardHostMatcher implements HostMatcher {
|
||||
private final Pattern pattern;
|
||||
|
||||
public WildcardHostMatcher(String hostEntry) {
|
||||
this.pattern = Pattern.compile("^" + hostEntry.replace("[", "\\[").replace("]", "\\]").replace(".", "\\.").replace("*", ".*").replace("?", ".") + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String hostname) throws IOException {
|
||||
return pattern.matcher(hostname).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WildcardHostMatcher[" + pattern + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.hierynomus.sshj.userauth.certificate;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Certificate wrapper for public keys, created to help implement
|
||||
* protocol described here:
|
||||
*
|
||||
* https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
|
||||
*
|
||||
* Consumed primarily by net.shmizz.sshj.common.KeyType
|
||||
*
|
||||
* @param <T> inner public key type
|
||||
*/
|
||||
public class Certificate<T extends PublicKey> implements PublicKey {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final T publicKey;
|
||||
private final byte[] nonce;
|
||||
private final BigInteger serial;
|
||||
private final long type;
|
||||
private final String id;
|
||||
private final List<String> validPrincipals;
|
||||
private final Date validAfter;
|
||||
private final Date validBefore;
|
||||
private final Map<String, String> critOptions;
|
||||
private final Map<String, String> extensions;
|
||||
private final byte[] signatureKey;
|
||||
private final byte[] signature;
|
||||
|
||||
Certificate(Builder<T> builder) {
|
||||
this.publicKey = builder.getPublicKey();
|
||||
this.nonce = builder.getNonce();
|
||||
this.serial = builder.getSerial();
|
||||
this.type = builder.getType();
|
||||
this.id = builder.getId();
|
||||
this.validPrincipals = builder.getValidPrincipals();
|
||||
this.validAfter = builder.getValidAfter();
|
||||
this.validBefore = builder.getValidBefore();
|
||||
this.critOptions = builder.getCritOptions();
|
||||
this.extensions = builder.getExtensions();
|
||||
this.signatureKey = builder.getSignatureKey();
|
||||
this.signature = builder.getSignature();
|
||||
}
|
||||
|
||||
public static <P extends PublicKey> Builder<P> getBuilder() {
|
||||
return new Builder<P>();
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public BigInteger getSerial() {
|
||||
return serial;
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<String> getValidPrincipals() {
|
||||
return validPrincipals;
|
||||
}
|
||||
|
||||
public Date getValidAfter() {
|
||||
return validAfter;
|
||||
}
|
||||
|
||||
public Date getValidBefore() {
|
||||
return validBefore;
|
||||
}
|
||||
|
||||
public Map<String, String> getCritOptions() {
|
||||
return critOptions;
|
||||
}
|
||||
|
||||
public Map<String, String> getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
public byte[] getSignatureKey() {
|
||||
return signatureKey;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public T getKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return publicKey.getEncoded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return publicKey.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return publicKey.getFormat();
|
||||
}
|
||||
|
||||
public static class Builder<T extends PublicKey> {
|
||||
private T publicKey;
|
||||
private byte[] nonce;
|
||||
private BigInteger serial;
|
||||
private long type;
|
||||
private String id;
|
||||
private List<String> validPrincipals;
|
||||
private Date validAfter;
|
||||
private Date validBefore;
|
||||
private Map<String, String> critOptions;
|
||||
private Map<String, String> extensions;
|
||||
private byte[] signatureKey;
|
||||
private byte[] signature;
|
||||
|
||||
public Certificate<T> build() {
|
||||
return new Certificate<T>(this);
|
||||
}
|
||||
|
||||
public T getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public Builder<T> publicKey(T publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public Builder<T> nonce(byte[] nonce) {
|
||||
this.nonce = nonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigInteger getSerial() {
|
||||
return serial;
|
||||
}
|
||||
|
||||
public Builder<T> serial(BigInteger serial) {
|
||||
this.serial = serial;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Builder<T> type(long type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Builder<T> id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getValidPrincipals() {
|
||||
return validPrincipals;
|
||||
}
|
||||
|
||||
public Builder<T> validPrincipals(List<String> validPrincipals) {
|
||||
this.validPrincipals = validPrincipals;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getValidAfter() {
|
||||
return validAfter;
|
||||
}
|
||||
|
||||
public Builder<T> validAfter(Date validAfter) {
|
||||
this.validAfter = validAfter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getValidBefore() {
|
||||
return validBefore;
|
||||
}
|
||||
|
||||
public Builder<T> validBefore(Date validBefore) {
|
||||
this.validBefore = validBefore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getCritOptions() {
|
||||
return critOptions;
|
||||
}
|
||||
|
||||
public Builder<T> critOptions(Map<String, String> critOptions) {
|
||||
this.critOptions = critOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
public Builder<T> extensions(Map<String, String> extensions) {
|
||||
this.extensions = extensions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getSignatureKey() {
|
||||
return signatureKey;
|
||||
}
|
||||
|
||||
public Builder<T> signatureKey(byte[] signatureKey) {
|
||||
this.signatureKey = signatureKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public Builder<T> signature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.keyprovider;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.common.Buffer.PlainBuffer;
|
||||
import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* Reads a key file in the new OpenSSH format.
|
||||
* The format is described in the following document: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||
*/
|
||||
public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OpenSSHKeyV1KeyFile.class);
|
||||
private static final String BEGIN = "-----BEGIN ";
|
||||
private static final String END = "-----END ";
|
||||
private static final byte[] AUTH_MAGIC = "openssh-key-v1\0".getBytes();
|
||||
public static final String OPENSSH_PRIVATE_KEY = "OPENSSH PRIVATE KEY-----";
|
||||
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
|
||||
|
||||
@Override
|
||||
public FileKeyProvider create() {
|
||||
return new OpenSSHKeyV1KeyFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyFormat.OpenSSHv1.name();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyPair readKeyPair() throws IOException {
|
||||
BufferedReader reader = new BufferedReader(resource.getReader());
|
||||
try {
|
||||
if (!checkHeader(reader)) {
|
||||
throw new IOException("This key is not in 'openssh-key-v1' format");
|
||||
}
|
||||
|
||||
String keyFile = readKeyFile(reader);
|
||||
byte[] decode = Base64.decode(keyFile);
|
||||
PlainBuffer keyBuffer = new PlainBuffer(decode);
|
||||
return readDecodedKeyPair(keyBuffer);
|
||||
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyPair readDecodedKeyPair(final PlainBuffer keyBuffer) throws IOException, GeneralSecurityException {
|
||||
byte[] bytes = new byte[AUTH_MAGIC.length];
|
||||
keyBuffer.readRawBytes(bytes); // byte[] AUTH_MAGIC
|
||||
if (!ByteArrayUtils.equals(bytes, 0, AUTH_MAGIC, 0, AUTH_MAGIC.length)) {
|
||||
throw new IOException("This key does not contain the 'openssh-key-v1' format magic header");
|
||||
}
|
||||
|
||||
String cipherName = keyBuffer.readString(); // string ciphername
|
||||
String kdfName = keyBuffer.readString(); // string kdfname
|
||||
String kdfOptions = keyBuffer.readString(); // string kdfoptions
|
||||
|
||||
int nrKeys = keyBuffer.readUInt32AsInt(); // int number of keys N; Should be 1
|
||||
if (nrKeys != 1) {
|
||||
throw new IOException("We don't support having more than 1 key in the file (yet).");
|
||||
}
|
||||
PublicKey publicKey = readPublicKey(new PlainBuffer(keyBuffer.readBytes())); // string publickey1
|
||||
PlainBuffer privateKeyBuffer = new PlainBuffer(keyBuffer.readBytes()); // string (possibly) encrypted, padded list of private keys
|
||||
if ("none".equals(cipherName)) {
|
||||
logger.debug("Reading unencrypted keypair");
|
||||
return readUnencrypted(privateKeyBuffer, publicKey);
|
||||
} else {
|
||||
logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + kdfOptions);
|
||||
throw new IOException("Cannot read encrypted keypair with " + cipherName + " yet.");
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey readPublicKey(final PlainBuffer plainBuffer) throws Buffer.BufferException, GeneralSecurityException {
|
||||
return KeyType.fromString(plainBuffer.readString()).readPubKeyFromBuffer(plainBuffer);
|
||||
}
|
||||
|
||||
private String readKeyFile(final BufferedReader reader) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = reader.readLine();
|
||||
while (!line.startsWith(END)) {
|
||||
sb.append(line);
|
||||
line = reader.readLine();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean checkHeader(final BufferedReader reader) throws IOException {
|
||||
String line = reader.readLine();
|
||||
while (line != null && !line.startsWith(BEGIN)) {
|
||||
line = reader.readLine();
|
||||
}
|
||||
line = line.substring(BEGIN.length());
|
||||
return line.startsWith(OPENSSH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey publicKey) throws IOException, GeneralSecurityException {
|
||||
int privKeyListSize = keyBuffer.available();
|
||||
if (privKeyListSize % 8 != 0) {
|
||||
throw new IOException("The private key section must be a multiple of the block size (8)");
|
||||
}
|
||||
int checkInt1 = keyBuffer.readUInt32AsInt(); // uint32 checkint1
|
||||
int checkInt2 = keyBuffer.readUInt32AsInt(); // uint32 checkint2
|
||||
if (checkInt1 != checkInt2) {
|
||||
throw new IOException("The checkInts differed, the key was not correctly decoded.");
|
||||
}
|
||||
// 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);
|
||||
|
||||
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
|
||||
byte[] padding = new byte[keyBuffer.available()];
|
||||
keyBuffer.readRawBytes(padding); // char[] padding
|
||||
for (int i = 0; i < padding.length; i++) {
|
||||
if ((int) padding[i] != i + 1) {
|
||||
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"))));
|
||||
}
|
||||
}
|
||||
918
src/main/java/djb/Curve25519.java
Normal file
918
src/main/java/djb/Curve25519.java
Normal file
@@ -0,0 +1,918 @@
|
||||
/* Ported from C to Java by Dmitry Skiba [sahn0], 23/02/08.
|
||||
* Original: http://cds.xs4all.nl:8081/ecdh/
|
||||
*/
|
||||
/* Generic 64-bit integer implementation of Curve25519 ECDH
|
||||
* Written by Matthijs van Duin, 200608242056
|
||||
* Public domain.
|
||||
*
|
||||
* Based on work by Daniel J Bernstein, http://cr.yp.to/ecdh.html
|
||||
*/
|
||||
package djb;
|
||||
|
||||
public class Curve25519 {
|
||||
|
||||
/* key size */
|
||||
public static final int KEY_SIZE = 32;
|
||||
|
||||
/* 0 */
|
||||
public static final byte[] ZERO = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
/* the prime 2^255-19 */
|
||||
public static final byte[] PRIME = {
|
||||
(byte)237, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)255,
|
||||
(byte)255, (byte)255, (byte)255, (byte)127
|
||||
};
|
||||
|
||||
/* group order (a prime near 2^252+2^124) */
|
||||
public static final byte[] ORDER = {
|
||||
(byte)237, (byte)211, (byte)245, (byte)92,
|
||||
(byte)26, (byte)99, (byte)18, (byte)88,
|
||||
(byte)214, (byte)156, (byte)247, (byte)162,
|
||||
(byte)222, (byte)249, (byte)222, (byte)20,
|
||||
(byte)0, (byte)0, (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, (byte)0, (byte)16
|
||||
};
|
||||
|
||||
/********* KEY AGREEMENT *********/
|
||||
|
||||
/* Private key clamping
|
||||
* k [out] your private key for key agreement
|
||||
* k [in] 32 random bytes
|
||||
*/
|
||||
public static final void clamp(byte[] k) {
|
||||
k[31] &= 0x7F;
|
||||
k[31] |= 0x40;
|
||||
k[ 0] &= 0xF8;
|
||||
}
|
||||
|
||||
/* Key-pair generation
|
||||
* P [out] your public key
|
||||
* s [out] your private key for signing
|
||||
* k [out] your private key for key agreement
|
||||
* k [in] 32 random bytes
|
||||
* s may be NULL if you don't care
|
||||
*
|
||||
* WARNING: if s is not NULL, this function has data-dependent timing */
|
||||
public static final void keygen(byte[] P, byte[] s, byte[] k) {
|
||||
clamp(k);
|
||||
core(P, s, k, null);
|
||||
}
|
||||
|
||||
/* Key agreement
|
||||
* Z [out] shared secret (needs hashing before use)
|
||||
* k [in] your private key for key agreement
|
||||
* P [in] peer's public key
|
||||
*/
|
||||
public static final void curve(byte[] Z, byte[] k, byte[] P) {
|
||||
core(Z, null, k, P);
|
||||
}
|
||||
|
||||
/********* DIGITAL SIGNATURES *********/
|
||||
|
||||
/* deterministic EC-KCDSA
|
||||
*
|
||||
* s is the private key for signing
|
||||
* P is the corresponding public key
|
||||
* Z is the context data (signer public key or certificate, etc)
|
||||
*
|
||||
* signing:
|
||||
*
|
||||
* m = hash(Z, message)
|
||||
* x = hash(m, s)
|
||||
* keygen25519(Y, NULL, x);
|
||||
* r = hash(Y);
|
||||
* h = m XOR r
|
||||
* sign25519(v, h, x, s);
|
||||
*
|
||||
* output (v,r) as the signature
|
||||
*
|
||||
* verification:
|
||||
*
|
||||
* m = hash(Z, message);
|
||||
* h = m XOR r
|
||||
* verify25519(Y, v, h, P)
|
||||
*
|
||||
* confirm r == hash(Y)
|
||||
*
|
||||
* It would seem to me that it would be simpler to have the signer directly do
|
||||
* h = hash(m, Y) and send that to the recipient instead of r, who can verify
|
||||
* the signature by checking h == hash(m, Y). If there are any problems with
|
||||
* such a scheme, please let me know.
|
||||
*
|
||||
* Also, EC-KCDSA (like most DS algorithms) picks x random, which is a waste of
|
||||
* perfectly good entropy, but does allow Y to be calculated in advance of (or
|
||||
* parallel to) hashing the message.
|
||||
*/
|
||||
|
||||
/* Signature generation primitive, calculates (x-h)s mod q
|
||||
* v [out] signature value
|
||||
* h [in] signature hash (of message, signature pub key, and context data)
|
||||
* x [in] signature private key
|
||||
* s [in] private key for signing
|
||||
* returns true on success, false on failure (use different x or h)
|
||||
*/
|
||||
public static final boolean sign(byte[] v, byte[] h, byte[] x, byte[] s) {
|
||||
// v = (x - h) s mod q
|
||||
int w, i;
|
||||
byte[] h1 = new byte[32], x1 = new byte[32];
|
||||
byte[] tmp1 = new byte[64];
|
||||
byte[] tmp2 = new byte[64];
|
||||
|
||||
// Don't clobber the arguments, be nice!
|
||||
cpy32(h1, h);
|
||||
cpy32(x1, x);
|
||||
|
||||
// Reduce modulo group order
|
||||
byte[] tmp3=new byte[32];
|
||||
divmod(tmp3, h1, 32, ORDER, 32);
|
||||
divmod(tmp3, x1, 32, ORDER, 32);
|
||||
|
||||
// v = x1 - h1
|
||||
// If v is negative, add the group order to it to become positive.
|
||||
// If v was already positive we don't have to worry about overflow
|
||||
// when adding the order because v < ORDER and 2*ORDER < 2^256
|
||||
mula_small(v, x1, 0, h1, 32, -1);
|
||||
mula_small(v, v , 0, ORDER, 32, 1);
|
||||
|
||||
// tmp1 = (x-h)*s mod q
|
||||
mula32(tmp1, v, s, 32, 1);
|
||||
divmod(tmp2, tmp1, 64, ORDER, 32);
|
||||
|
||||
for (w = 0, i = 0; i < 32; i++)
|
||||
w |= v[i] = tmp1[i];
|
||||
return w != 0;
|
||||
}
|
||||
|
||||
/* Signature verification primitive, calculates Y = vP + hG
|
||||
* Y [out] signature public key
|
||||
* v [in] signature value
|
||||
* h [in] signature hash
|
||||
* P [in] public key
|
||||
*/
|
||||
public static final void verify(byte[] Y, byte[] v, byte[] h, byte[] P) {
|
||||
/* Y = v abs(P) + h G */
|
||||
byte[] d=new byte[32];
|
||||
long10[]
|
||||
p=new long10[]{new long10(),new long10()},
|
||||
s=new long10[]{new long10(),new long10()},
|
||||
yx=new long10[]{new long10(),new long10(),new long10()},
|
||||
yz=new long10[]{new long10(),new long10(),new long10()},
|
||||
t1=new long10[]{new long10(),new long10(),new long10()},
|
||||
t2=new long10[]{new long10(),new long10(),new long10()};
|
||||
|
||||
int vi = 0, hi = 0, di = 0, nvh=0, i, j, k;
|
||||
|
||||
/* set p[0] to G and p[1] to P */
|
||||
|
||||
set(p[0], 9);
|
||||
unpack(p[1], P);
|
||||
|
||||
/* set s[0] to P+G and s[1] to P-G */
|
||||
|
||||
/* s[0] = (Py^2 + Gy^2 - 2 Py Gy)/(Px - Gx)^2 - Px - Gx - 486662 */
|
||||
/* s[1] = (Py^2 + Gy^2 + 2 Py Gy)/(Px - Gx)^2 - Px - Gx - 486662 */
|
||||
|
||||
x_to_y2(t1[0], t2[0], p[1]); /* t2[0] = Py^2 */
|
||||
sqrt(t1[0], t2[0]); /* t1[0] = Py or -Py */
|
||||
j = is_negative(t1[0]); /* ... check which */
|
||||
t2[0]._0 += 39420360; /* t2[0] = Py^2 + Gy^2 */
|
||||
mul(t2[1], BASE_2Y, t1[0]);/* t2[1] = 2 Py Gy or -2 Py Gy */
|
||||
sub(t1[j], t2[0], t2[1]); /* t1[0] = Py^2 + Gy^2 - 2 Py Gy */
|
||||
add(t1[1-j], t2[0], t2[1]);/* t1[1] = Py^2 + Gy^2 + 2 Py Gy */
|
||||
cpy(t2[0], p[1]); /* t2[0] = Px */
|
||||
t2[0]._0 -= 9; /* t2[0] = Px - Gx */
|
||||
sqr(t2[1], t2[0]); /* t2[1] = (Px - Gx)^2 */
|
||||
recip(t2[0], t2[1], 0); /* t2[0] = 1/(Px - Gx)^2 */
|
||||
mul(s[0], t1[0], t2[0]); /* s[0] = t1[0]/(Px - Gx)^2 */
|
||||
sub(s[0], s[0], p[1]); /* s[0] = t1[0]/(Px - Gx)^2 - Px */
|
||||
s[0]._0 -= 9 + 486662; /* s[0] = X(P+G) */
|
||||
mul(s[1], t1[1], t2[0]); /* s[1] = t1[1]/(Px - Gx)^2 */
|
||||
sub(s[1], s[1], p[1]); /* s[1] = t1[1]/(Px - Gx)^2 - Px */
|
||||
s[1]._0 -= 9 + 486662; /* s[1] = X(P-G) */
|
||||
mul_small(s[0], s[0], 1); /* reduce s[0] */
|
||||
mul_small(s[1], s[1], 1); /* reduce s[1] */
|
||||
|
||||
|
||||
/* prepare the chain */
|
||||
for (i = 0; i < 32; i++) {
|
||||
vi = (vi >> 8) ^ (v[i] & 0xFF) ^ ((v[i] & 0xFF) << 1);
|
||||
hi = (hi >> 8) ^ (h[i] & 0xFF) ^ ((h[i] & 0xFF) << 1);
|
||||
nvh = ~(vi ^ hi);
|
||||
di = (nvh & (di & 0x80) >> 7) ^ vi;
|
||||
di ^= nvh & (di & 0x01) << 1;
|
||||
di ^= nvh & (di & 0x02) << 1;
|
||||
di ^= nvh & (di & 0x04) << 1;
|
||||
di ^= nvh & (di & 0x08) << 1;
|
||||
di ^= nvh & (di & 0x10) << 1;
|
||||
di ^= nvh & (di & 0x20) << 1;
|
||||
di ^= nvh & (di & 0x40) << 1;
|
||||
d[i] = (byte)di;
|
||||
}
|
||||
|
||||
di = ((nvh & (di & 0x80) << 1) ^ vi) >> 8;
|
||||
|
||||
/* initialize state */
|
||||
set(yx[0], 1);
|
||||
cpy(yx[1], p[di]);
|
||||
cpy(yx[2], s[0]);
|
||||
set(yz[0], 0);
|
||||
set(yz[1], 1);
|
||||
set(yz[2], 1);
|
||||
|
||||
/* y[0] is (even)P + (even)G
|
||||
* y[1] is (even)P + (odd)G if current d-bit is 0
|
||||
* y[1] is (odd)P + (even)G if current d-bit is 1
|
||||
* y[2] is (odd)P + (odd)G
|
||||
*/
|
||||
|
||||
vi = 0;
|
||||
hi = 0;
|
||||
|
||||
/* and go for it! */
|
||||
for (i = 32; i--!=0; ) {
|
||||
vi = (vi << 8) | (v[i] & 0xFF);
|
||||
hi = (hi << 8) | (h[i] & 0xFF);
|
||||
di = (di << 8) | (d[i] & 0xFF);
|
||||
|
||||
for (j = 8; j--!=0; ) {
|
||||
mont_prep(t1[0], t2[0], yx[0], yz[0]);
|
||||
mont_prep(t1[1], t2[1], yx[1], yz[1]);
|
||||
mont_prep(t1[2], t2[2], yx[2], yz[2]);
|
||||
|
||||
k = ((vi ^ vi >> 1) >> j & 1)
|
||||
+ ((hi ^ hi >> 1) >> j & 1);
|
||||
mont_dbl(yx[2], yz[2], t1[k], t2[k], yx[0], yz[0]);
|
||||
|
||||
k = (di >> j & 2) ^ ((di >> j & 1) << 1);
|
||||
mont_add(t1[1], t2[1], t1[k], t2[k], yx[1], yz[1],
|
||||
p[di >> j & 1]);
|
||||
|
||||
mont_add(t1[2], t2[2], t1[0], t2[0], yx[2], yz[2],
|
||||
s[((vi ^ hi) >> j & 2) >> 1]);
|
||||
}
|
||||
}
|
||||
|
||||
k = (vi & 1) + (hi & 1);
|
||||
recip(t1[0], yz[k], 0);
|
||||
mul(t1[1], yx[k], t1[0]);
|
||||
|
||||
pack(t1[1], Y);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* sahn0:
|
||||
* Using this class instead of long[10] to avoid bounds checks. */
|
||||
private static final class long10 {
|
||||
public long10() {}
|
||||
public long10(
|
||||
long _0, long _1, long _2, long _3, long _4,
|
||||
long _5, long _6, long _7, long _8, long _9)
|
||||
{
|
||||
this._0=_0; this._1=_1; this._2=_2;
|
||||
this._3=_3; this._4=_4; this._5=_5;
|
||||
this._6=_6; this._7=_7; this._8=_8;
|
||||
this._9=_9;
|
||||
}
|
||||
public long _0,_1,_2,_3,_4,_5,_6,_7,_8,_9;
|
||||
}
|
||||
|
||||
/********************* radix 2^8 math *********************/
|
||||
|
||||
private static final void cpy32(byte[] d, byte[] s) {
|
||||
int i;
|
||||
for (i = 0; i < 32; i++)
|
||||
d[i] = s[i];
|
||||
}
|
||||
|
||||
/* p[m..n+m-1] = q[m..n+m-1] + z * x */
|
||||
/* n is the size of x */
|
||||
/* n+m is the size of p and q */
|
||||
private static final int mula_small(byte[] p,byte[] q,int m,byte[] x,int n,int z) {
|
||||
int v=0;
|
||||
for (int i=0;i<n;++i) {
|
||||
v+=(q[i+m] & 0xFF)+z*(x[i] & 0xFF);
|
||||
p[i+m]=(byte)v;
|
||||
v>>=8;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* p += x * y * z where z is a small integer
|
||||
* x is size 32, y is size t, p is size 32+t
|
||||
* y is allowed to overlap with p+32 if you don't care about the upper half */
|
||||
private static final int mula32(byte[] p, byte[] x, byte[] y, int t, int z) {
|
||||
final int n = 31;
|
||||
int w = 0;
|
||||
int i = 0;
|
||||
for (; i < t; i++) {
|
||||
int zy = z * (y[i] & 0xFF);
|
||||
w += mula_small(p, p, i, x, n, zy) +
|
||||
(p[i+n] & 0xFF) + zy * (x[n] & 0xFF);
|
||||
p[i+n] = (byte)w;
|
||||
w >>= 8;
|
||||
}
|
||||
p[i+n] = (byte)(w + (p[i+n] & 0xFF));
|
||||
return w >> 8;
|
||||
}
|
||||
|
||||
/* divide r (size n) by d (size t), returning quotient q and remainder r
|
||||
* quotient is size n-t+1, remainder is size t
|
||||
* requires t > 0 && d[t-1] != 0
|
||||
* requires that r[-1] and d[-1] are valid memory locations
|
||||
* q may overlap with r+t */
|
||||
private static final void divmod(byte[] q, byte[] r, int n, byte[] d, int t) {
|
||||
int rn = 0;
|
||||
int dt = ((d[t-1] & 0xFF) << 8);
|
||||
if (t>1) {
|
||||
dt |= (d[t-2] & 0xFF);
|
||||
}
|
||||
while (n-- >= t) {
|
||||
int z = (rn << 16) | ((r[n] & 0xFF) << 8);
|
||||
if (n>0) {
|
||||
z |= (r[n-1] & 0xFF);
|
||||
}
|
||||
z/=dt;
|
||||
rn += mula_small(r,r, n-t+1, d, t, -z);
|
||||
q[n-t+1] = (byte)((z + rn) & 0xFF); /* rn is 0 or -1 (underflow) */
|
||||
mula_small(r,r, n-t+1, d, t, -rn);
|
||||
rn = (r[n] & 0xFF);
|
||||
r[n] = 0;
|
||||
}
|
||||
r[t-1] = (byte)rn;
|
||||
}
|
||||
|
||||
private static final int numsize(byte[] x,int n) {
|
||||
while (n--!=0 && x[n]==0)
|
||||
;
|
||||
return n+1;
|
||||
}
|
||||
|
||||
/* Returns x if a contains the gcd, y if b.
|
||||
* Also, the returned buffer contains the inverse of a mod b,
|
||||
* as 32-byte signed.
|
||||
* x and y must have 64 bytes space for temporary use.
|
||||
* requires that a[-1] and b[-1] are valid memory locations */
|
||||
private static final byte[] egcd32(byte[] x,byte[] y,byte[] a,byte[] b) {
|
||||
int an, bn = 32, qn, i;
|
||||
for (i = 0; i < 32; i++)
|
||||
x[i] = y[i] = 0;
|
||||
x[0] = 1;
|
||||
an = numsize(a, 32);
|
||||
if (an==0)
|
||||
return y; /* division by zero */
|
||||
byte[] temp=new byte[32];
|
||||
while (true) {
|
||||
qn = bn - an + 1;
|
||||
divmod(temp, b, bn, a, an);
|
||||
bn = numsize(b, bn);
|
||||
if (bn==0)
|
||||
return x;
|
||||
mula32(y, x, temp, qn, -1);
|
||||
|
||||
qn = an - bn + 1;
|
||||
divmod(temp, a, an, b, bn);
|
||||
an = numsize(a, an);
|
||||
if (an==0)
|
||||
return y;
|
||||
mula32(x, y, temp, qn, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/********************* radix 2^25.5 GF(2^255-19) math *********************/
|
||||
|
||||
private static final int P25=33554431; /* (1 << 25) - 1 */
|
||||
private static final int P26=67108863; /* (1 << 26) - 1 */
|
||||
|
||||
/* Convert to internal format from little-endian byte format */
|
||||
private static final void unpack(long10 x,byte[] m) {
|
||||
x._0 = ((m[0] & 0xFF)) | ((m[1] & 0xFF))<<8 |
|
||||
(m[2] & 0xFF)<<16 | ((m[3] & 0xFF)& 3)<<24;
|
||||
x._1 = ((m[3] & 0xFF)&~ 3)>>2 | (m[4] & 0xFF)<<6 |
|
||||
(m[5] & 0xFF)<<14 | ((m[6] & 0xFF)& 7)<<22;
|
||||
x._2 = ((m[6] & 0xFF)&~ 7)>>3 | (m[7] & 0xFF)<<5 |
|
||||
(m[8] & 0xFF)<<13 | ((m[9] & 0xFF)&31)<<21;
|
||||
x._3 = ((m[9] & 0xFF)&~31)>>5 | (m[10] & 0xFF)<<3 |
|
||||
(m[11] & 0xFF)<<11 | ((m[12] & 0xFF)&63)<<19;
|
||||
x._4 = ((m[12] & 0xFF)&~63)>>6 | (m[13] & 0xFF)<<2 |
|
||||
(m[14] & 0xFF)<<10 | (m[15] & 0xFF) <<18;
|
||||
x._5 = (m[16] & 0xFF) | (m[17] & 0xFF)<<8 |
|
||||
(m[18] & 0xFF)<<16 | ((m[19] & 0xFF)& 1)<<24;
|
||||
x._6 = ((m[19] & 0xFF)&~ 1)>>1 | (m[20] & 0xFF)<<7 |
|
||||
(m[21] & 0xFF)<<15 | ((m[22] & 0xFF)& 7)<<23;
|
||||
x._7 = ((m[22] & 0xFF)&~ 7)>>3 | (m[23] & 0xFF)<<5 |
|
||||
(m[24] & 0xFF)<<13 | ((m[25] & 0xFF)&15)<<21;
|
||||
x._8 = ((m[25] & 0xFF)&~15)>>4 | (m[26] & 0xFF)<<4 |
|
||||
(m[27] & 0xFF)<<12 | ((m[28] & 0xFF)&63)<<20;
|
||||
x._9 = ((m[28] & 0xFF)&~63)>>6 | (m[29] & 0xFF)<<2 |
|
||||
(m[30] & 0xFF)<<10 | (m[31] & 0xFF) <<18;
|
||||
}
|
||||
|
||||
/* Check if reduced-form input >= 2^255-19 */
|
||||
private static final boolean is_overflow(long10 x) {
|
||||
return (
|
||||
((x._0 > P26-19)) &&
|
||||
((x._1 & x._3 & x._5 & x._7 & x._9) == P25) &&
|
||||
((x._2 & x._4 & x._6 & x._8) == P26)
|
||||
) || (x._9 > P25);
|
||||
}
|
||||
|
||||
/* Convert from internal format to little-endian byte format. The
|
||||
* number must be in a reduced form which is output by the following ops:
|
||||
* unpack, mul, sqr
|
||||
* set -- if input in range 0 .. P25
|
||||
* If you're unsure if the number is reduced, first multiply it by 1. */
|
||||
private static final void pack(long10 x,byte[] m) {
|
||||
int ld = 0, ud = 0;
|
||||
long t;
|
||||
ld = (is_overflow(x)?1:0) - ((x._9 < 0)?1:0);
|
||||
ud = ld * -(P25+1);
|
||||
ld *= 19;
|
||||
t = ld + x._0 + (x._1 << 26);
|
||||
m[ 0] = (byte)t;
|
||||
m[ 1] = (byte)(t >> 8);
|
||||
m[ 2] = (byte)(t >> 16);
|
||||
m[ 3] = (byte)(t >> 24);
|
||||
t = (t >> 32) + (x._2 << 19);
|
||||
m[ 4] = (byte)t;
|
||||
m[ 5] = (byte)(t >> 8);
|
||||
m[ 6] = (byte)(t >> 16);
|
||||
m[ 7] = (byte)(t >> 24);
|
||||
t = (t >> 32) + (x._3 << 13);
|
||||
m[ 8] = (byte)t;
|
||||
m[ 9] = (byte)(t >> 8);
|
||||
m[10] = (byte)(t >> 16);
|
||||
m[11] = (byte)(t >> 24);
|
||||
t = (t >> 32) + (x._4 << 6);
|
||||
m[12] = (byte)t;
|
||||
m[13] = (byte)(t >> 8);
|
||||
m[14] = (byte)(t >> 16);
|
||||
m[15] = (byte)(t >> 24);
|
||||
t = (t >> 32) + x._5 + (x._6 << 25);
|
||||
m[16] = (byte)t;
|
||||
m[17] = (byte)(t >> 8);
|
||||
m[18] = (byte)(t >> 16);
|
||||
m[19] = (byte)(t >> 24);
|
||||
t = (t >> 32) + (x._7 << 19);
|
||||
m[20] = (byte)t;
|
||||
m[21] = (byte)(t >> 8);
|
||||
m[22] = (byte)(t >> 16);
|
||||
m[23] = (byte)(t >> 24);
|
||||
t = (t >> 32) + (x._8 << 12);
|
||||
m[24] = (byte)t;
|
||||
m[25] = (byte)(t >> 8);
|
||||
m[26] = (byte)(t >> 16);
|
||||
m[27] = (byte)(t >> 24);
|
||||
t = (t >> 32) + ((x._9 + ud) << 6);
|
||||
m[28] = (byte)t;
|
||||
m[29] = (byte)(t >> 8);
|
||||
m[30] = (byte)(t >> 16);
|
||||
m[31] = (byte)(t >> 24);
|
||||
}
|
||||
|
||||
/* Copy a number */
|
||||
private static final void cpy(long10 out, long10 in) {
|
||||
out._0=in._0; out._1=in._1;
|
||||
out._2=in._2; out._3=in._3;
|
||||
out._4=in._4; out._5=in._5;
|
||||
out._6=in._6; out._7=in._7;
|
||||
out._8=in._8; out._9=in._9;
|
||||
}
|
||||
|
||||
/* Set a number to value, which must be in range -185861411 .. 185861411 */
|
||||
private static final void set(long10 out, int in) {
|
||||
out._0=in; out._1=0;
|
||||
out._2=0; out._3=0;
|
||||
out._4=0; out._5=0;
|
||||
out._6=0; out._7=0;
|
||||
out._8=0; out._9=0;
|
||||
}
|
||||
|
||||
/* Add/subtract two numbers. The inputs must be in reduced form, and the
|
||||
* output isn't, so to do another addition or subtraction on the output,
|
||||
* first multiply it by one to reduce it. */
|
||||
private static final void add(long10 xy, long10 x, long10 y) {
|
||||
xy._0 = x._0 + y._0; xy._1 = x._1 + y._1;
|
||||
xy._2 = x._2 + y._2; xy._3 = x._3 + y._3;
|
||||
xy._4 = x._4 + y._4; xy._5 = x._5 + y._5;
|
||||
xy._6 = x._6 + y._6; xy._7 = x._7 + y._7;
|
||||
xy._8 = x._8 + y._8; xy._9 = x._9 + y._9;
|
||||
}
|
||||
private static final void sub(long10 xy, long10 x, long10 y) {
|
||||
xy._0 = x._0 - y._0; xy._1 = x._1 - y._1;
|
||||
xy._2 = x._2 - y._2; xy._3 = x._3 - y._3;
|
||||
xy._4 = x._4 - y._4; xy._5 = x._5 - y._5;
|
||||
xy._6 = x._6 - y._6; xy._7 = x._7 - y._7;
|
||||
xy._8 = x._8 - y._8; xy._9 = x._9 - y._9;
|
||||
}
|
||||
|
||||
/* Multiply a number by a small integer in range -185861411 .. 185861411.
|
||||
* The output is in reduced form, the input x need not be. x and xy may point
|
||||
* to the same buffer. */
|
||||
private static final long10 mul_small(long10 xy, long10 x, long y) {
|
||||
long t;
|
||||
t = (x._8*y);
|
||||
xy._8 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x._9*y);
|
||||
xy._9 = (t & ((1 << 25) - 1));
|
||||
t = 19 * (t >> 25) + (x._0*y);
|
||||
xy._0 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x._1*y);
|
||||
xy._1 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x._2*y);
|
||||
xy._2 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x._3*y);
|
||||
xy._3 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x._4*y);
|
||||
xy._4 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x._5*y);
|
||||
xy._5 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x._6*y);
|
||||
xy._6 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x._7*y);
|
||||
xy._7 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + xy._8;
|
||||
xy._8 = (t & ((1 << 26) - 1));
|
||||
xy._9 += (t >> 26);
|
||||
return xy;
|
||||
}
|
||||
|
||||
/* Multiply two numbers. The output is in reduced form, the inputs need not
|
||||
* be. */
|
||||
private static final long10 mul(long10 xy, long10 x, long10 y) {
|
||||
/* sahn0:
|
||||
* Using local variables to avoid class access.
|
||||
* This seem to improve performance a bit...
|
||||
*/
|
||||
long
|
||||
x_0=x._0,x_1=x._1,x_2=x._2,x_3=x._3,x_4=x._4,
|
||||
x_5=x._5,x_6=x._6,x_7=x._7,x_8=x._8,x_9=x._9;
|
||||
long
|
||||
y_0=y._0,y_1=y._1,y_2=y._2,y_3=y._3,y_4=y._4,
|
||||
y_5=y._5,y_6=y._6,y_7=y._7,y_8=y._8,y_9=y._9;
|
||||
long t;
|
||||
t = (x_0*y_8) + (x_2*y_6) + (x_4*y_4) + (x_6*y_2) +
|
||||
(x_8*y_0) + 2 * ((x_1*y_7) + (x_3*y_5) +
|
||||
(x_5*y_3) + (x_7*y_1)) + 38 *
|
||||
(x_9*y_9);
|
||||
xy._8 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x_0*y_9) + (x_1*y_8) + (x_2*y_7) +
|
||||
(x_3*y_6) + (x_4*y_5) + (x_5*y_4) +
|
||||
(x_6*y_3) + (x_7*y_2) + (x_8*y_1) +
|
||||
(x_9*y_0);
|
||||
xy._9 = (t & ((1 << 25) - 1));
|
||||
t = (x_0*y_0) + 19 * ((t >> 25) + (x_2*y_8) + (x_4*y_6)
|
||||
+ (x_6*y_4) + (x_8*y_2)) + 38 *
|
||||
((x_1*y_9) + (x_3*y_7) + (x_5*y_5) +
|
||||
(x_7*y_3) + (x_9*y_1));
|
||||
xy._0 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x_0*y_1) + (x_1*y_0) + 19 * ((x_2*y_9)
|
||||
+ (x_3*y_8) + (x_4*y_7) + (x_5*y_6) +
|
||||
(x_6*y_5) + (x_7*y_4) + (x_8*y_3) +
|
||||
(x_9*y_2));
|
||||
xy._1 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x_0*y_2) + (x_2*y_0) + 19 * ((x_4*y_8)
|
||||
+ (x_6*y_6) + (x_8*y_4)) + 2 * (x_1*y_1)
|
||||
+ 38 * ((x_3*y_9) + (x_5*y_7) +
|
||||
(x_7*y_5) + (x_9*y_3));
|
||||
xy._2 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x_0*y_3) + (x_1*y_2) + (x_2*y_1) +
|
||||
(x_3*y_0) + 19 * ((x_4*y_9) + (x_5*y_8) +
|
||||
(x_6*y_7) + (x_7*y_6) +
|
||||
(x_8*y_5) + (x_9*y_4));
|
||||
xy._3 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x_0*y_4) + (x_2*y_2) + (x_4*y_0) + 19 *
|
||||
((x_6*y_8) + (x_8*y_6)) + 2 * ((x_1*y_3) +
|
||||
(x_3*y_1)) + 38 *
|
||||
((x_5*y_9) + (x_7*y_7) + (x_9*y_5));
|
||||
xy._4 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x_0*y_5) + (x_1*y_4) + (x_2*y_3) +
|
||||
(x_3*y_2) + (x_4*y_1) + (x_5*y_0) + 19 *
|
||||
((x_6*y_9) + (x_7*y_8) + (x_8*y_7) +
|
||||
(x_9*y_6));
|
||||
xy._5 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x_0*y_6) + (x_2*y_4) + (x_4*y_2) +
|
||||
(x_6*y_0) + 19 * (x_8*y_8) + 2 * ((x_1*y_5) +
|
||||
(x_3*y_3) + (x_5*y_1)) + 38 *
|
||||
((x_7*y_9) + (x_9*y_7));
|
||||
xy._6 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + (x_0*y_7) + (x_1*y_6) + (x_2*y_5) +
|
||||
(x_3*y_4) + (x_4*y_3) + (x_5*y_2) +
|
||||
(x_6*y_1) + (x_7*y_0) + 19 * ((x_8*y_9) +
|
||||
(x_9*y_8));
|
||||
xy._7 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + xy._8;
|
||||
xy._8 = (t & ((1 << 26) - 1));
|
||||
xy._9 += (t >> 26);
|
||||
return xy;
|
||||
}
|
||||
|
||||
/* Square a number. Optimization of mul25519(x2, x, x) */
|
||||
private static final long10 sqr(long10 x2, long10 x) {
|
||||
long
|
||||
x_0=x._0,x_1=x._1,x_2=x._2,x_3=x._3,x_4=x._4,
|
||||
x_5=x._5,x_6=x._6,x_7=x._7,x_8=x._8,x_9=x._9;
|
||||
long t;
|
||||
t = (x_4*x_4) + 2 * ((x_0*x_8) + (x_2*x_6)) + 38 *
|
||||
(x_9*x_9) + 4 * ((x_1*x_7) + (x_3*x_5));
|
||||
x2._8 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + 2 * ((x_0*x_9) + (x_1*x_8) + (x_2*x_7) +
|
||||
(x_3*x_6) + (x_4*x_5));
|
||||
x2._9 = (t & ((1 << 25) - 1));
|
||||
t = 19 * (t >> 25) + (x_0*x_0) + 38 * ((x_2*x_8) +
|
||||
(x_4*x_6) + (x_5*x_5)) + 76 * ((x_1*x_9)
|
||||
+ (x_3*x_7));
|
||||
x2._0 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + 2 * (x_0*x_1) + 38 * ((x_2*x_9) +
|
||||
(x_3*x_8) + (x_4*x_7) + (x_5*x_6));
|
||||
x2._1 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + 19 * (x_6*x_6) + 2 * ((x_0*x_2) +
|
||||
(x_1*x_1)) + 38 * (x_4*x_8) + 76 *
|
||||
((x_3*x_9) + (x_5*x_7));
|
||||
x2._2 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + 2 * ((x_0*x_3) + (x_1*x_2)) + 38 *
|
||||
((x_4*x_9) + (x_5*x_8) + (x_6*x_7));
|
||||
x2._3 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + (x_2*x_2) + 2 * (x_0*x_4) + 38 *
|
||||
((x_6*x_8) + (x_7*x_7)) + 4 * (x_1*x_3) + 76 *
|
||||
(x_5*x_9);
|
||||
x2._4 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + 2 * ((x_0*x_5) + (x_1*x_4) + (x_2*x_3))
|
||||
+ 38 * ((x_6*x_9) + (x_7*x_8));
|
||||
x2._5 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + 19 * (x_8*x_8) + 2 * ((x_0*x_6) +
|
||||
(x_2*x_4) + (x_3*x_3)) + 4 * (x_1*x_5) +
|
||||
76 * (x_7*x_9);
|
||||
x2._6 = (t & ((1 << 26) - 1));
|
||||
t = (t >> 26) + 2 * ((x_0*x_7) + (x_1*x_6) + (x_2*x_5) +
|
||||
(x_3*x_4)) + 38 * (x_8*x_9);
|
||||
x2._7 = (t & ((1 << 25) - 1));
|
||||
t = (t >> 25) + x2._8;
|
||||
x2._8 = (t & ((1 << 26) - 1));
|
||||
x2._9 += (t >> 26);
|
||||
return x2;
|
||||
}
|
||||
|
||||
/* Calculates a reciprocal. The output is in reduced form, the inputs need not
|
||||
* be. Simply calculates y = x^(p-2) so it's not too fast. */
|
||||
/* When sqrtassist is true, it instead calculates y = x^((p-5)/8) */
|
||||
private static final void recip(long10 y, long10 x, int sqrtassist) {
|
||||
long10
|
||||
t0=new long10(),
|
||||
t1=new long10(),
|
||||
t2=new long10(),
|
||||
t3=new long10(),
|
||||
t4=new long10();
|
||||
int i;
|
||||
/* the chain for x^(2^255-21) is straight from djb's implementation */
|
||||
sqr(t1, x); /* 2 == 2 * 1 */
|
||||
sqr(t2, t1); /* 4 == 2 * 2 */
|
||||
sqr(t0, t2); /* 8 == 2 * 4 */
|
||||
mul(t2, t0, x); /* 9 == 8 + 1 */
|
||||
mul(t0, t2, t1); /* 11 == 9 + 2 */
|
||||
sqr(t1, t0); /* 22 == 2 * 11 */
|
||||
mul(t3, t1, t2); /* 31 == 22 + 9
|
||||
== 2^5 - 2^0 */
|
||||
sqr(t1, t3); /* 2^6 - 2^1 */
|
||||
sqr(t2, t1); /* 2^7 - 2^2 */
|
||||
sqr(t1, t2); /* 2^8 - 2^3 */
|
||||
sqr(t2, t1); /* 2^9 - 2^4 */
|
||||
sqr(t1, t2); /* 2^10 - 2^5 */
|
||||
mul(t2, t1, t3); /* 2^10 - 2^0 */
|
||||
sqr(t1, t2); /* 2^11 - 2^1 */
|
||||
sqr(t3, t1); /* 2^12 - 2^2 */
|
||||
for (i = 1; i < 5; i++) {
|
||||
sqr(t1, t3);
|
||||
sqr(t3, t1);
|
||||
} /* t3 */ /* 2^20 - 2^10 */
|
||||
mul(t1, t3, t2); /* 2^20 - 2^0 */
|
||||
sqr(t3, t1); /* 2^21 - 2^1 */
|
||||
sqr(t4, t3); /* 2^22 - 2^2 */
|
||||
for (i = 1; i < 10; i++) {
|
||||
sqr(t3, t4);
|
||||
sqr(t4, t3);
|
||||
} /* t4 */ /* 2^40 - 2^20 */
|
||||
mul(t3, t4, t1); /* 2^40 - 2^0 */
|
||||
for (i = 0; i < 5; i++) {
|
||||
sqr(t1, t3);
|
||||
sqr(t3, t1);
|
||||
} /* t3 */ /* 2^50 - 2^10 */
|
||||
mul(t1, t3, t2); /* 2^50 - 2^0 */
|
||||
sqr(t2, t1); /* 2^51 - 2^1 */
|
||||
sqr(t3, t2); /* 2^52 - 2^2 */
|
||||
for (i = 1; i < 25; i++) {
|
||||
sqr(t2, t3);
|
||||
sqr(t3, t2);
|
||||
} /* t3 */ /* 2^100 - 2^50 */
|
||||
mul(t2, t3, t1); /* 2^100 - 2^0 */
|
||||
sqr(t3, t2); /* 2^101 - 2^1 */
|
||||
sqr(t4, t3); /* 2^102 - 2^2 */
|
||||
for (i = 1; i < 50; i++) {
|
||||
sqr(t3, t4);
|
||||
sqr(t4, t3);
|
||||
} /* t4 */ /* 2^200 - 2^100 */
|
||||
mul(t3, t4, t2); /* 2^200 - 2^0 */
|
||||
for (i = 0; i < 25; i++) {
|
||||
sqr(t4, t3);
|
||||
sqr(t3, t4);
|
||||
} /* t3 */ /* 2^250 - 2^50 */
|
||||
mul(t2, t3, t1); /* 2^250 - 2^0 */
|
||||
sqr(t1, t2); /* 2^251 - 2^1 */
|
||||
sqr(t2, t1); /* 2^252 - 2^2 */
|
||||
if (sqrtassist!=0) {
|
||||
mul(y, x, t2); /* 2^252 - 3 */
|
||||
} else {
|
||||
sqr(t1, t2); /* 2^253 - 2^3 */
|
||||
sqr(t2, t1); /* 2^254 - 2^4 */
|
||||
sqr(t1, t2); /* 2^255 - 2^5 */
|
||||
mul(y, t1, t0); /* 2^255 - 21 */
|
||||
}
|
||||
}
|
||||
|
||||
/* checks if x is "negative", requires reduced input */
|
||||
private static final int is_negative(long10 x) {
|
||||
return (int)(((is_overflow(x) || (x._9 < 0))?1:0) ^ (x._0 & 1));
|
||||
}
|
||||
|
||||
/* a square root */
|
||||
private static final void sqrt(long10 x, long10 u) {
|
||||
long10 v=new long10(), t1=new long10(), t2=new long10();
|
||||
add(t1, u, u); /* t1 = 2u */
|
||||
recip(v, t1, 1); /* v = (2u)^((p-5)/8) */
|
||||
sqr(x, v); /* x = v^2 */
|
||||
mul(t2, t1, x); /* t2 = 2uv^2 */
|
||||
t2._0--; /* t2 = 2uv^2-1 */
|
||||
mul(t1, v, t2); /* t1 = v(2uv^2-1) */
|
||||
mul(x, u, t1); /* x = uv(2uv^2-1) */
|
||||
}
|
||||
|
||||
/********************* Elliptic curve *********************/
|
||||
|
||||
/* y^2 = x^3 + 486662 x^2 + x over GF(2^255-19) */
|
||||
|
||||
/* t1 = ax + az
|
||||
* t2 = ax - az */
|
||||
private static final void mont_prep(long10 t1, long10 t2, long10 ax, long10 az) {
|
||||
add(t1, ax, az);
|
||||
sub(t2, ax, az);
|
||||
}
|
||||
|
||||
/* A = P + Q where
|
||||
* X(A) = ax/az
|
||||
* X(P) = (t1+t2)/(t1-t2)
|
||||
* X(Q) = (t3+t4)/(t3-t4)
|
||||
* X(P-Q) = dx
|
||||
* clobbers t1 and t2, preserves t3 and t4 */
|
||||
private static final void mont_add(long10 t1, long10 t2, long10 t3, long10 t4,long10 ax, long10 az, long10 dx) {
|
||||
mul(ax, t2, t3);
|
||||
mul(az, t1, t4);
|
||||
add(t1, ax, az);
|
||||
sub(t2, ax, az);
|
||||
sqr(ax, t1);
|
||||
sqr(t1, t2);
|
||||
mul(az, t1, dx);
|
||||
}
|
||||
|
||||
/* B = 2 * Q where
|
||||
* X(B) = bx/bz
|
||||
* X(Q) = (t3+t4)/(t3-t4)
|
||||
* clobbers t1 and t2, preserves t3 and t4 */
|
||||
private static final void mont_dbl(long10 t1, long10 t2, long10 t3, long10 t4,long10 bx, long10 bz) {
|
||||
sqr(t1, t3);
|
||||
sqr(t2, t4);
|
||||
mul(bx, t1, t2);
|
||||
sub(t2, t1, t2);
|
||||
mul_small(bz, t2, 121665);
|
||||
add(t1, t1, bz);
|
||||
mul(bz, t1, t2);
|
||||
}
|
||||
|
||||
/* Y^2 = X^3 + 486662 X^2 + X
|
||||
* t is a temporary */
|
||||
private static final void x_to_y2(long10 t, long10 y2, long10 x) {
|
||||
sqr(t, x);
|
||||
mul_small(y2, x, 486662);
|
||||
add(t, t, y2);
|
||||
t._0++;
|
||||
mul(y2, t, x);
|
||||
}
|
||||
|
||||
/* P = kG and s = sign(P)/k */
|
||||
private static final void core(byte[] Px, byte[] s, byte[] k, byte[] Gx) {
|
||||
long10
|
||||
dx=new long10(),
|
||||
t1=new long10(),
|
||||
t2=new long10(),
|
||||
t3=new long10(),
|
||||
t4=new long10();
|
||||
long10[]
|
||||
x=new long10[]{new long10(),new long10()},
|
||||
z=new long10[]{new long10(),new long10()};
|
||||
int i, j;
|
||||
|
||||
/* unpack the base */
|
||||
if (Gx!=null)
|
||||
unpack(dx, Gx);
|
||||
else
|
||||
set(dx, 9);
|
||||
|
||||
/* 0G = point-at-infinity */
|
||||
set(x[0], 1);
|
||||
set(z[0], 0);
|
||||
|
||||
/* 1G = G */
|
||||
cpy(x[1], dx);
|
||||
set(z[1], 1);
|
||||
|
||||
for (i = 32; i--!=0; ) {
|
||||
if (i==0) {
|
||||
i=0;
|
||||
}
|
||||
for (j = 8; j--!=0; ) {
|
||||
/* swap arguments depending on bit */
|
||||
int bit1 = (k[i] & 0xFF) >> j & 1;
|
||||
int bit0 = ~(k[i] & 0xFF) >> j & 1;
|
||||
long10 ax = x[bit0];
|
||||
long10 az = z[bit0];
|
||||
long10 bx = x[bit1];
|
||||
long10 bz = z[bit1];
|
||||
|
||||
/* a' = a + b */
|
||||
/* b' = 2 b */
|
||||
mont_prep(t1, t2, ax, az);
|
||||
mont_prep(t3, t4, bx, bz);
|
||||
mont_add(t1, t2, t3, t4, ax, az, dx);
|
||||
mont_dbl(t1, t2, t3, t4, bx, bz);
|
||||
}
|
||||
}
|
||||
|
||||
recip(t1, z[0], 0);
|
||||
mul(dx, x[0], t1);
|
||||
pack(dx, Px);
|
||||
|
||||
/* calculate s such that s abs(P) = G .. assumes G is std base point */
|
||||
if (s!=null) {
|
||||
x_to_y2(t2, t1, dx); /* t1 = Py^2 */
|
||||
recip(t3, z[1], 0); /* where Q=P+G ... */
|
||||
mul(t2, x[1], t3); /* t2 = Qx */
|
||||
add(t2, t2, dx); /* t2 = Qx + Px */
|
||||
t2._0 += 9 + 486662; /* t2 = Qx + Px + Gx + 486662 */
|
||||
dx._0 -= 9; /* dx = Px - Gx */
|
||||
sqr(t3, dx); /* t3 = (Px - Gx)^2 */
|
||||
mul(dx, t2, t3); /* dx = t2 (Px - Gx)^2 */
|
||||
sub(dx, dx, t1); /* dx = t2 (Px - Gx)^2 - Py^2 */
|
||||
dx._0 -= 39420360; /* dx = t2 (Px - Gx)^2 - Py^2 - Gy^2 */
|
||||
mul(t1, dx, BASE_R2Y); /* t1 = -Py */
|
||||
if (is_negative(t1)!=0) /* sign is 1, so just copy */
|
||||
cpy32(s, k);
|
||||
else /* sign is -1, so negate */
|
||||
mula_small(s, ORDER_TIMES_8, 0, k, 32, -1);
|
||||
|
||||
/* reduce s mod q
|
||||
* (is this needed? do it just in case, it's fast anyway) */
|
||||
//divmod((dstptr) t1, s, 32, order25519, 32);
|
||||
|
||||
/* take reciprocal of s mod q */
|
||||
byte[] temp1=new byte[32];
|
||||
byte[] temp2=new byte[64];
|
||||
byte[] temp3=new byte[64];
|
||||
cpy32(temp1, ORDER);
|
||||
cpy32(s, egcd32(temp2, temp3, s, temp1));
|
||||
if ((s[31] & 0x80)!=0)
|
||||
mula_small(s, s, 0, ORDER, 32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* smallest multiple of the order that's >= 2^255 */
|
||||
private static final byte[] ORDER_TIMES_8 = {
|
||||
(byte)104, (byte)159, (byte)174, (byte)231,
|
||||
(byte)210, (byte)24, (byte)147, (byte)192,
|
||||
(byte)178, (byte)230, (byte)188, (byte)23,
|
||||
(byte)245, (byte)206, (byte)247, (byte)166,
|
||||
(byte)0, (byte)0, (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, (byte)0, (byte)128
|
||||
};
|
||||
|
||||
/* constants 2Gy and 1/(2Gy) */
|
||||
private static final long10 BASE_2Y = new long10(
|
||||
39999547, 18689728, 59995525, 1648697, 57546132,
|
||||
24010086, 19059592, 5425144, 63499247, 16420658
|
||||
);
|
||||
private static final long10 BASE_R2Y = new long10(
|
||||
5744, 8160848, 4790893, 13779497, 35730846,
|
||||
12541209, 49101323, 30047407, 40071253, 6226132
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package net.schmizz.concurrent;
|
||||
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -42,8 +44,8 @@ public class Event<T extends Throwable> {
|
||||
* @param name name of this event
|
||||
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
|
||||
*/
|
||||
public Event(String name, ExceptionChainer<T> chainer) {
|
||||
promise = new Promise<Object, T>(name, chainer);
|
||||
public Event(String name, ExceptionChainer<T> chainer, LoggerFactory loggerFactory) {
|
||||
promise = new Promise<Object, T>(name, chainer, loggerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,8 +55,8 @@ public class Event<T extends Throwable> {
|
||||
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
|
||||
* @param lock lock to use
|
||||
*/
|
||||
public Event(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
|
||||
promise = new Promise<Object, T>(name, chainer, lock);
|
||||
public Event(String name, ExceptionChainer<T> chainer, ReentrantLock lock, LoggerFactory loggerFactory) {
|
||||
promise = new Promise<Object, T>(name, chainer, lock, loggerFactory);
|
||||
}
|
||||
|
||||
/** Sets this event to be {@code true}. Short for {@code set(true)}. */
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package net.schmizz.concurrent;
|
||||
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -32,8 +32,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
*/
|
||||
public class Promise<V, T extends Throwable> {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Logger log;
|
||||
private final String name;
|
||||
private final ExceptionChainer<T> chainer;
|
||||
private final ReentrantLock lock;
|
||||
@@ -49,8 +48,8 @@ public class Promise<V, T extends Throwable> {
|
||||
* @param name name of this promise
|
||||
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
|
||||
*/
|
||||
public Promise(String name, ExceptionChainer<T> chainer) {
|
||||
this(name, chainer, null);
|
||||
public Promise(String name, ExceptionChainer<T> chainer, LoggerFactory loggerFactory) {
|
||||
this(name, chainer, null, loggerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,10 +59,11 @@ public class Promise<V, T extends Throwable> {
|
||||
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
|
||||
* @param lock lock to use
|
||||
*/
|
||||
public Promise(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
|
||||
public Promise(String name, ExceptionChainer<T> chainer, ReentrantLock lock, LoggerFactory loggerFactory) {
|
||||
this.name = name;
|
||||
this.chainer = chainer;
|
||||
this.lock = lock == null ? new ReentrantLock() : lock;
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
this.cond = this.lock.newCondition();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,16 @@ import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.connection.ConnectionImpl;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class KeepAlive extends Thread {
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected final Logger log;
|
||||
protected final ConnectionImpl conn;
|
||||
|
||||
protected int keepAliveInterval = 0;
|
||||
|
||||
protected KeepAlive(ConnectionImpl conn, String name) {
|
||||
this.conn = conn;
|
||||
log = conn.getTransport().getConfig().getLoggerFactory().getLogger(getClass());
|
||||
setName(name);
|
||||
}
|
||||
|
||||
@@ -65,6 +64,8 @@ public abstract class KeepAlive extends Thread {
|
||||
}
|
||||
Thread.sleep(hi * 1000);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Interrupt signal may be catched when sleeping.
|
||||
} catch (Exception e) {
|
||||
// If we weren't interrupted, kill the transport, then this exception was unexpected.
|
||||
// Else we're in shutdown-mode already, so don't forcibly kill the transport.
|
||||
|
||||
@@ -22,14 +22,13 @@ import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** An abstract class for {@link Service} that implements common or default functionality. */
|
||||
public abstract class AbstractService
|
||||
implements Service {
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final Logger log;
|
||||
|
||||
/** Assigned name of this service */
|
||||
protected final String name;
|
||||
@@ -39,6 +38,7 @@ public abstract class AbstractService
|
||||
public AbstractService(String name, Transport trans) {
|
||||
this.name = name;
|
||||
this.trans = trans;
|
||||
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,12 +15,31 @@
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import com.hierynomus.sshj.signature.SignatureEdDSA;
|
||||
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.signature.SignatureDSA;
|
||||
import net.schmizz.sshj.signature.SignatureRSA;
|
||||
import net.schmizz.sshj.transport.random.JCERandom;
|
||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||
|
||||
/**
|
||||
* Registers SpongyCastle as JCE provider.
|
||||
*/
|
||||
public class AndroidConfig
|
||||
extends DefaultConfig {
|
||||
|
||||
static {
|
||||
SecurityUtils.registerSecurityProvider("org.spongycastle.jce.provider.BouncyCastleProvider");
|
||||
}
|
||||
|
||||
// don't add ECDSA
|
||||
protected void initSignatureFactories() {
|
||||
setSignatureFactories(new SignatureRSA.Factory(), new SignatureDSA.Factory(),
|
||||
// but add EdDSA
|
||||
new SignatureEdDSA.Factory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initRandomFactory(boolean ignored) {
|
||||
setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory()));
|
||||
|
||||
@@ -17,6 +17,7 @@ package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.signature.Signature;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.compression.Compression;
|
||||
@@ -175,4 +176,14 @@ public interface Config {
|
||||
* @param waitForServerIdentBeforeSendingClientIdent Whether to wait for the server ident.
|
||||
*/
|
||||
void setWaitForServerIdentBeforeSendingClientIdent(boolean waitForServerIdentBeforeSendingClientIdent);
|
||||
|
||||
/**
|
||||
* Sets the LoggerFactory to use.
|
||||
*/
|
||||
void setLoggerFactory(LoggerFactory loggerFactory);
|
||||
|
||||
/**
|
||||
* @return The LoggerFactory the SSHClient will use.
|
||||
*/
|
||||
LoggerFactory getLoggerFactory();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.signature.Signature;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.compression.Compression;
|
||||
@@ -45,6 +46,7 @@ public class ConfigImpl
|
||||
private List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories;
|
||||
|
||||
private boolean waitForServerIdentBeforeSendingClientIdent = false;
|
||||
private LoggerFactory loggerFactory;
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<Cipher>> getCipherFactories() {
|
||||
@@ -169,4 +171,14 @@ public class ConfigImpl
|
||||
public void setWaitForServerIdentBeforeSendingClientIdent(boolean waitForServerIdentBeforeSendingClientIdent) {
|
||||
this.waitForServerIdentBeforeSendingClientIdent = waitForServerIdentBeforeSendingClientIdent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggerFactory getLoggerFactory() {
|
||||
return loggerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoggerFactory(LoggerFactory loggerFactory) {
|
||||
this.loggerFactory = loggerFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,44 +18,34 @@ package net.schmizz.sshj;
|
||||
import com.hierynomus.sshj.signature.SignatureEdDSA;
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
import com.hierynomus.sshj.transport.cipher.StreamCiphers;
|
||||
import com.hierynomus.sshj.transport.kex.DHGroups;
|
||||
import com.hierynomus.sshj.transport.kex.ExtendedDHGroups;
|
||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.signature.SignatureDSA;
|
||||
import net.schmizz.sshj.signature.SignatureECDSA;
|
||||
import net.schmizz.sshj.signature.SignatureRSA;
|
||||
import net.schmizz.sshj.transport.cipher.AES128CBC;
|
||||
import net.schmizz.sshj.transport.cipher.AES128CTR;
|
||||
import net.schmizz.sshj.transport.cipher.AES192CBC;
|
||||
import net.schmizz.sshj.transport.cipher.AES192CTR;
|
||||
import net.schmizz.sshj.transport.cipher.AES256CBC;
|
||||
import net.schmizz.sshj.transport.cipher.AES256CTR;
|
||||
import net.schmizz.sshj.transport.cipher.BlowfishCBC;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.cipher.TripleDESCBC;
|
||||
import net.schmizz.sshj.transport.cipher.*;
|
||||
import net.schmizz.sshj.transport.compression.NoneCompression;
|
||||
import net.schmizz.sshj.transport.kex.*;
|
||||
import net.schmizz.sshj.transport.mac.HMACMD5;
|
||||
import net.schmizz.sshj.transport.mac.HMACMD596;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA1;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA196;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA2256;
|
||||
import net.schmizz.sshj.transport.mac.HMACSHA2512;
|
||||
import net.schmizz.sshj.transport.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;
|
||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.image.ByteLookupTable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
|
||||
@@ -63,9 +53,7 @@ import java.util.List;
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>{@link net.schmizz.sshj.ConfigImpl#setKeyExchangeFactories Key exchange}: {@link net.schmizz.sshj.transport.kex.DHG14}*, {@link net.schmizz.sshj.transport.kex.DHG1}</li>
|
||||
* <li>{@link net.schmizz.sshj.ConfigImpl#setCipherFactories Ciphers} [1]: {@link net.schmizz.sshj.transport.cipher.AES128CTR}, {@link net.schmizz.sshj.transport.cipher.AES192CTR}, {@link net.schmizz.sshj.transport.cipher.AES256CTR},
|
||||
* {@link
|
||||
* net.schmizz.sshj.transport.cipher.AES128CBC}, {@link net.schmizz.sshj.transport.cipher.AES192CBC}, {@link net.schmizz.sshj.transport.cipher.AES256CBC}, {@link net.schmizz.sshj.transport.cipher.AES192CBC}, {@link net.schmizz.sshj.transport.cipher.TripleDESCBC}, {@link net.schmizz.sshj.transport.cipher.BlowfishCBC}</li>
|
||||
* <li>{@link net.schmizz.sshj.ConfigImpl#setCipherFactories Ciphers}: {@link BlockCiphers}, {@link StreamCiphers} [1]</li>
|
||||
* <li>{@link net.schmizz.sshj.ConfigImpl#setMACFactories MAC}: {@link net.schmizz.sshj.transport.mac.HMACSHA1}, {@link net.schmizz.sshj.transport.mac.HMACSHA196}, {@link net.schmizz.sshj.transport.mac.HMACMD5}, {@link
|
||||
* net.schmizz.sshj.transport.mac.HMACMD596}</li>
|
||||
* <li>{@link net.schmizz.sshj.ConfigImpl#setCompressionFactories Compression}: {@link net.schmizz.sshj.transport.compression.NoneCompression}</li>
|
||||
@@ -82,12 +70,11 @@ import java.util.List;
|
||||
public class DefaultConfig
|
||||
extends ConfigImpl {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private static final String VERSION = "SSHJ_0_17_2";
|
||||
private Logger log;
|
||||
|
||||
public DefaultConfig() {
|
||||
setVersion(VERSION);
|
||||
setLoggerFactory(LoggerFactory.DEFAULT);
|
||||
setVersion(readVersionFromProperties());
|
||||
final boolean bouncyCastleRegistered = SecurityUtils.isBouncyCastleRegistered();
|
||||
initKeyExchangeFactories(bouncyCastleRegistered);
|
||||
initRandomFactory(bouncyCastleRegistered);
|
||||
@@ -99,18 +86,50 @@ public class DefaultConfig
|
||||
setKeepAliveProvider(KeepAliveProvider.HEARTBEAT);
|
||||
}
|
||||
|
||||
private String readVersionFromProperties() {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(DefaultConfig.class.getClassLoader().getResourceAsStream("sshj.properties"));
|
||||
String property = properties.getProperty("sshj.version");
|
||||
return "SSHJ_" + property.replace('-', '_'); // '-' is a disallowed character, see RFC-4253#section-4.2
|
||||
} catch (Exception e) {
|
||||
log.error("Could not read the sshj.properties file, returning an 'unknown' version as fallback.");
|
||||
return "SSHJ_VERSION_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoggerFactory(LoggerFactory loggerFactory) {
|
||||
super.setLoggerFactory(loggerFactory);
|
||||
log = loggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) {
|
||||
if (bouncyCastleRegistered)
|
||||
if (bouncyCastleRegistered) {
|
||||
setKeyExchangeFactories(new Curve25519SHA256.Factory(),
|
||||
new DHGexSHA256.Factory(),
|
||||
new ECDHNistP.Factory521(),
|
||||
new ECDHNistP.Factory384(),
|
||||
new ECDHNistP.Factory256(),
|
||||
new DHGexSHA1.Factory(),
|
||||
new DHG14.Factory(),
|
||||
new DHG1.Factory());
|
||||
else
|
||||
setKeyExchangeFactories(new DHG1.Factory(), new DHGexSHA1.Factory());
|
||||
DHGroups.Group1SHA1(),
|
||||
DHGroups.Group14SHA1(),
|
||||
DHGroups.Group14SHA256(),
|
||||
DHGroups.Group15SHA512(),
|
||||
DHGroups.Group16SHA512(),
|
||||
DHGroups.Group17SHA512(),
|
||||
DHGroups.Group18SHA512(),
|
||||
ExtendedDHGroups.Group14SHA256AtSSH(),
|
||||
ExtendedDHGroups.Group15SHA256(),
|
||||
ExtendedDHGroups.Group15SHA256AtSSH(),
|
||||
ExtendedDHGroups.Group15SHA384AtSSH(),
|
||||
ExtendedDHGroups.Group16SHA256(),
|
||||
ExtendedDHGroups.Group16SHA384AtSSH(),
|
||||
ExtendedDHGroups.Group16SHA512AtSSH(),
|
||||
ExtendedDHGroups.Group18SHA512AtSSH());
|
||||
} else {
|
||||
setKeyExchangeFactories(DHGroups.Group1SHA1(), new DHGexSHA1.Factory());
|
||||
}
|
||||
}
|
||||
|
||||
protected void initRandomFactory(boolean bouncyCastleRegistered) {
|
||||
@@ -120,21 +139,25 @@ public class DefaultConfig
|
||||
|
||||
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
|
||||
if (bouncyCastleRegistered) {
|
||||
setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory());
|
||||
setFileKeyProviderFactories(
|
||||
new OpenSSHKeyV1KeyFile.Factory(),
|
||||
new PKCS8KeyFile.Factory(),
|
||||
new PKCS5KeyFile.Factory(),
|
||||
new OpenSSHKeyFile.Factory(),
|
||||
new PuTTYKeyFile.Factory());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void initCipherFactories() {
|
||||
List<Factory.Named<Cipher>> avail = new LinkedList<Factory.Named<Cipher>>(Arrays.<Factory.Named<Cipher>>asList(
|
||||
new AES128CTR.Factory(),
|
||||
new AES192CTR.Factory(),
|
||||
new AES256CTR.Factory(),
|
||||
new AES128CBC.Factory(),
|
||||
new AES192CBC.Factory(),
|
||||
new AES256CBC.Factory(),
|
||||
new TripleDESCBC.Factory(),
|
||||
new BlowfishCBC.Factory(),
|
||||
BlockCiphers.AES128CBC(),
|
||||
BlockCiphers.AES128CTR(),
|
||||
BlockCiphers.AES192CBC(),
|
||||
BlockCiphers.AES192CTR(),
|
||||
BlockCiphers.AES256CBC(),
|
||||
BlockCiphers.AES256CTR(),
|
||||
BlockCiphers.BlowfishCBC(),
|
||||
BlockCiphers.BlowfishCTR(),
|
||||
BlockCiphers.Cast128CBC(),
|
||||
BlockCiphers.Cast128CTR(),
|
||||
@@ -146,6 +169,7 @@ public class DefaultConfig
|
||||
BlockCiphers.Serpent192CTR(),
|
||||
BlockCiphers.Serpent256CBC(),
|
||||
BlockCiphers.Serpent256CTR(),
|
||||
BlockCiphers.TripleDESCBC(),
|
||||
BlockCiphers.TripleDESCTR(),
|
||||
BlockCiphers.Twofish128CBC(),
|
||||
BlockCiphers.Twofish128CTR(),
|
||||
@@ -156,7 +180,8 @@ public class DefaultConfig
|
||||
BlockCiphers.TwofishCBC(),
|
||||
StreamCiphers.Arcfour(),
|
||||
StreamCiphers.Arcfour128(),
|
||||
StreamCiphers.Arcfour256()));
|
||||
StreamCiphers.Arcfour256())
|
||||
);
|
||||
|
||||
boolean warn = false;
|
||||
// Ref. https://issues.apache.org/jira/browse/SSHD-24
|
||||
@@ -182,17 +207,28 @@ public class DefaultConfig
|
||||
}
|
||||
|
||||
protected void initSignatureFactories() {
|
||||
setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory(), new SignatureEdDSA.Factory());
|
||||
setSignatureFactories(
|
||||
new SignatureECDSA.Factory256(),
|
||||
new SignatureECDSA.Factory384(),
|
||||
new SignatureECDSA.Factory521(),
|
||||
new SignatureRSA.Factory(),
|
||||
new SignatureDSA.Factory(),
|
||||
new SignatureEdDSA.Factory()
|
||||
);
|
||||
}
|
||||
|
||||
protected void initMACFactories() {
|
||||
setMACFactories(new HMACSHA1.Factory(), new HMACSHA196.Factory(), new HMACMD5.Factory(),
|
||||
new HMACMD596.Factory(), new HMACSHA2256.Factory(), new HMACSHA2512.Factory());
|
||||
setMACFactories(
|
||||
new HMACSHA1.Factory(),
|
||||
new HMACSHA196.Factory(),
|
||||
new HMACMD5.Factory(),
|
||||
new HMACMD596.Factory(),
|
||||
new HMACSHA2256.Factory(),
|
||||
new HMACSHA2512.Factory()
|
||||
);
|
||||
}
|
||||
|
||||
protected void initCompressionFactories() {
|
||||
setCompressionFactories(new NoneCompression.Factory());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.connection.ConnectionImpl;
|
||||
@@ -40,22 +38,14 @@ import net.schmizz.sshj.transport.compression.DelayedZlibCompression;
|
||||
import net.schmizz.sshj.transport.compression.NoneCompression;
|
||||
import net.schmizz.sshj.transport.compression.ZlibCompression;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
import net.schmizz.sshj.transport.verification.FingerprintVerifier;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
|
||||
import net.schmizz.sshj.userauth.UserAuth;
|
||||
import net.schmizz.sshj.userauth.UserAuthException;
|
||||
import net.schmizz.sshj.userauth.UserAuthImpl;
|
||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
|
||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||
import net.schmizz.sshj.userauth.method.AuthMethod;
|
||||
import net.schmizz.sshj.userauth.method.AuthPassword;
|
||||
import net.schmizz.sshj.userauth.method.AuthPublickey;
|
||||
import net.schmizz.sshj.userauth.method.PasswordResponseProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.*;
|
||||
import net.schmizz.sshj.userauth.method.*;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUpdateProvider;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
@@ -63,21 +53,16 @@ import net.schmizz.sshj.userauth.password.Resource;
|
||||
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
|
||||
import org.ietf.jgss.Oid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.security.auth.login.LoginContext;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Secure SHell client API.
|
||||
@@ -128,7 +113,8 @@ public class SSHClient
|
||||
public static final int DEFAULT_PORT = 22;
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final LoggerFactory loggerFactory;
|
||||
protected final Logger log;
|
||||
|
||||
/** Transport layer */
|
||||
protected final Transport trans;
|
||||
@@ -139,6 +125,11 @@ public class SSHClient
|
||||
/** {@code ssh-connection} service */
|
||||
protected final Connection conn;
|
||||
|
||||
private final List<LocalPortForwarder> forwarders = new ArrayList<LocalPortForwarder>();
|
||||
|
||||
/** character set of the remote machine */
|
||||
protected Charset remoteCharset = IOUtils.UTF8;
|
||||
|
||||
/** Default constructor. Initializes this object using {@link DefaultConfig}. */
|
||||
public SSHClient() {
|
||||
this(new DefaultConfig());
|
||||
@@ -151,6 +142,8 @@ public class SSHClient
|
||||
*/
|
||||
public SSHClient(Config config) {
|
||||
super(DEFAULT_PORT);
|
||||
loggerFactory = config.getLoggerFactory();
|
||||
log = loggerFactory.getLogger(getClass());
|
||||
this.trans = new TransportImpl(config, this);
|
||||
this.auth = new UserAuthImpl(trans);
|
||||
this.conn = new ConnectionImpl(trans, config.getKeepAliveProvider());
|
||||
@@ -177,19 +170,23 @@ public class SSHClient
|
||||
|
||||
/**
|
||||
* Add a {@link HostKeyVerifier} that will verify any host that's able to claim a host key with the given {@code
|
||||
* fingerprint}, e.g. {@code "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"}
|
||||
* fingerprint}.
|
||||
*
|
||||
* The fingerprint can be specified in either an MD5 colon-delimited format (16 hexadecimal octets, delimited by a colon),
|
||||
* or in a Base64 encoded format for SHA-1 or SHA-256 fingerprints.
|
||||
* Valid examples are:
|
||||
*
|
||||
* <ul><li>"SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak="</li>
|
||||
* <li>"SHA256:oQGbQTujGeNIgh0ONthcEpA/BHxtt3rcYY+NxXTxQjs="</li>
|
||||
* <li>"MD5:d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32"</li>
|
||||
* <li>"d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32"</li></ul>
|
||||
*
|
||||
* @param fingerprint expected fingerprint in colon-delimited format (16 octets in hex delimited by a colon)
|
||||
*
|
||||
* @see SecurityUtils#getFingerprint
|
||||
*/
|
||||
public void addHostKeyVerifier(final String fingerprint) {
|
||||
addHostKeyVerifier(new HostKeyVerifier() {
|
||||
@Override
|
||||
public boolean verify(String h, int p, PublicKey k) {
|
||||
return SecurityUtils.getFingerprint(k).equals(fingerprint);
|
||||
}
|
||||
});
|
||||
addHostKeyVerifier(FingerprintVerifier.getInstance(fingerprint));
|
||||
}
|
||||
|
||||
// FIXME: there are way too many auth... overrides. Better API needed.
|
||||
@@ -221,8 +218,9 @@ public class SSHClient
|
||||
public void auth(String username, Iterable<AuthMethod> methods)
|
||||
throws UserAuthException, TransportException {
|
||||
checkConnected();
|
||||
final Deque<UserAuthException> savedEx = new LinkedList<>();
|
||||
final Deque<UserAuthException> savedEx = new LinkedList<UserAuthException>();
|
||||
for (AuthMethod method: methods) {
|
||||
method.setLoggerFactory(loggerFactory);
|
||||
try {
|
||||
if (auth.authenticate(username, (Service) conn, method, trans.getTimeoutMs()))
|
||||
return;
|
||||
@@ -324,7 +322,7 @@ public class SSHClient
|
||||
public void authPublickey(String username)
|
||||
throws UserAuthException, TransportException {
|
||||
final String base = System.getProperty("user.home") + File.separator + ".ssh" + File.separator;
|
||||
authPublickey(username, base + "id_rsa", base + "id_dsa");
|
||||
authPublickey(username, base + "id_rsa", base + "id_dsa", base + "id_ed25519", base + "id_ecdsa");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,7 +340,7 @@ public class SSHClient
|
||||
*/
|
||||
public void authPublickey(String username, Iterable<KeyProvider> keyProviders)
|
||||
throws UserAuthException, TransportException {
|
||||
final List<AuthMethod> am = new LinkedList<>();
|
||||
final List<AuthMethod> am = new LinkedList<AuthMethod>();
|
||||
for (KeyProvider kp : keyProviders)
|
||||
am.add(new AuthPublickey(kp));
|
||||
auth(username, am);
|
||||
@@ -385,7 +383,7 @@ public class SSHClient
|
||||
*/
|
||||
public void authPublickey(String username, String... locations)
|
||||
throws UserAuthException, TransportException {
|
||||
final List<KeyProvider> keyProviders = new LinkedList<>();
|
||||
final List<KeyProvider> keyProviders = new LinkedList<KeyProvider>();
|
||||
for (String loc : locations) {
|
||||
try {
|
||||
log.debug("Attempting to load key from: {}", loc);
|
||||
@@ -415,7 +413,7 @@ public class SSHClient
|
||||
public void authGssApiWithMic(String username, LoginContext context, Oid supportedOid, Oid... supportedOids)
|
||||
throws UserAuthException, TransportException {
|
||||
// insert supportedOid to the front of the list since ordering matters
|
||||
List<Oid> oids = new ArrayList<>(Arrays.asList(supportedOids));
|
||||
List<Oid> oids = new ArrayList<Oid>(Arrays.asList(supportedOids));
|
||||
oids.add(0, supportedOid);
|
||||
|
||||
auth(username, new AuthGssApiWithMic(context, oids));
|
||||
@@ -431,6 +429,14 @@ public class SSHClient
|
||||
@Override
|
||||
public void disconnect()
|
||||
throws IOException {
|
||||
for (LocalPortForwarder forwarder : forwarders) {
|
||||
try {
|
||||
forwarder.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing forwarder", e);
|
||||
}
|
||||
}
|
||||
forwarders.clear();
|
||||
trans.disconnect();
|
||||
super.disconnect();
|
||||
}
|
||||
@@ -440,6 +446,15 @@ public class SSHClient
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character set used to communicate with the remote machine for certain strings (like paths).
|
||||
*
|
||||
* @return remote character set
|
||||
*/
|
||||
public Charset getRemoteCharset() {
|
||||
return remoteCharset;
|
||||
}
|
||||
|
||||
/** @return a {@link RemotePortForwarder} that allows requesting remote forwarding over this connection. */
|
||||
public RemotePortForwarder getRemotePortForwarder() {
|
||||
synchronized (conn) {
|
||||
@@ -524,8 +539,13 @@ public class SSHClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyProvider} instance from given location on the file system. Currently only PKCS8 format
|
||||
* private key files are supported (OpenSSH uses this format).
|
||||
* Creates a {@link KeyProvider} instance from given location on the file system. Currently the following private key files are supported:
|
||||
* <ul>
|
||||
* <li>PKCS8 (OpenSSH uses this format)</li>
|
||||
* <li>PKCS5</li>
|
||||
* <li>Putty keyfile</li>
|
||||
* <li>openssh-key-v1 (New OpenSSH keyfile format)</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
*
|
||||
* @param location the location of the key file
|
||||
@@ -630,7 +650,7 @@ public class SSHClient
|
||||
*/
|
||||
public void loadKnownHosts(File location)
|
||||
throws IOException {
|
||||
addHostKeyVerifier(new OpenSSHKnownHosts(location));
|
||||
addHostKeyVerifier(new OpenSSHKnownHosts(location, loggerFactory));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -648,7 +668,9 @@ public class SSHClient
|
||||
*/
|
||||
public LocalPortForwarder newLocalPortForwarder(LocalPortForwarder.Parameters parameters,
|
||||
ServerSocket serverSocket) {
|
||||
return new LocalPortForwarder(conn, parameters, serverSocket);
|
||||
LocalPortForwarder forwarder = new LocalPortForwarder(conn, parameters, serverSocket, loggerFactory);
|
||||
forwarders.add(forwarder);
|
||||
return forwarder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -675,7 +697,7 @@ public class SSHClient
|
||||
public SCPFileTransfer newSCPFileTransfer() {
|
||||
checkConnected();
|
||||
checkAuthenticated();
|
||||
return new SCPFileTransfer(this);
|
||||
return new SCPFileTransfer(this, loggerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -701,12 +723,22 @@ public class SSHClient
|
||||
doKex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the character set used to communicate with the remote machine for certain strings (like paths)
|
||||
*
|
||||
* @param remoteCharset
|
||||
* remote character set or {@code null} for default
|
||||
*/
|
||||
public void setRemoteCharset(Charset remoteCharset) {
|
||||
this.remoteCharset = remoteCharset != null ? remoteCharset : IOUtils.UTF8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session startSession()
|
||||
throws ConnectionException, TransportException {
|
||||
checkConnected();
|
||||
checkAuthenticated();
|
||||
final SessionChannel sess = new SessionChannel(conn);
|
||||
final SessionChannel sess = new SessionChannel(conn, remoteCharset);
|
||||
sess.open();
|
||||
return sess;
|
||||
}
|
||||
|
||||
@@ -48,12 +48,50 @@ public abstract class SocketClient {
|
||||
this.defaultPort = defaultPort;
|
||||
}
|
||||
|
||||
public void connect(InetAddress host, int port) throws IOException {
|
||||
socket = socketFactory.createSocket();
|
||||
socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
* @param hostname The host name to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(String hostname, Proxy proxy) throws IOException {
|
||||
connect(hostname, defaultPort, proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
* @param hostname The host name to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(String hostname, int port, Proxy proxy) throws IOException {
|
||||
this.hostname = hostname;
|
||||
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
|
||||
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
|
||||
socket = new Jdk7HttpProxySocket(proxy);
|
||||
} else {
|
||||
socket = new Socket(proxy);
|
||||
}
|
||||
socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
|
||||
onConnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
* @param host The host address to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(InetAddress host, Proxy proxy) throws IOException {
|
||||
connect(host, defaultPort, proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
@@ -75,23 +113,41 @@ public abstract class SocketClient {
|
||||
onConnect();
|
||||
}
|
||||
|
||||
public void connect(String hostname, int port) throws IOException {
|
||||
this.hostname = hostname;
|
||||
connect(InetAddress.getByName(hostname), port);
|
||||
public void connect(String hostname) throws IOException {
|
||||
connect(hostname, defaultPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
* @param hostname The host name to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(String hostname, int port, Proxy proxy) throws IOException {
|
||||
this.hostname = hostname;
|
||||
connect(InetAddress.getByName(hostname), port, proxy);
|
||||
public void connect(String hostname, int port) throws IOException {
|
||||
if (hostname == null) {
|
||||
connect(InetAddress.getByName(null), port);
|
||||
} else {
|
||||
this.hostname = hostname;
|
||||
socket = socketFactory.createSocket();
|
||||
socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
|
||||
onConnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void connect(String hostname, int port, InetAddress localAddr, int localPort) throws IOException {
|
||||
if (hostname == null) {
|
||||
connect(InetAddress.getByName(null), port, localAddr, localPort);
|
||||
} else {
|
||||
this.hostname = hostname;
|
||||
socket = socketFactory.createSocket();
|
||||
socket.bind(new InetSocketAddress(localAddr, localPort));
|
||||
socket.connect(new InetSocketAddress(hostname, port), connectTimeout);
|
||||
onConnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void connect(InetAddress host) throws IOException {
|
||||
connect(host, defaultPort);
|
||||
}
|
||||
|
||||
public void connect(InetAddress host, int port) throws IOException {
|
||||
socket = socketFactory.createSocket();
|
||||
socket.connect(new InetSocketAddress(host, port), connectTimeout);
|
||||
onConnect();
|
||||
}
|
||||
|
||||
public void connect(InetAddress host, int port, InetAddress localAddr, int localPort)
|
||||
@@ -102,43 +158,6 @@ public abstract class SocketClient {
|
||||
onConnect();
|
||||
}
|
||||
|
||||
public void connect(String hostname, int port, InetAddress localAddr, int localPort) throws IOException {
|
||||
this.hostname = hostname;
|
||||
connect(InetAddress.getByName(hostname), port, localAddr, localPort);
|
||||
}
|
||||
|
||||
public void connect(InetAddress host) throws IOException {
|
||||
connect(host, defaultPort);
|
||||
}
|
||||
|
||||
public void connect(String hostname) throws IOException {
|
||||
connect(hostname, defaultPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
* @param host The host address to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(InetAddress host, Proxy proxy) throws IOException {
|
||||
connect(host, defaultPort, proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a host via a proxy.
|
||||
* @param hostname The host name to connect to.
|
||||
* @param proxy The proxy to connect via.
|
||||
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
|
||||
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public void connect(String hostname, Proxy proxy) throws IOException {
|
||||
connect(hostname, defaultPort, proxy);
|
||||
}
|
||||
|
||||
public void disconnect() throws IOException {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
@@ -57,6 +57,11 @@ public class Buffer<T extends Buffer<T>> {
|
||||
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
|
||||
public static final int MAX_SIZE = (1 << 30);
|
||||
|
||||
/** Maximum size of a uint64 */
|
||||
private static final BigInteger MAX_UINT64_VALUE = BigInteger.ONE
|
||||
.shiftLeft(64)
|
||||
.subtract(BigInteger.ONE);
|
||||
|
||||
protected static int getNextPowerOf2(int i) {
|
||||
int j = 1;
|
||||
while (j < i) {
|
||||
@@ -241,7 +246,7 @@ public class Buffer<T extends Buffer<T>> {
|
||||
* @return this
|
||||
*/
|
||||
public T putBytes(byte[] b, int off, int len) {
|
||||
return putUInt32(len - off).putRawBytes(b, off, len);
|
||||
return putUInt32(len).putRawBytes(b, off, len);
|
||||
}
|
||||
|
||||
public void readRawBytes(byte[] buf)
|
||||
@@ -311,7 +316,7 @@ public class Buffer<T extends Buffer<T>> {
|
||||
public T putUInt32(long uint32) {
|
||||
ensureCapacity(4);
|
||||
if (uint32 < 0 || uint32 > 0xffffffffL)
|
||||
throw new RuntimeException("Invalid value: " + uint32);
|
||||
throw new IllegalArgumentException("Invalid value: " + uint32);
|
||||
data[wpos++] = (byte) (uint32 >> 24);
|
||||
data[wpos++] = (byte) (uint32 >> 16);
|
||||
data[wpos++] = (byte) (uint32 >> 8);
|
||||
@@ -343,10 +348,29 @@ public class Buffer<T extends Buffer<T>> {
|
||||
return uint64;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BigInteger readUInt64AsBigInteger()
|
||||
throws BufferException {
|
||||
byte[] magnitude = new byte[8];
|
||||
readRawBytes(magnitude);
|
||||
return new BigInteger(1, magnitude);
|
||||
}
|
||||
|
||||
public T putUInt64(long uint64) {
|
||||
if (uint64 < 0)
|
||||
throw new RuntimeException("Invalid value: " + uint64);
|
||||
throw new IllegalArgumentException("Invalid value: " + uint64);
|
||||
return putUInt64Unchecked(uint64);
|
||||
}
|
||||
|
||||
public T putUInt64(BigInteger uint64) {
|
||||
if (uint64.compareTo(MAX_UINT64_VALUE) > 0 ||
|
||||
uint64.compareTo(BigInteger.ZERO) < 0) {
|
||||
throw new IllegalArgumentException("Invalid value: " + uint64);
|
||||
}
|
||||
return putUInt64Unchecked(uint64.longValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T putUInt64Unchecked(long uint64) {
|
||||
data[wpos++] = (byte) (uint64 >> 56);
|
||||
data[wpos++] = (byte) (uint64 >> 48);
|
||||
data[wpos++] = (byte) (uint64 >> 40);
|
||||
@@ -361,24 +385,31 @@ public class Buffer<T extends Buffer<T>> {
|
||||
/**
|
||||
* Reads an SSH string
|
||||
*
|
||||
* @param cs the charset to use for decoding
|
||||
*
|
||||
* @return the string as a Java {@code String}
|
||||
*/
|
||||
public String readString()
|
||||
public String readString(Charset cs)
|
||||
throws BufferException {
|
||||
int len = readUInt32AsInt();
|
||||
if (len < 0 || len > 32768)
|
||||
throw new BufferException("Bad item length: " + len);
|
||||
ensureAvailable(len);
|
||||
String s;
|
||||
try {
|
||||
s = new String(data, rpos, len, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
String s = new String(data, rpos, len, cs);
|
||||
rpos += len;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an SSH string using {@code UTF8}
|
||||
*
|
||||
* @return the string as a Java {@code String}
|
||||
*/
|
||||
public String readString()
|
||||
throws BufferException {
|
||||
return readString(IOUtils.UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an SSH string
|
||||
*
|
||||
@@ -397,8 +428,12 @@ public class Buffer<T extends Buffer<T>> {
|
||||
return putBytes(str, offset, len);
|
||||
}
|
||||
|
||||
public T putString(String string, Charset cs) {
|
||||
return putString(string.getBytes(cs));
|
||||
}
|
||||
|
||||
public T putString(String string) {
|
||||
return putString(string.getBytes(IOUtils.UTF8));
|
||||
return putString(string, IOUtils.UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -425,11 +460,13 @@ public class Buffer<T extends Buffer<T>> {
|
||||
|
||||
public PublicKey readPublicKey()
|
||||
throws BufferException {
|
||||
KeyType keyType = KeyType.fromString(readString());
|
||||
try {
|
||||
final String type = readString();
|
||||
return KeyType.fromString(type).readPubKeyFromBuffer(type, this);
|
||||
return keyType.readPubKeyFromBuffer(this);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
throw new BufferException("Could not decode keytype " + keyType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,4 +94,34 @@ public class ByteArrayUtils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] parseHex(String hex) {
|
||||
if (hex == null) {
|
||||
throw new IllegalArgumentException("Hex string is null");
|
||||
}
|
||||
if (hex.length() % 2 != 0) {
|
||||
throw new IllegalArgumentException("Hex string '" + hex + "' should have even length.");
|
||||
}
|
||||
|
||||
byte[] result = new byte[hex.length() / 2];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
int hi = parseHexDigit(hex.charAt(i * 2)) << 4;
|
||||
int lo = parseHexDigit(hex.charAt(i * 2 + 1));
|
||||
result[i] = (byte) (hi + lo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int parseHexDigit(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
throw new IllegalArgumentException("Digit '" + c + "' out of bounds [0-9a-fA-F]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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 com.hierynomus.sshj.secg.SecgUtils;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class ECDSAVariationsAdapter {
|
||||
|
||||
private final static String BASE_ALGORITHM_NAME = "ecdsa-sha2-nistp";
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ECDSAVariationsAdapter.class);
|
||||
|
||||
public final static Map<String, String> SUPPORTED_CURVES = new HashMap<String, String>();
|
||||
public final static Map<String, String> NIST_CURVES_NAMES = new HashMap<String, String>();
|
||||
|
||||
static {
|
||||
NIST_CURVES_NAMES.put("256", "p-256");
|
||||
NIST_CURVES_NAMES.put("384", "p-384");
|
||||
NIST_CURVES_NAMES.put("521", "p-521");
|
||||
|
||||
SUPPORTED_CURVES.put("256", "nistp256");
|
||||
SUPPORTED_CURVES.put("384", "nistp384");
|
||||
SUPPORTED_CURVES.put("521", "nistp521");
|
||||
}
|
||||
|
||||
static PublicKey readPubKeyFromBuffer(Buffer<?> buf, String variation) throws GeneralSecurityException {
|
||||
String algorithm = BASE_ALGORITHM_NAME + variation;
|
||||
if (!SecurityUtils.isBouncyCastleRegistered()) {
|
||||
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + algorithm);
|
||||
}
|
||||
try {
|
||||
// final String algo = buf.readString(); it has been already read
|
||||
final String curveName = buf.readString();
|
||||
final int keyLen = buf.readUInt32AsInt();
|
||||
final byte x04 = buf.readByte(); // it must be 0x04, but don't think
|
||||
// we need that check
|
||||
final byte[] x = new byte[(keyLen - 1) / 2];
|
||||
final byte[] y = new byte[(keyLen - 1) / 2];
|
||||
buf.readRawBytes(x);
|
||||
buf.readRawBytes(y);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
|
||||
algorithm, curveName, keyLen, x04, Arrays.toString(x), Arrays.toString(y)));
|
||||
}
|
||||
|
||||
if (!SUPPORTED_CURVES.values().contains(curveName)) {
|
||||
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
|
||||
}
|
||||
|
||||
BigInteger bigX = new BigInteger(1, x);
|
||||
BigInteger bigY = new BigInteger(1, y);
|
||||
|
||||
String name = NIST_CURVES_NAMES.get(variation);
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName(name);
|
||||
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
|
||||
ECPoint p = new ECPoint(bigX, bigY);
|
||||
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(p, ecCurveSpec);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
|
||||
return keyFactory.generatePublic(publicKeySpec);
|
||||
} catch (Exception ex) {
|
||||
throw new GeneralSecurityException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
||||
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
|
||||
|
||||
buf.putString("nistp" + Integer.toString(fieldSizeFromKey(ecdsa)))
|
||||
.putBytes(encoded);
|
||||
}
|
||||
|
||||
static boolean isECKeyWithFieldSize(Key key, int fieldSize) {
|
||||
return "ECDSA".equals(key.getAlgorithm())
|
||||
&& fieldSizeFromKey((ECKey) key) == fieldSize;
|
||||
}
|
||||
|
||||
private static int fieldSizeFromKey(ECKey ecPublicKey) {
|
||||
return ecPublicKey.getParams().getCurve().getField().getFieldSize();
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@@ -26,25 +23,33 @@ import java.nio.charset.Charset;
|
||||
|
||||
public class IOUtils {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IOUtils.class);
|
||||
|
||||
public static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
public static void closeQuietly(Closeable... closeables) {
|
||||
for (Closeable c : closeables)
|
||||
try {
|
||||
if (c != null)
|
||||
c.close();
|
||||
} catch (IOException logged) {
|
||||
LOG.warn("Error closing {} - {}", c, logged);
|
||||
}
|
||||
closeQuietly(LoggerFactory.DEFAULT, closeables);
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream readFully(InputStream stream)
|
||||
throws IOException {
|
||||
return readFully(stream, LoggerFactory.DEFAULT);
|
||||
}
|
||||
|
||||
public static void closeQuietly(LoggerFactory loggerFactory, Closeable... closeables) {
|
||||
for (Closeable c : closeables) {
|
||||
try {
|
||||
if (c != null)
|
||||
c.close();
|
||||
} catch (IOException logged) {
|
||||
loggerFactory.getLogger(IOUtils.class).warn("Error closing {} - {}", c, logged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream readFully(InputStream stream, LoggerFactory loggerFactory)
|
||||
throws IOException {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
new StreamCopier(stream, baos).copy();
|
||||
new StreamCopier(stream, baos, loggerFactory).copy();
|
||||
return baos;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,13 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import com.hierynomus.sshj.secg.SecgUtils;
|
||||
import com.hierynomus.sshj.signature.Ed25519PublicKey;
|
||||
import com.hierynomus.sshj.userauth.certificate.Certificate;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.math.GroupElement;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -35,19 +30,21 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.*;
|
||||
import java.security.interfaces.DSAPrivateKey;
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.*;
|
||||
|
||||
/** Type of key e.g. rsa, dsa */
|
||||
public enum KeyType {
|
||||
|
||||
|
||||
/** SSH identifier for RSA keys */
|
||||
RSA("ssh-rsa") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
final BigInteger e, n;
|
||||
try {
|
||||
@@ -61,24 +58,22 @@ public enum KeyType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final RSAPublicKey rsaKey = (RSAPublicKey) pk;
|
||||
buf.putString(sType)
|
||||
.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
buf.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/** SSH identifier for DSA keys */
|
||||
DSA("ssh-dss") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
BigInteger p, q, g, y;
|
||||
try {
|
||||
@@ -94,13 +89,12 @@ public enum KeyType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
|
||||
buf.putString(sType)
|
||||
.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
buf.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,89 +104,87 @@ public enum KeyType {
|
||||
|
||||
},
|
||||
|
||||
/** SSH identifier for ECDSA keys */
|
||||
ECDSA("ecdsa-sha2-nistp256") {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
/** SSH identifier for ECDSA-256 keys */
|
||||
ECDSA256("ecdsa-sha2-nistp256") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
try {
|
||||
// final String algo = buf.readString(); it has been already read
|
||||
final String curveName = buf.readString();
|
||||
final int keyLen = buf.readUInt32AsInt();
|
||||
final byte x04 = buf.readByte(); // it must be 0x04, but don't think we need that check
|
||||
final byte[] x = new byte[(keyLen - 1) / 2];
|
||||
final byte[] y = new byte[(keyLen - 1) / 2];
|
||||
buf.readRawBytes(x);
|
||||
buf.readRawBytes(y);
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
|
||||
type,
|
||||
curveName,
|
||||
keyLen,
|
||||
x04,
|
||||
Arrays.toString(x),
|
||||
Arrays.toString(y))
|
||||
);
|
||||
}
|
||||
|
||||
if (!NISTP_CURVE.equals(curveName)) {
|
||||
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
|
||||
}
|
||||
|
||||
BigInteger bigX = new BigInteger(1, x);
|
||||
BigInteger bigY = new BigInteger(1, y);
|
||||
|
||||
X9ECParameters ecParams = NISTNamedCurves.getByName("p-256");
|
||||
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY);
|
||||
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(),
|
||||
ecParams.getG(), ecParams.getN());
|
||||
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
|
||||
return keyFactory.generatePublic(publicSpec);
|
||||
} catch (Exception ex) {
|
||||
throw new GeneralSecurityException(ex);
|
||||
}
|
||||
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "256");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final ECPublicKey ecdsa = (ECPublicKey) pk;
|
||||
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
|
||||
|
||||
buf.putString(sType)
|
||||
.putString(NISTP_CURVE)
|
||||
.putBytes(encoded);
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return ("ECDSA".equals(key.getAlgorithm()));
|
||||
return ECDSAVariationsAdapter.isECKeyWithFieldSize(key, 256);
|
||||
}
|
||||
},
|
||||
|
||||
/** SSH identifier for ECDSA-384 keys */
|
||||
ECDSA384("ecdsa-sha2-nistp384") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "384");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return ECDSAVariationsAdapter.isECKeyWithFieldSize(key, 384);
|
||||
}
|
||||
},
|
||||
|
||||
/** SSH identifier for ECDSA-521 keys */
|
||||
ECDSA521("ecdsa-sha2-nistp521") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "521");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return ECDSAVariationsAdapter.isECKeyWithFieldSize(key, 521);
|
||||
}
|
||||
},
|
||||
|
||||
ED25519("ssh-ed25519") {
|
||||
private final Logger logger = LoggerFactory.getLogger(KeyType.class);
|
||||
private final Logger log = LoggerFactory.getLogger(KeyType.class);
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf) throws GeneralSecurityException {
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf) throws GeneralSecurityException {
|
||||
try {
|
||||
final int keyLen = buf.readUInt32AsInt();
|
||||
final byte[] p = new byte[keyLen];
|
||||
buf.readRawBytes(p);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Key algo: %s, Key curve: 25519, Key Len: %s\np: %s",
|
||||
type,
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(String.format("Key algo: %s, Key curve: 25519, Key Len: %s\np: %s",
|
||||
sType,
|
||||
keyLen,
|
||||
Arrays.toString(p))
|
||||
);
|
||||
}
|
||||
|
||||
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512");
|
||||
GroupElement point = ed25519.getCurve().createPoint(p, true);
|
||||
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(point, ed25519);
|
||||
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
|
||||
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(p, ed25519);
|
||||
return new Ed25519PublicKey(publicSpec);
|
||||
|
||||
} catch (Buffer.BufferException be) {
|
||||
@@ -201,9 +193,9 @@ public enum KeyType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
EdDSAPublicKey key = (EdDSAPublicKey) pk;
|
||||
buf.putString(sType).putBytes(key.getAbyte());
|
||||
buf.putBytes(key.getAbyte());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -212,12 +204,50 @@ public enum KeyType {
|
||||
}
|
||||
},
|
||||
|
||||
/** Signed rsa certificate */
|
||||
RSA_CERT("ssh-rsa-cert-v01@openssh.com") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return CertUtils.readPubKey(buf, RSA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
CertUtils.writePubKeyContentsIntoBuffer(pk, RSA, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return CertUtils.isCertificateOfType(key, RSA);
|
||||
}
|
||||
},
|
||||
|
||||
/** Signed dsa certificate */
|
||||
DSA_CERT("ssh-dss-cert-v01@openssh.com") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
return CertUtils.readPubKey(buf, DSA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
CertUtils.writePubKeyContentsIntoBuffer(pk, DSA, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return CertUtils.isCertificateOfType(key, DSA);
|
||||
}
|
||||
},
|
||||
|
||||
/** Unrecognized */
|
||||
UNKNOWN("unknown") {
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
throw new UnsupportedOperationException("Don't know how to decode key:" + type);
|
||||
throw new UnsupportedOperationException("Don't know how to decode key:" + sType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,25 +255,31 @@ public enum KeyType {
|
||||
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static final String NISTP_CURVE = "nistp256";
|
||||
|
||||
protected final String sType;
|
||||
|
||||
private KeyType(String type) {
|
||||
this.sType = type;
|
||||
}
|
||||
|
||||
public abstract PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
public abstract PublicKey readPubKeyFromBuffer(Buffer<?> buf)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
public abstract void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf);
|
||||
protected abstract void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf);
|
||||
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
writePubKeyContentsIntoBuffer(pk, buf.putString(sType));
|
||||
}
|
||||
|
||||
protected abstract boolean isMyType(Key key);
|
||||
|
||||
@@ -266,4 +302,128 @@ public enum KeyType {
|
||||
return sType;
|
||||
}
|
||||
|
||||
static class CertUtils {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends PublicKey> Certificate<T> readPubKey(Buffer<?> buf, KeyType innerKeyType) throws GeneralSecurityException {
|
||||
Certificate.Builder<T> builder = Certificate.getBuilder();
|
||||
|
||||
try {
|
||||
builder.nonce(buf.readBytes());
|
||||
builder.publicKey((T) innerKeyType.readPubKeyFromBuffer(buf));
|
||||
builder.serial(buf.readUInt64AsBigInteger());
|
||||
builder.type(buf.readUInt32());
|
||||
builder.id(buf.readString());
|
||||
builder.validPrincipals(unpackList(buf.readBytes()));
|
||||
builder.validAfter(dateFromEpoch(buf.readUInt64()));
|
||||
builder.validBefore(dateFromEpoch(buf.readUInt64()));
|
||||
builder.critOptions(unpackMap(buf.readBytes()));
|
||||
builder.extensions(unpackMap(buf.readBytes()));
|
||||
buf.readString(); // reserved
|
||||
builder.signatureKey(buf.readBytes());
|
||||
builder.signature(buf.readBytes());
|
||||
} catch (Buffer.BufferException be) {
|
||||
throw new GeneralSecurityException(be);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static void writePubKeyContentsIntoBuffer(PublicKey publicKey, KeyType innerKeyType, Buffer<?> buf) {
|
||||
Certificate<PublicKey> certificate = toCertificate(publicKey);
|
||||
buf.putBytes(certificate.getNonce());
|
||||
innerKeyType.writePubKeyContentsIntoBuffer(certificate.getKey(), buf);
|
||||
buf.putUInt64(certificate.getSerial())
|
||||
.putUInt32(certificate.getType())
|
||||
.putString(certificate.getId())
|
||||
.putBytes(packList(certificate.getValidPrincipals()))
|
||||
.putUInt64(epochFromDate(certificate.getValidAfter()))
|
||||
.putUInt64(epochFromDate(certificate.getValidBefore()))
|
||||
.putBytes(packMap(certificate.getCritOptions()))
|
||||
.putBytes(packMap(certificate.getExtensions()))
|
||||
.putString("") // reserved
|
||||
.putBytes(certificate.getSignatureKey())
|
||||
.putBytes(certificate.getSignature());
|
||||
}
|
||||
|
||||
static boolean isCertificateOfType(Key key, KeyType innerKeyType) {
|
||||
if (!(key instanceof Certificate)) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Key innerKey = ((Certificate<PublicKey>) key).getKey();
|
||||
return innerKeyType.isMyType(innerKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Certificate<PublicKey> toCertificate(PublicKey key) {
|
||||
if (!(key instanceof Certificate)) {
|
||||
throw new UnsupportedOperationException("Can't convert non-certificate key " +
|
||||
key.getAlgorithm() + " to certificate");
|
||||
}
|
||||
return ((Certificate<PublicKey>) key);
|
||||
}
|
||||
|
||||
private static Date dateFromEpoch(long seconds) {
|
||||
return new Date(seconds * 1000);
|
||||
}
|
||||
|
||||
private static long epochFromDate(Date date) {
|
||||
return date.getTime() / 1000;
|
||||
}
|
||||
|
||||
private static String unpackString(byte[] packedString) throws BufferException {
|
||||
if (packedString.length == 0) {
|
||||
return "";
|
||||
}
|
||||
return new Buffer.PlainBuffer(packedString).readString();
|
||||
}
|
||||
|
||||
private static List<String> unpackList(byte[] packedString) throws BufferException {
|
||||
List<String> list = new ArrayList<String>();
|
||||
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
|
||||
while (buf.available() > 0) {
|
||||
list.add(buf.readString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static Map<String, String> unpackMap(byte[] packedString) throws BufferException {
|
||||
Map<String, String> map = new LinkedHashMap<String, String>();
|
||||
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
|
||||
while (buf.available() > 0) {
|
||||
String name = buf.readString();
|
||||
String data = unpackString(buf.readStringAsBytes());
|
||||
map.put(name, data);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static byte[] packString(String data) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return "".getBytes();
|
||||
}
|
||||
return new Buffer.PlainBuffer().putString(data).getCompactData();
|
||||
}
|
||||
|
||||
private static byte[] packList(Iterable<String> strings) {
|
||||
Buffer<?> buf = new Buffer.PlainBuffer();
|
||||
for (String string : strings) {
|
||||
buf.putString(string);
|
||||
}
|
||||
return buf.getCompactData();
|
||||
}
|
||||
|
||||
private static byte[] packMap(Map<String, String> map) {
|
||||
Buffer<?> buf = new Buffer.PlainBuffer();
|
||||
List<String> keys = new ArrayList<String>(map.keySet());
|
||||
Collections.sort(keys);
|
||||
for (String key : keys) {
|
||||
buf.putString(key);
|
||||
String value = map.get(key);
|
||||
buf.putString(packString(value));
|
||||
}
|
||||
return buf.getCompactData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
src/main/java/net/schmizz/sshj/common/LoggerFactory.java
Normal file
38
src/main/java/net/schmizz/sshj/common/LoggerFactory.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.slf4j.Logger;
|
||||
|
||||
public interface LoggerFactory {
|
||||
Logger getLogger(String name);
|
||||
Logger getLogger(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Default SLF4J-based implementation of the SSHJ LoggerFactory.
|
||||
*/
|
||||
LoggerFactory DEFAULT = new LoggerFactory() {
|
||||
@Override
|
||||
public Logger getLogger(String name) {
|
||||
return org.slf4j.LoggerFactory.getLogger(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger(Class<?> clazz) {
|
||||
return org.slf4j.LoggerFactory.getLogger(clazz);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class SSHRuntimeException
|
||||
}
|
||||
|
||||
public SSHRuntimeException(Throwable cause) {
|
||||
this(null, cause);
|
||||
this(cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,58 +15,90 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Provider;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
|
||||
// TODO refactor
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
/** Static utility method relating to security facilities. */
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* Static utility method relating to security facilities.
|
||||
*/
|
||||
public class SecurityUtils {
|
||||
|
||||
private static class BouncyCastleRegistration {
|
||||
|
||||
public void run()
|
||||
throws Exception {
|
||||
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {
|
||||
LOG.debug("Trying to register BouncyCastle as a JCE provider");
|
||||
java.security.Security.addProvider(new BouncyCastleProvider());
|
||||
MessageDigest.getInstance("MD5", BOUNCY_CASTLE);
|
||||
KeyAgreement.getInstance("DH", BOUNCY_CASTLE);
|
||||
LOG.info("BouncyCastle registration succeeded");
|
||||
} else
|
||||
LOG.info("BouncyCastle already registered as a JCE provider");
|
||||
securityProvider = BOUNCY_CASTLE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SecurityUtils.class);
|
||||
|
||||
/** Identifier for the BouncyCastle JCE provider */
|
||||
/**
|
||||
* Identifier for the BouncyCastle JCE provider
|
||||
*/
|
||||
public static final String BOUNCY_CASTLE = "BC";
|
||||
|
||||
/**
|
||||
* Identifier for the BouncyCastle JCE provider
|
||||
*/
|
||||
public static final String SPONGY_CASTLE = "SC";
|
||||
|
||||
/*
|
||||
* Security provider identifier. null = default JCE
|
||||
*/
|
||||
private static String securityProvider = null;
|
||||
|
||||
// relate to BC registration
|
||||
// relate to BC registration (or SpongyCastle on Android)
|
||||
private static Boolean registerBouncyCastle;
|
||||
private static boolean registrationDone;
|
||||
|
||||
public static boolean registerSecurityProvider(String providerClassName) {
|
||||
Provider provider = null;
|
||||
try {
|
||||
Class<?> name = Class.forName(providerClassName);
|
||||
provider = (Provider) name.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOG.info("Security Provider class '{}' not found", providerClassName);
|
||||
} catch (InstantiationException e) {
|
||||
LOG.info("Security Provider class '{}' could not be created", providerClassName);
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.info("Security Provider class '{}' could not be accessed", providerClassName);
|
||||
}
|
||||
|
||||
if (provider == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Security.getProvider(provider.getName()) == null) {
|
||||
Security.addProvider(provider);
|
||||
}
|
||||
|
||||
if (securityProvider == null) {
|
||||
MessageDigest.getInstance("MD5", provider);
|
||||
KeyAgreement.getInstance("DH", provider);
|
||||
setSecurityProvider(provider.getName());
|
||||
return true;
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.info(format("Security Provider '%s' does not support necessary algorithm", providerClassName), e);
|
||||
} catch (Exception e) {
|
||||
LOG.info(format("Registration of Security Provider '%s' unexpectedly failed", providerClassName), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static synchronized Cipher getCipher(String transformation)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
|
||||
register();
|
||||
@@ -80,9 +112,7 @@ public class SecurityUtils {
|
||||
* Computes the fingerprint for a public key, in the standard SSH format, e.g. "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"
|
||||
*
|
||||
* @param key the public key
|
||||
*
|
||||
* @return the fingerprint
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00">specification</a>
|
||||
*/
|
||||
public static String getFingerprint(PublicKey key) {
|
||||
@@ -105,9 +135,7 @@ public class SecurityUtils {
|
||||
* Creates a new instance of {@link KeyAgreement} with the given algorithm.
|
||||
*
|
||||
* @param algorithm key agreement algorithm
|
||||
*
|
||||
* @return new instance
|
||||
*
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
@@ -124,9 +152,7 @@ public class SecurityUtils {
|
||||
* Creates a new instance of {@link KeyFactory} with the given algorithm.
|
||||
*
|
||||
* @param algorithm key factory algorithm e.g. RSA, DSA
|
||||
*
|
||||
* @return new instance
|
||||
*
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
@@ -143,9 +169,7 @@ public class SecurityUtils {
|
||||
* Creates a new instance of {@link KeyPairGenerator} with the given algorithm.
|
||||
*
|
||||
* @param algorithm key pair generator algorithm
|
||||
*
|
||||
* @return new instance
|
||||
*
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
@@ -162,9 +186,7 @@ public class SecurityUtils {
|
||||
* Create a new instance of {@link Mac} with the given algorithm.
|
||||
*
|
||||
* @param algorithm MAC algorithm
|
||||
*
|
||||
* @return new instance
|
||||
*
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
@@ -181,9 +203,7 @@ public class SecurityUtils {
|
||||
* Create a new instance of {@link MessageDigest} with the given algorithm.
|
||||
*
|
||||
* @param algorithm MessageDigest algorithm name
|
||||
*
|
||||
* @return new instance
|
||||
*
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
@@ -219,11 +239,11 @@ public class SecurityUtils {
|
||||
* Attempts registering BouncyCastle as security provider if it has not been previously attempted and returns
|
||||
* whether the registration succeeded.
|
||||
*
|
||||
* @return whether BC registered
|
||||
* @return whether BC (or SC on Android) registered
|
||||
*/
|
||||
public static synchronized boolean isBouncyCastleRegistered() {
|
||||
register();
|
||||
return BOUNCY_CASTLE.equals(securityProvider);
|
||||
return BOUNCY_CASTLE.equals(securityProvider) || SPONGY_CASTLE.equals(securityProvider);
|
||||
}
|
||||
|
||||
public static synchronized void setRegisterBouncyCastle(boolean registerBouncyCastle) {
|
||||
@@ -243,20 +263,16 @@ public class SecurityUtils {
|
||||
|
||||
private static void register() {
|
||||
if (!registrationDone) {
|
||||
if (securityProvider == null && (registerBouncyCastle == null || registerBouncyCastle))
|
||||
// Use an inner class to avoid a strong dependency on BouncyCastle
|
||||
try {
|
||||
new BouncyCastleRegistration().run();
|
||||
} catch (Throwable t) {
|
||||
if (registerBouncyCastle == null)
|
||||
LOG.info("BouncyCastle not registered, using the default JCE provider");
|
||||
else {
|
||||
LOG.error("Failed to register BouncyCastle as the defaut JCE provider");
|
||||
throw new SSHRuntimeException("Failed to register BouncyCastle as the defaut JCE provider", t);
|
||||
}
|
||||
if (securityProvider == null && (registerBouncyCastle == null || registerBouncyCastle)) {
|
||||
registerSecurityProvider("org.bouncycastle.jce.provider.BouncyCastleProvider");
|
||||
if (securityProvider == null && registerBouncyCastle == null) {
|
||||
LOG.info("BouncyCastle not registered, using the default JCE provider");
|
||||
} else if (securityProvider == null) {
|
||||
LOG.error("Failed to register BouncyCastle as the defaut JCE provider");
|
||||
throw new SSHRuntimeException("Failed to register BouncyCastle as the defaut JCE provider");
|
||||
}
|
||||
}
|
||||
registrationDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package net.schmizz.sshj.common;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.concurrent.ExceptionChainer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -39,8 +38,8 @@ public class StreamCopier {
|
||||
}
|
||||
};
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final LoggerFactory loggerFactory;
|
||||
private final Logger log;
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
|
||||
@@ -50,9 +49,11 @@ public class StreamCopier {
|
||||
private boolean keepFlushing = true;
|
||||
private long length = -1;
|
||||
|
||||
public StreamCopier(InputStream in, OutputStream out) {
|
||||
public StreamCopier(InputStream in, OutputStream out, LoggerFactory loggerFactory) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
this.loggerFactory = loggerFactory;
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
public StreamCopier bufSize(int bufSize) {
|
||||
@@ -66,8 +67,11 @@ public class StreamCopier {
|
||||
}
|
||||
|
||||
public StreamCopier listener(Listener listener) {
|
||||
if (listener == null) listener = NULL_LISTENER;
|
||||
this.listener = listener;
|
||||
if (listener == null) {
|
||||
this.listener = NULL_LISTENER;
|
||||
} else {
|
||||
this.listener = listener;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -91,7 +95,7 @@ public class StreamCopier {
|
||||
public IOException chain(Throwable t) {
|
||||
return (t instanceof IOException) ? (IOException) t : new IOException(t);
|
||||
}
|
||||
});
|
||||
}, loggerFactory);
|
||||
|
||||
new Thread() {
|
||||
{
|
||||
@@ -107,7 +111,7 @@ public class StreamCopier {
|
||||
log.debug("Done copying from {}", in);
|
||||
doneEvent.set();
|
||||
} catch (IOException ioe) {
|
||||
log.error("In pipe from {} to {}: {}", in, out, ioe);
|
||||
log.error(String.format("In pipe from %1$s to %2$s", in.toString(), out.toString()), ioe);
|
||||
doneEvent.deliverError(ioe);
|
||||
}
|
||||
}
|
||||
@@ -124,11 +128,13 @@ public class StreamCopier {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
if (length == -1) {
|
||||
while ((read = in.read(buf)) != -1)
|
||||
count = write(buf, count, read);
|
||||
while ((read = in.read(buf)) != -1) {
|
||||
count += write(buf, count, read);
|
||||
}
|
||||
} else {
|
||||
while (count < length && (read = in.read(buf, 0, (int) Math.min(bufSize, length - count))) != -1)
|
||||
count = write(buf, count, read);
|
||||
while (count < length && (read = in.read(buf, 0, (int) Math.min(bufSize, length - count))) != -1) {
|
||||
count += write(buf, count, read);
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepFlushing)
|
||||
@@ -136,7 +142,7 @@ public class StreamCopier {
|
||||
|
||||
final double timeSeconds = (System.currentTimeMillis() - startTime) / 1000.0;
|
||||
final double sizeKiB = count / 1024.0;
|
||||
log.debug("{} KiB transferred in {} seconds ({} KiB/s)", sizeKiB, timeSeconds, (sizeKiB / timeSeconds));
|
||||
log.debug(String.format("%1$,.1f KiB transferred in %2$,.1f seconds (%3$,.2f KiB/s)", sizeKiB, timeSeconds, (sizeKiB / timeSeconds)));
|
||||
|
||||
if (length != -1 && read == -1)
|
||||
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");
|
||||
@@ -144,14 +150,13 @@ public class StreamCopier {
|
||||
return count;
|
||||
}
|
||||
|
||||
private long write(byte[] buf, long count, int read)
|
||||
private long write(byte[] buf, long curPos, int len)
|
||||
throws IOException {
|
||||
out.write(buf, 0, read);
|
||||
count += read;
|
||||
out.write(buf, 0, len);
|
||||
if (keepFlushing)
|
||||
out.flush();
|
||||
listener.reportProgress(count);
|
||||
return count;
|
||||
listener.reportProgress(curPos + len);
|
||||
return len;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -156,4 +156,4 @@ public interface Connection {
|
||||
* @return The configured {@link net.schmizz.keepalive.KeepAlive} mechanism.
|
||||
*/
|
||||
KeepAlive getKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,7 @@ import net.schmizz.concurrent.Promise;
|
||||
import net.schmizz.keepalive.KeepAlive;
|
||||
import net.schmizz.keepalive.KeepAliveProvider;
|
||||
import net.schmizz.sshj.AbstractService;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.ErrorNotifiable;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.channel.Channel;
|
||||
import net.schmizz.sshj.connection.channel.OpenFailException.Reason;
|
||||
import net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener;
|
||||
@@ -131,10 +126,9 @@ public class ConnectionImpl
|
||||
@Override
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws SSHException {
|
||||
if (msg.in(91, 100))
|
||||
if (msg.in(91, 100)) {
|
||||
getChannel(buf).handle(msg, buf);
|
||||
|
||||
else if (msg.in(80, 90))
|
||||
} else if (msg.in(80, 90)) {
|
||||
switch (msg) {
|
||||
case REQUEST_SUCCESS:
|
||||
gotGlobalReqResponse(buf);
|
||||
@@ -147,10 +141,11 @@ public class ConnectionImpl
|
||||
break;
|
||||
default:
|
||||
super.handle(msg, buf);
|
||||
break;
|
||||
}
|
||||
|
||||
else
|
||||
} else {
|
||||
super.handle(msg, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,11 +174,11 @@ public class ConnectionImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void join()
|
||||
throws InterruptedException {
|
||||
public void join() throws InterruptedException {
|
||||
synchronized (internalSynchronizer) {
|
||||
while (!channels.isEmpty())
|
||||
while (!channels.isEmpty()) {
|
||||
internalSynchronizer.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +199,7 @@ public class ConnectionImpl
|
||||
|
||||
Promise<SSHPacket, ConnectionException> promise = null;
|
||||
if (wantReply) {
|
||||
promise = new Promise<SSHPacket, ConnectionException>("global req for " + name, ConnectionException.chainer);
|
||||
promise = new Promise<SSHPacket, ConnectionException>("global req for " + name, ConnectionException.chainer, trans.getConfig().getLoggerFactory());
|
||||
globalReqPromises.add(promise);
|
||||
}
|
||||
return promise;
|
||||
|
||||
@@ -17,22 +17,16 @@ package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.concurrent.ErrorDeliveryUtil;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -44,7 +38,8 @@ public abstract class AbstractChannel
|
||||
private static final int REMOTE_MAX_PACKET_SIZE_CEILING = 1024 * 1024;
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final LoggerFactory loggerFactory;
|
||||
protected final Logger log;
|
||||
|
||||
/** Transport layer */
|
||||
protected final Transport trans;
|
||||
@@ -57,6 +52,10 @@ public abstract class AbstractChannel
|
||||
private final int id;
|
||||
/** Remote recipient ID */
|
||||
private int recipient;
|
||||
/** Remote character set */
|
||||
private final Charset remoteCharset;
|
||||
|
||||
private boolean eof = false;
|
||||
|
||||
private final Queue<Event<ConnectionException>> chanReqResponseEvents = new LinkedList<Event<ConnectionException>>();
|
||||
|
||||
@@ -82,22 +81,28 @@ public abstract class AbstractChannel
|
||||
private volatile boolean autoExpand = false;
|
||||
|
||||
protected AbstractChannel(Connection conn, String type) {
|
||||
this(conn, type, null);
|
||||
}
|
||||
protected AbstractChannel(Connection conn, String type, Charset remoteCharset) {
|
||||
this.conn = conn;
|
||||
this.loggerFactory = conn.getTransport().getConfig().getLoggerFactory();
|
||||
this.type = type;
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
this.trans = conn.getTransport();
|
||||
|
||||
this.remoteCharset = remoteCharset != null ? remoteCharset : IOUtils.UTF8;
|
||||
id = conn.nextID();
|
||||
|
||||
lwin = new Window.Local(conn.getWindowSize(), conn.getMaxPacketSize());
|
||||
lwin = new Window.Local(conn.getWindowSize(), conn.getMaxPacketSize(), loggerFactory);
|
||||
in = new ChannelInputStream(this, trans, lwin);
|
||||
|
||||
openEvent = new Event<ConnectionException>("chan#" + id + " / " + "open", ConnectionException.chainer, openCloseLock);
|
||||
closeEvent = new Event<ConnectionException>("chan#" + id + " / " + "close", ConnectionException.chainer, openCloseLock);
|
||||
openEvent = new Event<ConnectionException>("chan#" + id + " / " + "open", ConnectionException.chainer, openCloseLock, loggerFactory);
|
||||
closeEvent = new Event<ConnectionException>("chan#" + id + " / " + "close", ConnectionException.chainer, openCloseLock, loggerFactory);
|
||||
}
|
||||
|
||||
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));
|
||||
rwin = new Window.Remote(remoteWinSize, (int) Math.min(remoteMaxPacketSize, REMOTE_MAX_PACKET_SIZE_CEILING), loggerFactory);
|
||||
out = new ChannelOutputStream(this, trans, rwin);
|
||||
log.debug("Initialized - {}", this);
|
||||
}
|
||||
@@ -137,6 +142,11 @@ public abstract class AbstractChannel
|
||||
return recipient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Charset getRemoteCharset() {
|
||||
return remoteCharset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteMaxPacketSize() {
|
||||
return rwin.getMaxPacketSize();
|
||||
@@ -191,10 +201,20 @@ public abstract class AbstractChannel
|
||||
|
||||
default:
|
||||
gotUnknown(msg, buf);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEOF() {
|
||||
return eof;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggerFactory getLoggerFactory() {
|
||||
return loggerFactory;
|
||||
}
|
||||
|
||||
private void gotClose()
|
||||
throws TransportException {
|
||||
log.debug("Got close");
|
||||
@@ -321,6 +341,8 @@ public abstract class AbstractChannel
|
||||
|
||||
protected void gotUnknown(Message msg, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
log.warn("Got unknown packet with type {}", msg);
|
||||
|
||||
}
|
||||
|
||||
protected void handleRequest(String reqType, SSHPacket buf)
|
||||
@@ -362,7 +384,7 @@ public abstract class AbstractChannel
|
||||
Event<ConnectionException> responseEvent = null;
|
||||
if (wantReply) {
|
||||
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType,
|
||||
ConnectionException.chainer);
|
||||
ConnectionException.chainer, loggerFactory);
|
||||
chanReqResponseEvents.add(responseEvent);
|
||||
}
|
||||
return responseEvent;
|
||||
@@ -393,6 +415,7 @@ public abstract class AbstractChannel
|
||||
/** Called when EOF has been received. Subclasses can override but must call super. */
|
||||
protected void eofInputStreams() {
|
||||
in.eof();
|
||||
eof = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -402,4 +425,4 @@ public abstract class AbstractChannel
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.common.ErrorNotifiable;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHPacketHandler;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
@@ -23,15 +24,18 @@ import net.schmizz.sshj.transport.TransportException;
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** A channel is the basic medium for application-layer data on top of an SSH transport. */
|
||||
public interface Channel
|
||||
extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
/**
|
||||
* A channel is the basic medium for application-layer data on top of an SSH transport.
|
||||
*/
|
||||
public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
|
||||
/** Direct channels are those that are initiated by us. */
|
||||
interface Direct
|
||||
extends Channel {
|
||||
/**
|
||||
* Direct channels are those that are initiated by us.
|
||||
*/
|
||||
interface Direct extends Channel {
|
||||
|
||||
/**
|
||||
* Request opening this channel from remote end.
|
||||
@@ -40,27 +44,30 @@ public interface Channel
|
||||
* @throws ConnectionException other connection-layer error
|
||||
* @throws TransportException error writing packets etc.
|
||||
*/
|
||||
void open()
|
||||
throws ConnectionException, TransportException;
|
||||
void open() throws ConnectionException, TransportException;
|
||||
|
||||
}
|
||||
|
||||
/** Forwarded channels are those that are initiated by the server. */
|
||||
interface Forwarded
|
||||
extends Channel {
|
||||
/**
|
||||
* Forwarded channels are those that are initiated by the server.
|
||||
*/
|
||||
interface Forwarded extends Channel {
|
||||
|
||||
/**
|
||||
* Confirm {@code CHANNEL_OPEN} request.
|
||||
*
|
||||
* @throws TransportException error sending confirmation packet
|
||||
*/
|
||||
void confirm()
|
||||
throws TransportException;
|
||||
void confirm() throws TransportException;
|
||||
|
||||
/** @return the IP of where the forwarded connection originates. */
|
||||
/**
|
||||
* @return the IP of where the forwarded connection originates.
|
||||
*/
|
||||
String getOriginatorIP();
|
||||
|
||||
/** @return port from which the forwarded connection originates. */
|
||||
/**
|
||||
* @return port from which the forwarded connection originates.
|
||||
*/
|
||||
int getOriginatorPort();
|
||||
|
||||
/**
|
||||
@@ -68,55 +75,76 @@ public interface Channel
|
||||
*
|
||||
* @param reason indicate {@link OpenFailException.Reason reason} for rejection of the request
|
||||
* @param message indicate a message for why the request is rejected
|
||||
*
|
||||
* @throws TransportException error sending rejection packet
|
||||
*/
|
||||
void reject(OpenFailException.Reason reason, String message)
|
||||
throws TransportException;
|
||||
void reject(OpenFailException.Reason reason, String message) throws TransportException;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Close this channel. */
|
||||
/**
|
||||
* Close this channel.
|
||||
*/
|
||||
@Override
|
||||
void close()
|
||||
throws TransportException, ConnectionException;
|
||||
void close() throws TransportException, ConnectionException;
|
||||
|
||||
/**
|
||||
* @return whether auto-expansion of local window is set.
|
||||
*
|
||||
* @see #setAutoExpand(boolean)
|
||||
*/
|
||||
boolean getAutoExpand();
|
||||
|
||||
/** @return the channel ID */
|
||||
/**
|
||||
* @return the channel ID
|
||||
*/
|
||||
int getID();
|
||||
|
||||
/** @return the {@code InputStream} for this channel. */
|
||||
/**
|
||||
* @return the {@code InputStream} for this channel.
|
||||
*/
|
||||
InputStream getInputStream();
|
||||
|
||||
/** @return the maximum packet size that we have specified. */
|
||||
/**
|
||||
* @return the maximum packet size that we have specified.
|
||||
*/
|
||||
int getLocalMaxPacketSize();
|
||||
|
||||
/** @return the current local window size. */
|
||||
/**
|
||||
* @return the current local window size.
|
||||
*/
|
||||
long getLocalWinSize();
|
||||
|
||||
/** @return an {@code OutputStream} for this channel. */
|
||||
/**
|
||||
* @return an {@code OutputStream} for this channel.
|
||||
*/
|
||||
OutputStream getOutputStream();
|
||||
|
||||
/** @return the channel ID at the remote end. */
|
||||
/**
|
||||
* @return the channel ID at the remote end.
|
||||
*/
|
||||
int getRecipient();
|
||||
|
||||
/** @return the maximum packet size as specified by the remote end. */
|
||||
/** @return the character set used to communicate with the remote machine for certain strings (like paths). */
|
||||
Charset getRemoteCharset();
|
||||
|
||||
/**
|
||||
* @return the maximum packet size as specified by the remote end.
|
||||
*/
|
||||
int getRemoteMaxPacketSize();
|
||||
|
||||
/** @return the current remote window size. */
|
||||
/**
|
||||
* @return the current remote window size.
|
||||
*/
|
||||
long getRemoteWinSize();
|
||||
|
||||
/** @return the channel type identifier. */
|
||||
/**
|
||||
* @return the channel type identifier.
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/** @return whether the channel is open. */
|
||||
/**
|
||||
* @return whether the channel is open.
|
||||
*/
|
||||
boolean isOpen();
|
||||
|
||||
/**
|
||||
@@ -128,10 +156,17 @@ public interface Channel
|
||||
*/
|
||||
void setAutoExpand(boolean autoExpand);
|
||||
|
||||
void join()
|
||||
throws ConnectionException;
|
||||
void join() throws ConnectionException;
|
||||
|
||||
void join(long timeout, TimeUnit unit)
|
||||
throws ConnectionException;
|
||||
void join(long timeout, TimeUnit unit) throws ConnectionException;
|
||||
|
||||
/**
|
||||
* Returns whether EOF has been received.
|
||||
*/
|
||||
boolean isEOF();
|
||||
|
||||
/**
|
||||
* Get the LoggerFactory associated with the SSH client.
|
||||
*/
|
||||
LoggerFactory getLoggerFactory();
|
||||
}
|
||||
|
||||
@@ -15,16 +15,11 @@
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ErrorNotifiable;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -38,7 +33,7 @@ public final class ChannelInputStream
|
||||
extends InputStream
|
||||
implements ErrorNotifiable {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
private final Logger log;
|
||||
|
||||
private final Channel chan;
|
||||
private final Transport trans;
|
||||
@@ -51,6 +46,7 @@ public final class ChannelInputStream
|
||||
|
||||
public ChannelInputStream(Channel chan, Transport trans, Window.Local win) {
|
||||
this.chan = chan;
|
||||
log = chan.getLoggerFactory().getLogger(getClass());
|
||||
this.trans = trans;
|
||||
this.win = win;
|
||||
buf = new Buffer.PlainBuffer(chan.getLocalMaxPacketSize());
|
||||
|
||||
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ErrorNotifiable;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
@@ -31,9 +27,7 @@ import java.io.OutputStream;
|
||||
* {@link OutputStream} for channels. Buffers data upto the remote window's maximum packet size. Data can also be
|
||||
* flushed via {@link #flush()} and is also flushed on {@link #close()}.
|
||||
*/
|
||||
public final class ChannelOutputStream
|
||||
extends OutputStream
|
||||
implements ErrorNotifiable {
|
||||
public final class ChannelOutputStream extends OutputStream implements ErrorNotifiable {
|
||||
|
||||
private final Channel chan;
|
||||
private final Transport trans;
|
||||
@@ -60,8 +54,7 @@ public final class ChannelOutputStream
|
||||
dataOffset = packet.wpos();
|
||||
}
|
||||
|
||||
int write(byte[] data, int off, int len)
|
||||
throws TransportException, ConnectionException {
|
||||
int write(byte[] data, int off, int len) throws TransportException, ConnectionException {
|
||||
final int bufferSize = packet.wpos() - dataOffset;
|
||||
if (bufferSize >= win.getMaxPacketSize()) {
|
||||
flush(bufferSize, true);
|
||||
@@ -73,15 +66,13 @@ public final class ChannelOutputStream
|
||||
}
|
||||
}
|
||||
|
||||
boolean flush(boolean canAwaitExpansion)
|
||||
throws TransportException, ConnectionException {
|
||||
boolean flush(boolean canAwaitExpansion) throws TransportException, ConnectionException {
|
||||
return flush(packet.wpos() - dataOffset, canAwaitExpansion);
|
||||
}
|
||||
|
||||
boolean flush(int bufferSize, boolean canAwaitExpansion)
|
||||
throws TransportException, ConnectionException {
|
||||
while (bufferSize > 0) {
|
||||
|
||||
boolean flush(int bufferSize, boolean canAwaitExpansion) throws TransportException, ConnectionException {
|
||||
int dataLeft = bufferSize;
|
||||
while (dataLeft > 0) {
|
||||
long remoteWindowSize = win.getSize();
|
||||
if (remoteWindowSize == 0) {
|
||||
if (canAwaitExpansion) {
|
||||
@@ -95,7 +86,7 @@ public final class ChannelOutputStream
|
||||
// a) how much data we have
|
||||
// b) the max packet size
|
||||
// c) what the current window size will allow
|
||||
final int writeNow = Math.min(bufferSize, (int) Math.min(win.getMaxPacketSize(), remoteWindowSize));
|
||||
final int writeNow = Math.min(dataLeft, (int) Math.min(win.getMaxPacketSize(), remoteWindowSize));
|
||||
|
||||
packet.wpos(headerOffset);
|
||||
packet.putMessageID(Message.CHANNEL_DATA);
|
||||
@@ -103,7 +94,7 @@ public final class ChannelOutputStream
|
||||
packet.putUInt32(writeNow);
|
||||
packet.wpos(dataOffset + writeNow);
|
||||
|
||||
final int leftOverBytes = bufferSize - writeNow;
|
||||
final int leftOverBytes = dataLeft - writeNow;
|
||||
if (leftOverBytes > 0) {
|
||||
leftOvers.putRawBytes(packet.array(), packet.wpos(), leftOverBytes);
|
||||
}
|
||||
@@ -119,7 +110,7 @@ public final class ChannelOutputStream
|
||||
leftOvers.clear();
|
||||
}
|
||||
|
||||
bufferSize = leftOverBytes;
|
||||
dataLeft = leftOverBytes;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -144,10 +135,12 @@ public final class ChannelOutputStream
|
||||
public synchronized void write(final byte[] data, int off, int len)
|
||||
throws IOException {
|
||||
checkClose();
|
||||
while (len > 0) {
|
||||
final int n = buffer.write(data, off, len);
|
||||
off += n;
|
||||
len -= n;
|
||||
int length = len;
|
||||
int offset = off;
|
||||
while (length > 0) {
|
||||
final int n = buffer.write(data, offset, length);
|
||||
offset += n;
|
||||
length -= n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,8 +149,7 @@ public final class ChannelOutputStream
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
private void checkClose()
|
||||
throws SSHException {
|
||||
private void checkClose() throws SSHException {
|
||||
if (closed) {
|
||||
if (error != null)
|
||||
throw error;
|
||||
@@ -167,8 +159,7 @@ public final class ChannelOutputStream
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close()
|
||||
throws IOException {
|
||||
public synchronized void close() throws IOException {
|
||||
if (!closed) {
|
||||
try {
|
||||
buffer.flush(false);
|
||||
@@ -186,8 +177,7 @@ public final class ChannelOutputStream
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public synchronized void flush()
|
||||
throws IOException {
|
||||
public synchronized void flush() throws IOException {
|
||||
checkClose();
|
||||
buffer.flush(true);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class Window {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final Logger log;
|
||||
|
||||
protected final Object lock = new Object();
|
||||
|
||||
@@ -30,9 +30,10 @@ public abstract class Window {
|
||||
|
||||
protected long size;
|
||||
|
||||
public Window(long initialWinSize, int maxPacketSize) {
|
||||
public Window(long initialWinSize, int maxPacketSize, LoggerFactory loggerFactory) {
|
||||
size = initialWinSize;
|
||||
this.maxPacketSize = maxPacketSize;
|
||||
log = loggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
public void expand(long inc) {
|
||||
@@ -72,12 +73,11 @@ public abstract class Window {
|
||||
public static final class Remote
|
||||
extends Window {
|
||||
|
||||
public Remote(long initialWinSize, int maxPacketSize) {
|
||||
super(initialWinSize, maxPacketSize);
|
||||
public Remote(long initialWinSize, int maxPacketSize, LoggerFactory loggerFactory) {
|
||||
super(initialWinSize, maxPacketSize, loggerFactory);
|
||||
}
|
||||
|
||||
public long awaitExpansion(long was)
|
||||
throws ConnectionException {
|
||||
public long awaitExpansion(long was) throws ConnectionException {
|
||||
synchronized (lock) {
|
||||
while (size <= was) {
|
||||
log.debug("Waiting, need size to grow from {} bytes", was);
|
||||
@@ -108,8 +108,8 @@ public abstract class Window {
|
||||
private final long initialSize;
|
||||
private final long threshold;
|
||||
|
||||
public Local(long initialWinSize, int maxPacketSize) {
|
||||
super(initialWinSize, maxPacketSize);
|
||||
public Local(long initialWinSize, int maxPacketSize, LoggerFactory loggerFactory) {
|
||||
super(initialWinSize, maxPacketSize, loggerFactory);
|
||||
this.initialSize = initialWinSize;
|
||||
threshold = Math.min(maxPacketSize * 20, initialSize / 4);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import net.schmizz.sshj.connection.channel.Channel;
|
||||
import net.schmizz.sshj.connection.channel.OpenFailException;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Base class for direct channels whose open is initated by the client. */
|
||||
@@ -41,6 +42,15 @@ public abstract class AbstractDirectChannel
|
||||
conn.attach(this);
|
||||
}
|
||||
|
||||
protected AbstractDirectChannel(Connection conn, String type, Charset remoteCharset) {
|
||||
super(conn, type, remoteCharset);
|
||||
|
||||
/*
|
||||
* We expect to receive channel open confirmation/rejection and want to be able to next this packet.
|
||||
*/
|
||||
conn.attach(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open()
|
||||
throws ConnectionException, TransportException {
|
||||
@@ -94,4 +104,4 @@ public abstract class AbstractDirectChannel
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,17 @@ 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;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.hierynomus.sshj.backport.Sockets.asCloseable;
|
||||
@@ -82,10 +82,10 @@ public class LocalPortForwarder {
|
||||
throws IOException {
|
||||
socket.setSendBufferSize(getLocalMaxPacketSize());
|
||||
socket.setReceiveBufferSize(getRemoteMaxPacketSize());
|
||||
final Event<IOException> soc2chan = new StreamCopier(socket.getInputStream(), getOutputStream())
|
||||
final Event<IOException> soc2chan = new StreamCopier(socket.getInputStream(), getOutputStream(), loggerFactory)
|
||||
.bufSize(getRemoteMaxPacketSize())
|
||||
.spawnDaemon("soc2chan");
|
||||
final Event<IOException> chan2soc = new StreamCopier(getInputStream(), socket.getOutputStream())
|
||||
final Event<IOException> chan2soc = new StreamCopier(getInputStream(), socket.getOutputStream(), loggerFactory)
|
||||
.bufSize(getLocalMaxPacketSize())
|
||||
.spawnDaemon("chan2soc");
|
||||
SocketStreamCopyMonitor.monitor(5, TimeUnit.SECONDS, soc2chan, chan2soc, this, socket);
|
||||
@@ -102,16 +102,19 @@ public class LocalPortForwarder {
|
||||
|
||||
}
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(LocalPortForwarder.class);
|
||||
|
||||
private final LoggerFactory loggerFactory;
|
||||
private final Logger log;
|
||||
private final Connection conn;
|
||||
private final Parameters parameters;
|
||||
private final ServerSocket serverSocket;
|
||||
private Thread runningThread;
|
||||
|
||||
public LocalPortForwarder(Connection conn, Parameters parameters, ServerSocket serverSocket) {
|
||||
public LocalPortForwarder(Connection conn, Parameters parameters, ServerSocket serverSocket, LoggerFactory loggerFactory) {
|
||||
this.conn = conn;
|
||||
this.parameters = parameters;
|
||||
this.serverSocket = serverSocket;
|
||||
this.loggerFactory = loggerFactory;
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
private void startChannel(Socket socket) throws IOException {
|
||||
@@ -130,15 +133,57 @@ public class LocalPortForwarder {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void listen()
|
||||
throws IOException {
|
||||
log.info("Listening on {}", serverSocket.getLocalSocketAddress());
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
final Socket socket = serverSocket.accept();
|
||||
log.debug("Got connection from {}", socket.getRemoteSocketAddress());
|
||||
startChannel(socket);
|
||||
}
|
||||
log.debug("Interrupted!");
|
||||
public void listen() throws IOException {
|
||||
listen(Thread.currentThread());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Returns whether this listener is running (ie. whether a thread is attached to it).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return this.runningThread != null && !serverSocket.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening for incoming connections and forward to remote host as a channel and ensure that the thread is registered.
|
||||
* This is useful if for instance {@link #close() is called from another thread}
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void listen(Thread runningThread) throws IOException {
|
||||
this.runningThread = runningThread;
|
||||
log.info("Listening on {}", serverSocket.getLocalSocketAddress());
|
||||
while (!runningThread.isInterrupted()) {
|
||||
try {
|
||||
final Socket socket = serverSocket.accept();
|
||||
log.debug("Got connection from {}", socket.getRemoteSocketAddress());
|
||||
startChannel(socket);
|
||||
} catch (SocketException e) {
|
||||
if (!serverSocket.isClosed()) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (serverSocket.isClosed()) {
|
||||
log.debug("LocalPortForwarder closed");
|
||||
} else {
|
||||
log.debug("LocalPortForwarder interrupted!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the ServerSocket that's listening for connections to forward.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (!serverSocket.isClosed()) {
|
||||
log.info("Closing listener on {}", serverSocket.getLocalSocketAddress());
|
||||
serverSocket.close();
|
||||
runningThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,18 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.direct;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.connection.channel.ChannelInputStream;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -52,6 +48,10 @@ public class SessionChannel
|
||||
super(conn, "session");
|
||||
}
|
||||
|
||||
public SessionChannel(Connection conn, Charset remoteCharset) {
|
||||
super(conn, "session", remoteCharset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allocateDefaultPTY()
|
||||
throws ConnectionException, TransportException {
|
||||
@@ -98,7 +98,7 @@ public class SessionChannel
|
||||
throws ConnectionException, TransportException {
|
||||
checkReuse();
|
||||
log.debug("Will request to exec `{}`", command);
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command))
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command, getRemoteCharset()))
|
||||
.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
|
||||
usedUp = true;
|
||||
return this;
|
||||
|
||||
@@ -73,4 +73,4 @@ public abstract class AbstractForwardedChannel
|
||||
return origPort;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import net.schmizz.sshj.connection.channel.Channel;
|
||||
import net.schmizz.sshj.connection.channel.OpenFailException;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -29,14 +28,14 @@ import java.io.IOException;
|
||||
public abstract class AbstractForwardedChannelOpener
|
||||
implements ForwardedChannelOpener {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected final Logger log;
|
||||
protected final String chanType;
|
||||
protected final Connection conn;
|
||||
|
||||
protected AbstractForwardedChannelOpener(String chanType, Connection conn) {
|
||||
this.chanType = chanType;
|
||||
this.conn = conn;
|
||||
log = conn.getTransport().getConfig().getLoggerFactory().getLogger(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,4 +71,4 @@ public abstract class AbstractForwardedChannelOpener
|
||||
}.start();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public class RemotePortForwarder
|
||||
// Addresses match up
|
||||
return true;
|
||||
}
|
||||
if ("localhost".equals(address) && (channelForward.address.equals("127.0.0.1") || channelForward.address.equals("::1"))) {
|
||||
if ("localhost".equals(address) && ("127.0.0.1".equals(channelForward.address) || "::1".equals(channelForward.address))) {
|
||||
// Localhost special case.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.common.StreamCopier;
|
||||
import net.schmizz.sshj.connection.channel.Channel;
|
||||
import net.schmizz.sshj.connection.channel.SocketStreamCopyMonitor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
@@ -31,8 +29,6 @@ import java.util.concurrent.TimeUnit;
|
||||
public class SocketForwardingConnectListener
|
||||
implements ConnectListener {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected final SocketAddress addr;
|
||||
|
||||
/** Create with a {@link SocketAddress} this listener will forward to. */
|
||||
@@ -44,7 +40,7 @@ public class SocketForwardingConnectListener
|
||||
@Override
|
||||
public void gotConnect(Channel.Forwarded chan)
|
||||
throws IOException {
|
||||
log.debug("New connection from {}:{}", chan.getOriginatorIP(), chan.getOriginatorPort());
|
||||
chan.getLoggerFactory().getLogger(getClass()).debug("New connection from {}:{}", chan.getOriginatorIP(), chan.getOriginatorPort());
|
||||
|
||||
final Socket sock = new Socket();
|
||||
sock.setSendBufferSize(chan.getLocalMaxPacketSize());
|
||||
@@ -55,11 +51,11 @@ public class SocketForwardingConnectListener
|
||||
// ok so far -- could connect, let's confirm the channel
|
||||
chan.confirm();
|
||||
|
||||
final Event<IOException> soc2chan = new StreamCopier(sock.getInputStream(), chan.getOutputStream())
|
||||
final Event<IOException> soc2chan = new StreamCopier(sock.getInputStream(), chan.getOutputStream(), chan.getLoggerFactory())
|
||||
.bufSize(chan.getRemoteMaxPacketSize())
|
||||
.spawnDaemon("soc2chan");
|
||||
|
||||
final Event<IOException> chan2soc = new StreamCopier(chan.getInputStream(), sock.getOutputStream())
|
||||
final Event<IOException> chan2soc = new StreamCopier(chan.getInputStream(), sock.getOutputStream(), chan.getLoggerFactory())
|
||||
.bufSize(chan.getLocalMaxPacketSize())
|
||||
.spawnDaemon("chan2soc");
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ public final class FileAttributes {
|
||||
sb.append("size=").append(size).append(";");
|
||||
|
||||
if (has(Flag.UIDGID))
|
||||
sb.append("uid=").append(size).append(",gid=").append(gid).append(";");
|
||||
sb.append("uid=").append(uid).append(",gid=").append(gid).append(";");
|
||||
|
||||
if (has(Flag.MODE))
|
||||
sb.append("mode=").append(mode.toString()).append(";");
|
||||
|
||||
@@ -36,7 +36,7 @@ public class FileMode {
|
||||
/** directory */
|
||||
DIRECTORY(0040000),
|
||||
/** symbolic link */
|
||||
SYMKLINK(0120000),
|
||||
SYMLINK(0120000),
|
||||
/** unknown */
|
||||
UNKNOWN(0);
|
||||
|
||||
|
||||
@@ -17,20 +17,19 @@ package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.concurrent.Promise;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PacketReader
|
||||
extends Thread {
|
||||
public class PacketReader extends Thread {
|
||||
|
||||
/** Logger */
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private final Logger log;
|
||||
|
||||
private final InputStream in;
|
||||
private final Map<Long, Promise<Response, SFTPException>> promises = new ConcurrentHashMap<Long, Promise<Response, SFTPException>>();
|
||||
@@ -40,6 +39,7 @@ public class PacketReader
|
||||
|
||||
public PacketReader(SFTPEngine engine) {
|
||||
this.engine = engine;
|
||||
log = engine.getLoggerFactory().getLogger(getClass());
|
||||
this.in = engine.getSubsystem().getInputStream();
|
||||
setName("sftp reader");
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class PacketReader
|
||||
| lenBuf[3] & 0x000000ffL);
|
||||
|
||||
if (len > SFTPPacket.MAX_SIZE) {
|
||||
throw new SSHException(String.format("Indicated packet length %d too large", len));
|
||||
throw new SSHException(String.format("Indicated packet length %d too large", len));
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
@@ -100,14 +100,14 @@ public class PacketReader
|
||||
log.debug("Received {} packet", resp.getType());
|
||||
if (promise == null)
|
||||
throw new SFTPException("Received [" + resp.readType() + "] response for request-id " + resp.getRequestID()
|
||||
+ ", no such request was made");
|
||||
+ ", no such request was made");
|
||||
else
|
||||
promise.deliver(resp);
|
||||
}
|
||||
|
||||
public Promise<Response, SFTPException> expectResponseTo(long requestId) {
|
||||
final Promise<Response, SFTPException> promise
|
||||
= new Promise<Response, SFTPException>("sftp / " + requestId, SFTPException.chainer);
|
||||
= new Promise<Response, SFTPException>("sftp / " + requestId, SFTPException.chainer, engine.getLoggerFactory());
|
||||
promises.put(requestId, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class PathHelper {
|
||||
if (path.equals(pathSep))
|
||||
return getComponents("", "");
|
||||
|
||||
if (path.isEmpty() || path.equals(".") || path.equals("." + pathSep))
|
||||
if (path.isEmpty() || ".".equals(path) || ("." + pathSep).equals(path))
|
||||
return getComponents(getDotDir());
|
||||
|
||||
final String withoutTrailSep = trimTrailingSeparator(path);
|
||||
@@ -81,7 +81,7 @@ public class PathHelper {
|
||||
final String parent = (lastSep == -1) ? "" : withoutTrailSep.substring(0, lastSep);
|
||||
final String name = (lastSep == -1) ? withoutTrailSep : withoutTrailSep.substring(lastSep + pathSep.length());
|
||||
|
||||
if (name.equals(".") || name.equals("..")) {
|
||||
if (".".equals(name) || "..".equals(name)) {
|
||||
return getComponents(canonicalizer.canonicalize(path));
|
||||
} else {
|
||||
return getComponents(parent, name);
|
||||
|
||||
@@ -15,12 +15,7 @@
|
||||
*/
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
|
||||
public class RandomAccessRemoteFile
|
||||
implements DataInput, DataOutput {
|
||||
|
||||
@@ -25,13 +25,14 @@ import java.util.concurrent.TimeUnit;
|
||||
public class RemoteDirectory
|
||||
extends RemoteResource {
|
||||
|
||||
public RemoteDirectory(Requester requester, String path, byte[] handle) {
|
||||
public RemoteDirectory(SFTPEngine requester, String path, byte[] handle) {
|
||||
super(requester, path, handle);
|
||||
}
|
||||
|
||||
public List<RemoteResourceInfo> scan(RemoteResourceFilter filter)
|
||||
throws IOException {
|
||||
List<RemoteResourceInfo> rri = new LinkedList<RemoteResourceInfo>();
|
||||
// TODO: Remove GOTO!
|
||||
loop:
|
||||
for (; ; ) {
|
||||
final Response res = requester.request(newRequest(PacketType.READDIR))
|
||||
@@ -41,13 +42,14 @@ public class RemoteDirectory
|
||||
case NAME:
|
||||
final int count = res.readUInt32AsInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final String name = res.readString();
|
||||
final String name = res.readString(requester.sub.getRemoteCharset());
|
||||
res.readString(); // long name - IGNORED - shdve never been in the protocol
|
||||
final FileAttributes attrs = res.readFileAttributes();
|
||||
final PathComponents comps = requester.getPathHelper().getComponents(path, name);
|
||||
final RemoteResourceInfo inf = new RemoteResourceInfo(comps, attrs);
|
||||
if (!(name.equals(".") || name.equals("..")) && (filter == null || filter.accept(inf)))
|
||||
if (!(".".equals(name) || "..".equals(name)) && (filter == null || filter.accept(inf))) {
|
||||
rri.add(inf);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.concurrent.TimeUnit;
|
||||
public class RemoteFile
|
||||
extends RemoteResource {
|
||||
|
||||
public RemoteFile(Requester requester, String path, byte[] handle) {
|
||||
public RemoteFile(SFTPEngine requester, String path, byte[] handle) {
|
||||
super(requester, path, handle);
|
||||
}
|
||||
|
||||
@@ -81,10 +81,8 @@ public class RemoteFile
|
||||
protected Promise<Response, SFTPException> asyncWrite(long fileOffset, byte[] data, int off, int len)
|
||||
throws IOException {
|
||||
return requester.request(newRequest(PacketType.WRITE)
|
||||
.putUInt64(fileOffset)
|
||||
// TODO The SFTP spec claims this field is unneeded...? See #187
|
||||
.putUInt32(len)
|
||||
.putRawBytes(data, off, len)
|
||||
.putUInt64(fileOffset)
|
||||
.putString(data, off, len)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,10 +192,10 @@ public class RemoteFile
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
final long fileLength = length();
|
||||
final Long previousFileOffset = fileOffset;
|
||||
fileOffset = Math.min(fileOffset + n, fileLength);
|
||||
return fileOffset - previousFileOffset;
|
||||
final long fileLength = length();
|
||||
final Long previousFileOffset = fileOffset;
|
||||
fileOffset = Math.min(fileOffset + n, fileLength);
|
||||
return fileOffset - previousFileOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -341,7 +339,7 @@ public class RemoteFile
|
||||
public int available() throws IOException {
|
||||
boolean lastRead = true;
|
||||
while (!eof && (pending.available() <= 0) && lastRead) {
|
||||
lastRead = retrieveUnconfirmedRead(false /*blocking*/);
|
||||
lastRead = retrieveUnconfirmedRead(false /*blocking*/);
|
||||
}
|
||||
return pending.available();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@@ -26,14 +25,15 @@ public abstract class RemoteResource
|
||||
implements Closeable {
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final Logger log;
|
||||
|
||||
protected final Requester requester;
|
||||
protected final SFTPEngine requester;
|
||||
protected final String path;
|
||||
protected final byte[] handle;
|
||||
|
||||
protected RemoteResource(Requester requester, String path, byte[] handle) {
|
||||
protected RemoteResource(SFTPEngine requester, String path, byte[] handle) {
|
||||
this.requester = requester;
|
||||
log = requester.getLoggerFactory().getLogger(getClass());
|
||||
this.path = path;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import net.schmizz.sshj.common.Buffer;
|
||||
public final class Response
|
||||
extends SFTPPacket<Response> {
|
||||
|
||||
public static enum StatusCode {
|
||||
public enum StatusCode {
|
||||
UNKNOWN(-1),
|
||||
OK(0),
|
||||
EOF(1),
|
||||
@@ -30,7 +30,30 @@ public final class Response
|
||||
BAD_MESSAGE(5),
|
||||
NO_CONNECTION(6),
|
||||
CONNECITON_LOST(7),
|
||||
OP_UNSUPPORTED(8);
|
||||
OP_UNSUPPORTED(8),
|
||||
INVALID_HANDLE(9),
|
||||
NO_SUCH_PATH(10),
|
||||
FILE_ALREADY_EXISTS(11),
|
||||
WRITE_PROTECT(12),
|
||||
NO_MEDIA(13),
|
||||
NO_SPACE_ON_FILESYSTEM(14),
|
||||
QUOTA_EXCEEDED(15),
|
||||
UNKNOWN_PRINCIPAL(16),
|
||||
LOCK_CONFLICT(17),
|
||||
DIR_NOT_EMPTY(18),
|
||||
NOT_A_DIRECTORY(19),
|
||||
INVALID_FILENAME(20),
|
||||
LINK_LOOP(21),
|
||||
CANNOT_DELETE(22),
|
||||
INVALID_PARAMETER(23),
|
||||
FILE_IS_A_DIRECTORY(24),
|
||||
BYTE_RANGE_LOCK_CONFLICT(25),
|
||||
BYTE_RANGE_LOCK_REFUSED(26),
|
||||
DELETE_PENDING(27),
|
||||
FILE_CORRUPT(28),
|
||||
OWNER_INVALID(29),
|
||||
GROUP_INVALID(30),
|
||||
NO_MATCHING_BYTE_RANGE_LOCK(31);
|
||||
|
||||
private final int code;
|
||||
|
||||
@@ -99,6 +122,7 @@ public final class Response
|
||||
return ensurePacketTypeIs(PacketType.STATUS).ensureStatusIs(StatusCode.OK);
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
public Response ensureStatusIs(StatusCode acceptable)
|
||||
throws SFTPException {
|
||||
final StatusCode sc = readStatusCode();
|
||||
|
||||
@@ -19,27 +19,23 @@ import net.schmizz.sshj.xfer.FilePermission;
|
||||
import net.schmizz.sshj.xfer.LocalDestFile;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
public class SFTPClient
|
||||
implements Closeable {
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final Logger log;
|
||||
|
||||
protected final SFTPEngine engine;
|
||||
protected final SFTPFileTransfer xfer;
|
||||
|
||||
public SFTPClient(SFTPEngine engine) {
|
||||
this.engine = engine;
|
||||
log = engine.getLoggerFactory().getLogger(getClass());
|
||||
this.xfer = new SFTPFileTransfer(engine);
|
||||
}
|
||||
|
||||
@@ -89,7 +85,7 @@ public class SFTPClient
|
||||
|
||||
public void mkdirs(String path)
|
||||
throws IOException {
|
||||
final Deque<String> dirsToMake = new LinkedList<>();
|
||||
final Deque<String> dirsToMake = new LinkedList<String>();
|
||||
for (PathComponents current = engine.getPathHelper().getComponents(path); ;
|
||||
current = engine.getPathHelper().getComponents(current.getParent())) {
|
||||
final FileAttributes attrs = statExistence(current.getPath());
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.concurrent.Promise;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session.Subsystem;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -38,13 +40,14 @@ public class SFTPEngine
|
||||
public static final int DEFAULT_TIMEOUT_MS = 30 * 1000; // way too long, but it was the original default
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected final LoggerFactory loggerFactory;
|
||||
protected final Logger log;
|
||||
|
||||
protected volatile int timeoutMs = DEFAULT_TIMEOUT_MS;
|
||||
|
||||
protected final PathHelper pathHelper;
|
||||
|
||||
protected final Subsystem sub;
|
||||
protected final Session.Subsystem sub;
|
||||
protected final PacketReader reader;
|
||||
protected final OutputStream out;
|
||||
|
||||
@@ -59,7 +62,10 @@ public class SFTPEngine
|
||||
|
||||
public SFTPEngine(SessionFactory ssh, String pathSep)
|
||||
throws SSHException {
|
||||
sub = ssh.startSession().startSubsystem("sftp");
|
||||
Session session = ssh.startSession();
|
||||
loggerFactory = session.getLoggerFactory();
|
||||
log = loggerFactory.getLogger(getClass());
|
||||
sub = session.startSubsystem("sftp");
|
||||
out = sub.getOutputStream();
|
||||
reader = new PacketReader(this);
|
||||
pathHelper = new PathHelper(new PathHelper.Canonicalizer() {
|
||||
@@ -94,7 +100,7 @@ public class SFTPEngine
|
||||
return this;
|
||||
}
|
||||
|
||||
public Subsystem getSubsystem() {
|
||||
public Session.Subsystem getSubsystem() {
|
||||
return sub;
|
||||
}
|
||||
|
||||
@@ -133,7 +139,7 @@ public class SFTPEngine
|
||||
public RemoteFile open(String path, Set<OpenMode> modes, FileAttributes fa)
|
||||
throws IOException {
|
||||
final byte[] handle = doRequest(
|
||||
newRequest(PacketType.OPEN).putString(path).putUInt32(OpenMode.toMask(modes)).putFileAttributes(fa)
|
||||
newRequest(PacketType.OPEN).putString(path, sub.getRemoteCharset()).putUInt32(OpenMode.toMask(modes)).putFileAttributes(fa)
|
||||
).ensurePacketTypeIs(PacketType.HANDLE).readBytes();
|
||||
return new RemoteFile(this, path, handle);
|
||||
}
|
||||
@@ -151,7 +157,7 @@ public class SFTPEngine
|
||||
public RemoteDirectory openDir(String path)
|
||||
throws IOException {
|
||||
final byte[] handle = doRequest(
|
||||
newRequest(PacketType.OPENDIR).putString(path)
|
||||
newRequest(PacketType.OPENDIR).putString(path, sub.getRemoteCharset())
|
||||
).ensurePacketTypeIs(PacketType.HANDLE).readBytes();
|
||||
return new RemoteDirectory(this, path, handle);
|
||||
}
|
||||
@@ -159,7 +165,7 @@ public class SFTPEngine
|
||||
public void setAttributes(String path, FileAttributes attrs)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.SETSTAT).putString(path).putFileAttributes(attrs)
|
||||
newRequest(PacketType.SETSTAT).putString(path, sub.getRemoteCharset()).putFileAttributes(attrs)
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
@@ -169,13 +175,13 @@ public class SFTPEngine
|
||||
throw new SFTPException("READLINK is not supported in SFTPv" + operativeVersion);
|
||||
return readSingleName(
|
||||
doRequest(
|
||||
newRequest(PacketType.READLINK).putString(path)
|
||||
));
|
||||
newRequest(PacketType.READLINK).putString(path, sub.getRemoteCharset())
|
||||
), sub.getRemoteCharset());
|
||||
}
|
||||
|
||||
public void makeDir(String path, FileAttributes attrs)
|
||||
throws IOException {
|
||||
doRequest(newRequest(PacketType.MKDIR).putString(path).putFileAttributes(attrs)).ensureStatusPacketIsOK();
|
||||
doRequest(newRequest(PacketType.MKDIR).putString(path, sub.getRemoteCharset()).putFileAttributes(attrs)).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void makeDir(String path)
|
||||
@@ -188,21 +194,21 @@ public class SFTPEngine
|
||||
if (operativeVersion < 3)
|
||||
throw new SFTPException("SYMLINK is not supported in SFTPv" + operativeVersion);
|
||||
doRequest(
|
||||
newRequest(PacketType.SYMLINK).putString(linkpath).putString(targetpath)
|
||||
newRequest(PacketType.SYMLINK).putString(linkpath, sub.getRemoteCharset()).putString(targetpath, sub.getRemoteCharset())
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void remove(String filename)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.REMOVE).putString(filename)
|
||||
newRequest(PacketType.REMOVE).putString(filename, sub.getRemoteCharset())
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void removeDir(String path)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.RMDIR).putString(path)
|
||||
newRequest(PacketType.RMDIR).putString(path, sub.getRemoteCharset())
|
||||
).ensureStatusIs(Response.StatusCode.OK);
|
||||
}
|
||||
|
||||
@@ -221,7 +227,7 @@ public class SFTPEngine
|
||||
if (operativeVersion < 1)
|
||||
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
|
||||
doRequest(
|
||||
newRequest(PacketType.RENAME).putString(oldPath).putString(newPath)
|
||||
newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset())
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
@@ -229,8 +235,8 @@ public class SFTPEngine
|
||||
throws IOException {
|
||||
return readSingleName(
|
||||
doRequest(
|
||||
newRequest(PacketType.REALPATH).putString(path)
|
||||
));
|
||||
newRequest(PacketType.REALPATH).putString(path, sub.getRemoteCharset())
|
||||
), sub.getRemoteCharset());
|
||||
}
|
||||
|
||||
public void setTimeoutMs(int timeoutMs) {
|
||||
@@ -248,22 +254,38 @@ public class SFTPEngine
|
||||
reader.interrupt();
|
||||
}
|
||||
|
||||
protected LoggerFactory getLoggerFactory() {
|
||||
return loggerFactory;
|
||||
}
|
||||
|
||||
protected FileAttributes stat(PacketType pt, String path)
|
||||
throws IOException {
|
||||
return doRequest(newRequest(pt).putString(path))
|
||||
return doRequest(newRequest(pt).putString(path, sub.getRemoteCharset()))
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
protected static String readSingleName(Response res)
|
||||
private static byte[] readSingleNameAsBytes(Response res)
|
||||
throws IOException {
|
||||
res.ensurePacketTypeIs(PacketType.NAME);
|
||||
if (res.readUInt32AsInt() == 1)
|
||||
return res.readString();
|
||||
return res.readStringAsBytes();
|
||||
else
|
||||
throw new SFTPException("Unexpected data in " + res.getType() + " packet");
|
||||
}
|
||||
|
||||
/** Using UTF-8 */
|
||||
protected static String readSingleName(Response res)
|
||||
throws IOException {
|
||||
return readSingleName(res, IOUtils.UTF8);
|
||||
}
|
||||
|
||||
/** Using any character set */
|
||||
protected static String readSingleName(Response res, Charset charset)
|
||||
throws IOException {
|
||||
return new String(readSingleNameAsBytes(res), charset);
|
||||
}
|
||||
|
||||
protected synchronized void transmit(SFTPPacket<Request> payload)
|
||||
throws IOException {
|
||||
final int len = payload.available();
|
||||
|
||||
@@ -17,19 +17,12 @@ package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.sshj.common.StreamCopier;
|
||||
import net.schmizz.sshj.sftp.Response.StatusCode;
|
||||
import net.schmizz.sshj.xfer.AbstractFileTransfer;
|
||||
import net.schmizz.sshj.xfer.FileSystemFile;
|
||||
import net.schmizz.sshj.xfer.FileTransfer;
|
||||
import net.schmizz.sshj.xfer.LocalDestFile;
|
||||
import net.schmizz.sshj.xfer.LocalFileFilter;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
import net.schmizz.sshj.xfer.TransferListener;
|
||||
import net.schmizz.sshj.xfer.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class SFTPFileTransfer
|
||||
extends AbstractFileTransfer
|
||||
@@ -42,6 +35,7 @@ public class SFTPFileTransfer
|
||||
private volatile boolean preserveAttributes = true;
|
||||
|
||||
public SFTPFileTransfer(SFTPEngine engine) {
|
||||
super(engine.getLoggerFactory());
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
@@ -66,14 +60,12 @@ public class SFTPFileTransfer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upload(LocalSourceFile localFile, String remotePath)
|
||||
throws IOException {
|
||||
public void upload(LocalSourceFile localFile, String remotePath) throws IOException {
|
||||
new Uploader(localFile, remotePath).upload(getTransferListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download(String source, LocalDestFile dest)
|
||||
throws IOException {
|
||||
public void download(String source, LocalDestFile dest) throws IOException {
|
||||
final PathComponents pathComponents = engine.getPathHelper().getComponents(source);
|
||||
final FileAttributes attributes = engine.stat(source);
|
||||
new Downloader().download(getTransferListener(), new RemoteResourceInfo(pathComponents, attributes), dest);
|
||||
@@ -97,10 +89,10 @@ public class SFTPFileTransfer
|
||||
|
||||
private class Downloader {
|
||||
|
||||
@SuppressWarnings("PMD.MissingBreakInSwitch")
|
||||
private void download(final TransferListener listener,
|
||||
final RemoteResourceInfo remote,
|
||||
final LocalDestFile local)
|
||||
throws IOException {
|
||||
final LocalDestFile local) throws IOException {
|
||||
final LocalDestFile adjustedFile;
|
||||
switch (remote.getAttributes().getType()) {
|
||||
case DIRECTORY:
|
||||
@@ -110,8 +102,7 @@ public class SFTPFileTransfer
|
||||
log.warn("Server did not supply information about the type of file at `{}` " +
|
||||
"-- assuming it is a regular file!", remote.getPath());
|
||||
case REGULAR:
|
||||
adjustedFile = downloadFile(listener.file(remote.getName(), remote.getAttributes().getSize()),
|
||||
remote, local);
|
||||
adjustedFile = downloadFile(listener.file(remote.getName(), remote.getAttributes().getSize()), remote, local);
|
||||
break;
|
||||
default:
|
||||
throw new IOException(remote + " is not a regular file or directory");
|
||||
@@ -145,7 +136,7 @@ public class SFTPFileTransfer
|
||||
final RemoteFile.ReadAheadRemoteFileInputStream rfis = rf.new ReadAheadRemoteFileInputStream(16);
|
||||
final OutputStream os = adjusted.getOutputStream();
|
||||
try {
|
||||
new StreamCopier(rfis, os)
|
||||
new StreamCopier(rfis, os, engine.getLoggerFactory())
|
||||
.bufSize(engine.getSubsystem().getLocalMaxPacketSize())
|
||||
.keepFlushing(false)
|
||||
.listener(listener)
|
||||
@@ -235,14 +226,36 @@ public class SFTPFileTransfer
|
||||
final String remote)
|
||||
throws IOException {
|
||||
final String adjusted = prepareFile(local, remote);
|
||||
try (RemoteFile rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC))) {
|
||||
try (InputStream fis = local.getInputStream();
|
||||
RemoteFile.RemoteFileOutputStream rfos = rf.new RemoteFileOutputStream(0, 16)) {
|
||||
new StreamCopier(fis, rfos)
|
||||
.bufSize(engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead())
|
||||
.keepFlushing(false)
|
||||
.listener(listener)
|
||||
.copy();
|
||||
RemoteFile rf = null;
|
||||
InputStream fis = null;
|
||||
RemoteFile.RemoteFileOutputStream rfos = null;
|
||||
try {
|
||||
rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC));
|
||||
fis = local.getInputStream();
|
||||
rfos = rf.new RemoteFileOutputStream(0, 16);
|
||||
new StreamCopier(fis, rfos, engine.getLoggerFactory())
|
||||
.bufSize(engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead())
|
||||
.keepFlushing(false)
|
||||
.listener(listener)
|
||||
.copy();
|
||||
} finally {
|
||||
if (rf != null) {
|
||||
try {
|
||||
rf.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (rfos != null) {
|
||||
try {
|
||||
rfos.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return adjusted;
|
||||
|
||||
@@ -15,34 +15,48 @@
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.*;
|
||||
|
||||
/** An abstract class for {@link Signature} that implements common functionality. */
|
||||
/**
|
||||
* An abstract class for {@link Signature} that implements common functionality.
|
||||
*/
|
||||
public abstract class AbstractSignature
|
||||
implements Signature {
|
||||
|
||||
protected final String algorithm;
|
||||
protected java.security.Signature signature;
|
||||
@SuppressWarnings("PMD.UnnecessaryFullyQualifiedName")
|
||||
protected final java.security.Signature signature;
|
||||
|
||||
protected AbstractSignature(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
try {
|
||||
this.signature = SecurityUtils.getSignature(algorithm);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractSignature(@SuppressWarnings("PMD.UnnecessaryFullyQualifiedName")
|
||||
java.security.Signature signatureEngine) {
|
||||
this.signature = signatureEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PublicKey publicKey, PrivateKey privateKey) {
|
||||
public void initVerify(PublicKey publicKey) {
|
||||
try {
|
||||
signature = SecurityUtils.getSignature(algorithm);
|
||||
if (publicKey != null)
|
||||
signature.initVerify(publicKey);
|
||||
if (privateKey != null)
|
||||
signature.initSign(privateKey);
|
||||
} catch (GeneralSecurityException e) {
|
||||
signature.initVerify(publicKey);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSign(PrivateKey privateKey) {
|
||||
try {
|
||||
signature.initSign(privateKey);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
@@ -70,23 +84,24 @@ public abstract class AbstractSignature
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] extractSig(byte[] sig) {
|
||||
if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0) {
|
||||
int i = 0;
|
||||
int j = sig[i++] << 24 & 0xff000000
|
||||
| sig[i++] << 16 & 0x00ff0000
|
||||
| sig[i++] << 8 & 0x0000ff00
|
||||
| sig[i++] & 0x000000ff;
|
||||
i += j;
|
||||
j = sig[i++] << 24 & 0xff000000
|
||||
| sig[i++] << 16 & 0x00ff0000
|
||||
| sig[i++] << 8 & 0x0000ff00
|
||||
| sig[i++] & 0x000000ff;
|
||||
byte[] newSig = new byte[j];
|
||||
System.arraycopy(sig, i, newSig, 0, j);
|
||||
sig = newSig;
|
||||
/**
|
||||
* Check whether the signature is generated using the expected algorithm, and if so, return the signature blob
|
||||
*
|
||||
* @param sig The full signature
|
||||
* @param expectedKeyAlgorithm The expected key algorithm
|
||||
* @return The blob part of the signature
|
||||
*/
|
||||
protected byte[] extractSig(byte[] sig, String expectedKeyAlgorithm) {
|
||||
Buffer.PlainBuffer buffer = new Buffer.PlainBuffer(sig);
|
||||
try {
|
||||
String algo = buffer.readString();
|
||||
if (!expectedKeyAlgorithm.equals(algo)) {
|
||||
throw new SSHRuntimeException("Expected '" + expectedKeyAlgorithm + "' key algorithm, but got: " + algo);
|
||||
}
|
||||
return buffer.readBytes();
|
||||
} catch (Buffer.BufferException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
return sig;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,24 @@ import java.security.PublicKey;
|
||||
public interface Signature {
|
||||
|
||||
/**
|
||||
* Initialize this signature with the given public key and private key. If the private key is null, only signature
|
||||
* verification can be performed.
|
||||
* Initialize this signature with the given public key for signature verification.
|
||||
*
|
||||
* @param pubkey (null-ok) specify in case verification is needed
|
||||
* @param prvkey (null-ok) specify in case signing is needed
|
||||
* Note that subsequent calls to either {@link #initVerify(PublicKey)} or {@link #initSign(PrivateKey)} will
|
||||
* overwrite prior initialization.
|
||||
*
|
||||
* @param pubkey the public key to use for signature verification
|
||||
*/
|
||||
void init(PublicKey pubkey, PrivateKey prvkey);
|
||||
void initVerify(PublicKey pubkey);
|
||||
|
||||
/**
|
||||
* Initialize this signature with the given private key for signing.
|
||||
*
|
||||
* Note that subsequent calls to either {@link #initVerify(PublicKey)} or {@link #initSign(PrivateKey)} will
|
||||
* overwrite prior initialization.
|
||||
*
|
||||
* @param prvkey the private key to use for signing
|
||||
*/
|
||||
void initSign(PrivateKey prvkey);
|
||||
|
||||
/**
|
||||
* Convenience method, same as calling {@link #update(byte[], int, int)} with offset as {@code 0} and {@code
|
||||
|
||||
@@ -15,16 +15,25 @@
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import java.security.SignatureException;
|
||||
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import org.bouncycastle.asn1.*;
|
||||
|
||||
/** DSA {@link Signature} */
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* DSA {@link Signature}
|
||||
*/
|
||||
public class SignatureDSA
|
||||
extends AbstractSignature {
|
||||
|
||||
/** A named factory for DSA signature */
|
||||
/**
|
||||
* A named factory for DSA signature
|
||||
*/
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@@ -74,33 +83,33 @@ public class SignatureDSA
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
sig = extractSig(sig);
|
||||
|
||||
// ASN.1
|
||||
int frst = (sig[0] & 0x80) != 0 ? 1 : 0;
|
||||
int scnd = (sig[20] & 0x80) != 0 ? 1 : 0;
|
||||
|
||||
int length = sig.length + 6 + frst + scnd;
|
||||
byte[] tmp = new byte[length];
|
||||
tmp[0] = (byte) 0x30;
|
||||
tmp[1] = (byte) 0x2c;
|
||||
tmp[1] += frst;
|
||||
tmp[1] += scnd;
|
||||
tmp[2] = (byte) 0x02;
|
||||
tmp[3] = (byte) 0x14;
|
||||
tmp[3] += frst;
|
||||
System.arraycopy(sig, 0, tmp, 4 + frst, 20);
|
||||
tmp[4 + tmp[3]] = (byte) 0x02;
|
||||
tmp[5 + tmp[3]] = (byte) 0x14;
|
||||
tmp[5 + tmp[3]] += scnd;
|
||||
System.arraycopy(sig, 20, tmp, 6 + tmp[3] + scnd, 20);
|
||||
sig = tmp;
|
||||
|
||||
try {
|
||||
return signature.verify(sig);
|
||||
byte[] sigBlob = extractSig(sig, "ssh-dss");
|
||||
return signature.verify(asnEncode(sigBlob));
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the signature as a DER sequence (ASN.1 format).
|
||||
*/
|
||||
private byte[] asnEncode(byte[] sigBlob) throws IOException {
|
||||
byte[] r = new BigInteger(1, Arrays.copyOfRange(sigBlob, 0, 20)).toByteArray();
|
||||
byte[] s = new BigInteger(1, Arrays.copyOfRange(sigBlob, 20, 40)).toByteArray();
|
||||
|
||||
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||
vector.add(new ASN1Integer(r));
|
||||
vector.add(new ASN1Integer(s));
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ASN1OutputStream asnOS = new ASN1OutputStream(baos);
|
||||
|
||||
asnOS.writeObject(new DERSequence(vector));
|
||||
asnOS.flush();
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,35 +15,72 @@
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SignatureException;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1OutputStream;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SignatureException;
|
||||
|
||||
/** ECDSA {@link Signature} */
|
||||
public class SignatureECDSA
|
||||
extends AbstractSignature {
|
||||
public class SignatureECDSA extends AbstractSignature {
|
||||
|
||||
/** A named factory for ECDSA signature */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
/** A named factory for ECDSA-256 signature */
|
||||
public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureECDSA();
|
||||
return new SignatureECDSA("SHA256withECDSA", KeyType.ECDSA256.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.ECDSA.toString();
|
||||
return KeyType.ECDSA256.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SignatureECDSA() {
|
||||
super("SHA256withECDSA");
|
||||
/** A named factory for ECDSA-384 signature */
|
||||
public static class Factory384 implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureECDSA("SHA384withECDSA", KeyType.ECDSA384.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.ECDSA384.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A named factory for ECDSA-521 signature */
|
||||
public static class Factory521 implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureECDSA("SHA512withECDSA", KeyType.ECDSA521.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.ECDSA521.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String keyTypeName;
|
||||
|
||||
public SignatureECDSA(String algorithm, String keyTypeName) {
|
||||
super(algorithm);
|
||||
this.keyTypeName = keyTypeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +98,7 @@ public class SignatureECDSA
|
||||
System.arraycopy(sig, 4, r, 0, rLen);
|
||||
System.arraycopy(sig, 6 + rLen, s, 0, sLen);
|
||||
|
||||
Buffer buf = new Buffer.PlainBuffer();
|
||||
Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
|
||||
buf.putMPInt(new BigInteger(r));
|
||||
buf.putMPInt(new BigInteger(s));
|
||||
|
||||
@@ -70,68 +107,34 @@ public class SignatureECDSA
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
byte[] r;
|
||||
byte[] s;
|
||||
try {
|
||||
Buffer sigbuf = new Buffer.PlainBuffer(sig);
|
||||
final String algo = new String(sigbuf.readBytes());
|
||||
if (!"ecdsa-sha2-nistp256".equals(algo)) {
|
||||
throw new SSHRuntimeException(String.format("Signature :: ecdsa-sha2-nistp256 expected, got %s", algo));
|
||||
}
|
||||
final int rsLen = sigbuf.readUInt32AsInt();
|
||||
if (!(sigbuf.available() == rsLen)) {
|
||||
throw new SSHRuntimeException("Invalid key length");
|
||||
}
|
||||
r = sigbuf.readBytes();
|
||||
s = sigbuf.readBytes();
|
||||
} catch (Exception e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
|
||||
int rLen = r.length;
|
||||
int sLen = s.length;
|
||||
|
||||
/* We can't have the high bit set, so add an extra zero at the beginning if so. */
|
||||
if ((r[0] & 0x80) != 0) {
|
||||
rLen++;
|
||||
}
|
||||
if ((s[0] & 0x80) != 0) {
|
||||
sLen++;
|
||||
}
|
||||
|
||||
/* Calculate total output length */
|
||||
int length = 6 + rLen + sLen;
|
||||
byte[] asn1 = new byte[length];
|
||||
|
||||
/* ASN.1 SEQUENCE tag */
|
||||
asn1[0] = (byte) 0x30;
|
||||
|
||||
/* Size of SEQUENCE */
|
||||
asn1[1] = (byte) (4 + rLen + sLen);
|
||||
|
||||
/* ASN.1 INTEGER tag */
|
||||
asn1[2] = (byte) 0x02;
|
||||
|
||||
/* "r" INTEGER length */
|
||||
asn1[3] = (byte) rLen;
|
||||
|
||||
/* Copy in the "r" INTEGER */
|
||||
System.arraycopy(r, 0, asn1, 4, rLen);
|
||||
|
||||
/* ASN.1 INTEGER tag */
|
||||
asn1[rLen + 4] = (byte) 0x02;
|
||||
|
||||
/* "s" INTEGER length */
|
||||
asn1[rLen + 5] = (byte) sLen;
|
||||
|
||||
/* Copy in the "s" INTEGER */
|
||||
System.arraycopy(s, 0, asn1, (6 + rLen), sLen);
|
||||
|
||||
|
||||
try {
|
||||
return signature.verify(asn1);
|
||||
byte[] sigBlob = extractSig(sig, keyTypeName);
|
||||
return signature.verify(asnEncode(sigBlob));
|
||||
} catch (SignatureException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the signature as a DER sequence (ASN.1 format).
|
||||
*/
|
||||
private byte[] asnEncode(byte[] sigBlob) throws IOException {
|
||||
Buffer.PlainBuffer sigbuf = new Buffer.PlainBuffer(sigBlob);
|
||||
byte[] r = sigbuf.readBytes();
|
||||
byte[] s = sigbuf.readBytes();
|
||||
|
||||
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||
vector.add(new ASN1Integer(r));
|
||||
vector.add(new ASN1Integer(s));
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ASN1OutputStream asnOS = new ASN1OutputStream(baos);
|
||||
|
||||
asnOS.writeObject(new DERSequence(vector));
|
||||
asnOS.flush();
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package net.schmizz.sshj.signature;
|
||||
|
||||
import java.security.SignatureException;
|
||||
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
|
||||
import java.security.SignatureException;
|
||||
|
||||
/** RSA {@link Signature} */
|
||||
public class SignatureRSA
|
||||
extends AbstractSignature {
|
||||
@@ -51,7 +51,7 @@ public class SignatureRSA
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
sig = extractSig(sig);
|
||||
sig = extractSig(sig, "ssh-rsa");
|
||||
try {
|
||||
return signature.verify(sig);
|
||||
} catch (SignatureException e) {
|
||||
|
||||
@@ -15,17 +15,11 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.SSHPacketHandler;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.compression.Compression;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Decodes packets from the SSH binary protocol per the current algorithms. */
|
||||
final class Decoder
|
||||
@@ -33,7 +27,7 @@ final class Decoder
|
||||
|
||||
private static final int MAX_PACKET_LEN = 256 * 1024;
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
private final Logger log;
|
||||
|
||||
/** What we pass decoded packets to */
|
||||
private final SSHPacketHandler packetHandler;
|
||||
@@ -53,8 +47,9 @@ final class Decoder
|
||||
*/
|
||||
private int needed = 8;
|
||||
|
||||
Decoder(SSHPacketHandler packetHandler) {
|
||||
Decoder(Transport packetHandler) {
|
||||
this.packetHandler = packetHandler;
|
||||
log = packetHandler.getConfig().getLoggerFactory().getLogger(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,4 +188,4 @@ final class Decoder
|
||||
return MAX_PACKET_LEN;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport;
|
||||
|
||||
import net.schmizz.sshj.common.LoggerFactory;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.compression.Compression;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
import net.schmizz.sshj.transport.random.Random;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
@@ -29,28 +29,14 @@ import java.util.concurrent.locks.Lock;
|
||||
final class Encoder
|
||||
extends Converter {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Logger log;
|
||||
private final Random prng;
|
||||
|
||||
private final Lock encodeLock;
|
||||
|
||||
Encoder(Random prng, Lock encodeLock) {
|
||||
Encoder(Random prng, Lock encodeLock, LoggerFactory loggerFactory) {
|
||||
this.prng = prng;
|
||||
this.encodeLock = encodeLock;
|
||||
}
|
||||
|
||||
private SSHPacket checkHeaderSpace(SSHPacket buffer) {
|
||||
if (buffer.rpos() < 5) {
|
||||
log.warn("Performance cost: when sending a packet, ensure that "
|
||||
+ "5 bytes are available in front of the buffer");
|
||||
SSHPacket nb = new SSHPacket(buffer.available() + 5);
|
||||
nb.rpos(5);
|
||||
nb.wpos(5);
|
||||
nb.putBuffer(buffer);
|
||||
buffer = nb;
|
||||
}
|
||||
return buffer;
|
||||
log = loggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
private void compress(SSHPacket buffer) {
|
||||
@@ -142,4 +128,4 @@ final class Encoder
|
||||
return Compression.Mode.DEFLATE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,26 +17,15 @@ package net.schmizz.sshj.transport;
|
||||
|
||||
import net.schmizz.concurrent.ErrorDeliveryUtil;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.ErrorNotifiable;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.SSHPacketHandler;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.transport.cipher.Cipher;
|
||||
import net.schmizz.sshj.transport.compression.Compression;
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
import net.schmizz.sshj.transport.kex.KeyExchange;
|
||||
import net.schmizz.sshj.transport.mac.MAC;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -60,8 +49,7 @@ final class KeyExchanger
|
||||
NEWKEYS,
|
||||
}
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Logger log;
|
||||
private final TransportImpl transport;
|
||||
|
||||
/**
|
||||
@@ -86,18 +74,20 @@ final class KeyExchanger
|
||||
private Proposal clientProposal;
|
||||
private NegotiatedAlgorithms negotiatedAlgs;
|
||||
|
||||
private final Event<TransportException> kexInitSent =
|
||||
new Event<TransportException>("kexinit sent", TransportException.chainer);
|
||||
private final Event<TransportException> kexInitSent;
|
||||
|
||||
private final Event<TransportException> done;
|
||||
|
||||
KeyExchanger(TransportImpl trans) {
|
||||
this.transport = trans;
|
||||
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
|
||||
kexInitSent = new Event<TransportException>("kexinit sent", TransportException.chainer, trans.getConfig().getLoggerFactory());
|
||||
|
||||
/*
|
||||
* Use TransportImpl's writeLock, since TransportImpl.write() may wait on this event and the lock should
|
||||
* be released while waiting.
|
||||
*/
|
||||
this.done = new Event<TransportException>("kex done", TransportException.chainer, trans.getWriteLock());
|
||||
this.done = new Event<TransportException>("kex done", TransportException.chainer, trans.getWriteLock(), trans.getConfig().getLoggerFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,6 +157,7 @@ final class KeyExchanger
|
||||
"Key exchange packet received when key exchange was not ongoing");
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
private static void ensureReceivedMatchesExpected(Message got, Message expected)
|
||||
throws TransportException {
|
||||
if (got != expected)
|
||||
@@ -206,6 +197,12 @@ final class KeyExchanger
|
||||
if (hkv.verify(transport.getRemoteHost(), transport.getRemotePort(), key))
|
||||
return;
|
||||
}
|
||||
log.error("Disconnecting because none of the configured Host key verifiers ({}) could verify '{}' host key with fingerprint {} for {}:{}",
|
||||
hostVerifiers,
|
||||
KeyType.fromKey(key),
|
||||
SecurityUtils.getFingerprint(key),
|
||||
transport.getRemoteHost(),
|
||||
transport.getRemotePort());
|
||||
|
||||
throw new TransportException(DisconnectReason.HOST_KEY_NOT_VERIFIABLE,
|
||||
"Could not verify `" + KeyType.fromKey(key)
|
||||
@@ -395,4 +392,4 @@ final class KeyExchanger
|
||||
ErrorDeliveryUtil.alertEvents(error, kexInitSent, done);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package net.schmizz.sshj.transport;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -24,12 +23,12 @@ import java.net.SocketTimeoutException;
|
||||
public final class Reader
|
||||
extends Thread {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Logger log;
|
||||
private final TransportImpl trans;
|
||||
|
||||
public Reader(TransportImpl trans) {
|
||||
this.trans = trans;
|
||||
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
|
||||
setName("reader");
|
||||
}
|
||||
|
||||
|
||||
@@ -235,4 +235,4 @@ public interface Transport
|
||||
* @param e The exception that occurred.
|
||||
*/
|
||||
void die(Exception e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -34,9 +33,11 @@ import java.io.OutputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/** A thread-safe {@link Transport} implementation. */
|
||||
/**
|
||||
* A thread-safe {@link Transport} implementation.
|
||||
*/
|
||||
public final class TransportImpl
|
||||
implements Transport {
|
||||
implements Transport, DisconnectListener {
|
||||
|
||||
private static final class NullService
|
||||
extends AbstractService {
|
||||
@@ -46,14 +47,15 @@ public final class TransportImpl
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class ConnInfo {
|
||||
|
||||
final String host;
|
||||
|
||||
final int port;
|
||||
final InputStream in;
|
||||
final OutputStream out;
|
||||
public ConnInfo(String host, int port, InputStream in, OutputStream out) {
|
||||
|
||||
ConnInfo(String host, int port, InputStream in, OutputStream out) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.in = in;
|
||||
@@ -61,16 +63,12 @@ public final class TransportImpl
|
||||
}
|
||||
|
||||
}
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Service nullService = new NullService(this);
|
||||
private final LoggerFactory loggerFactory;
|
||||
|
||||
private final DisconnectListener nullDisconnectListener = new DisconnectListener() {
|
||||
@Override
|
||||
public void notifyDisconnect(DisconnectReason reason, String message) {
|
||||
log.info("Disconnected - {}", reason);
|
||||
}
|
||||
};
|
||||
private final Logger log;
|
||||
|
||||
private final Service nullService;
|
||||
|
||||
private final Config config;
|
||||
|
||||
@@ -88,36 +86,51 @@ public final class TransportImpl
|
||||
|
||||
private final Decoder decoder;
|
||||
|
||||
private final Event<TransportException> serviceAccept = new Event<TransportException>("service accept", TransportException.chainer);
|
||||
private final Event<TransportException> serviceAccept;
|
||||
|
||||
private final Event<TransportException> close = new Event<TransportException>("transport close", TransportException.chainer);
|
||||
private final Event<TransportException> close;
|
||||
|
||||
/** Client version identification string */
|
||||
/**
|
||||
* Client version identification string
|
||||
*/
|
||||
private final String clientID;
|
||||
|
||||
private volatile int timeoutMs = 30 * 1000; // Crazy long, but it was the original default
|
||||
|
||||
private volatile boolean authed = false;
|
||||
|
||||
/** Currently active service e.g. UserAuthService, ConnectionService */
|
||||
private volatile Service service = nullService;
|
||||
/**
|
||||
* Currently active service e.g. UserAuthService, ConnectionService
|
||||
*/
|
||||
private volatile Service service;
|
||||
|
||||
private DisconnectListener disconnectListener = nullDisconnectListener;
|
||||
private DisconnectListener disconnectListener;
|
||||
|
||||
private ConnInfo connInfo;
|
||||
|
||||
/** Server version identification string */
|
||||
/**
|
||||
* Server version identification string
|
||||
*/
|
||||
private String serverID;
|
||||
|
||||
/** Message identifier of last packet received */
|
||||
/**
|
||||
* Message identifier of last packet received
|
||||
*/
|
||||
private Message msg;
|
||||
|
||||
private final ReentrantLock writeLock = new ReentrantLock();
|
||||
|
||||
public TransportImpl(Config config) {
|
||||
this.config = config;
|
||||
this.loggerFactory = config.getLoggerFactory();
|
||||
this.serviceAccept = new Event<TransportException>("service accept", TransportException.chainer, loggerFactory);
|
||||
this.close = new Event<TransportException>("transport close", TransportException.chainer, loggerFactory);
|
||||
this.nullService = new NullService(this);
|
||||
this.service = nullService;
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
this.disconnectListener = this;
|
||||
this.reader = new Reader(this);
|
||||
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock);
|
||||
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock, loggerFactory);
|
||||
this.decoder = new Decoder(this);
|
||||
this.kexer = new KeyExchanger(this);
|
||||
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
|
||||
@@ -131,16 +144,21 @@ public final class TransportImpl
|
||||
@Deprecated
|
||||
public TransportImpl(Config config, SSHClient sshClient) {
|
||||
this.config = config;
|
||||
this.loggerFactory = config.getLoggerFactory();
|
||||
this.serviceAccept = new Event<TransportException>("service accept", TransportException.chainer, loggerFactory);
|
||||
this.close = new Event<TransportException>("transport close", TransportException.chainer, loggerFactory);
|
||||
this.log = loggerFactory.getLogger(getClass());
|
||||
this.nullService = new NullService(this);
|
||||
this.service = nullService;
|
||||
this.disconnectListener = this;
|
||||
this.reader = new Reader(this);
|
||||
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock);
|
||||
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock, loggerFactory);
|
||||
this.decoder = new Decoder(this);
|
||||
this.kexer = new KeyExchanger(this);
|
||||
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
|
||||
this.sshClient = sshClient;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void init(String remoteHost, int remotePort, InputStream in, OutputStream out)
|
||||
throws TransportException {
|
||||
@@ -166,18 +184,30 @@ public final class TransportImpl
|
||||
reader.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* TransportImpl implements its own default DisconnectListener.
|
||||
*/
|
||||
@Override
|
||||
public void notifyDisconnect(DisconnectReason reason, String message) {
|
||||
log.info("Disconnected - {}", reason);
|
||||
}
|
||||
|
||||
private void receiveServerIdent() throws IOException {
|
||||
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
|
||||
while ((serverID = readIdentification(buf)).isEmpty()) {
|
||||
int b = connInfo.in.read();
|
||||
if (b == -1)
|
||||
if (b == -1) {
|
||||
log.error("Received end of connection, but no identification received. ");
|
||||
throw new TransportException("Server closed connection during identification exchange");
|
||||
|
||||
}
|
||||
buf.putByte((byte) b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive the server identification string.
|
||||
*
|
||||
* @throws IOException If there was an error writing to the outputstream.
|
||||
*/
|
||||
private void sendClientIdent() throws IOException {
|
||||
@@ -196,58 +226,19 @@ public final class TransportImpl
|
||||
* This is not efficient but is only done once.
|
||||
*
|
||||
* @param buffer The buffer to read from.
|
||||
*
|
||||
* @return empty string if full ident string has not yet been received
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private String readIdentification(Buffer.PlainBuffer buffer)
|
||||
throws IOException {
|
||||
String ident = new IdentificationStringParser(buffer).parseIdentificationString();
|
||||
String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();
|
||||
if (ident.isEmpty()) {
|
||||
return ident;
|
||||
}
|
||||
//
|
||||
// byte[] data = new byte[256];
|
||||
// for (; ; ) {
|
||||
// int savedBufPos = buffer.rpos();
|
||||
// int pos = 0;
|
||||
// boolean needLF = false;
|
||||
// for (; ; ) {
|
||||
// if (buffer.available() == 0) {
|
||||
// // Need more data, so undo reading and return null
|
||||
// buffer.rpos(savedBufPos);
|
||||
// return "";
|
||||
// }
|
||||
// byte b = buffer.readByte();
|
||||
// if (b == '\r') {
|
||||
// needLF = true;
|
||||
// continue;
|
||||
// }
|
||||
// if (b == '\n')
|
||||
// break;
|
||||
// if (needLF) {
|
||||
// log.error("Incorrect identification, was expecting a '\n' after the '\r', got: '{}' (hex: {})", b, Integer.toHexString(b & 0xFF));
|
||||
// log.error("Data received up til here was: {}", new String(data, 0, pos));
|
||||
// throw new TransportException("Incorrect identification: bad line ending: " + ByteArrayUtils.toHex(data, 0, pos));
|
||||
// }
|
||||
// if (pos >= data.length) {
|
||||
// log.error("Incorrect identification String received, line was longer than expected: {}", new String(data, 0, pos));
|
||||
// log.error("Just for good measure, bytes were: {}", ByteArrayUtils.printHex(data, 0, pos));
|
||||
// throw new TransportException("Incorrect identification: line too long: " + ByteArrayUtils.printHex(data, 0, pos));
|
||||
// }
|
||||
// data[pos++] = b;
|
||||
// }
|
||||
// ident = new String(data, 0, pos);
|
||||
// if (ident.startsWith("SSH-"))
|
||||
// break;
|
||||
// if (buffer.rpos() > 16 * 1024)
|
||||
// throw new TransportException("Incorrect identification: too many header lines");
|
||||
// }
|
||||
|
||||
if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
|
||||
throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
|
||||
"Server does not support SSHv2, identified as: " + ident);
|
||||
"Server does not support SSHv2, identified as: " + ident);
|
||||
|
||||
return ident;
|
||||
}
|
||||
@@ -358,7 +349,6 @@ public final class TransportImpl
|
||||
* Sends a service request for the specified service
|
||||
*
|
||||
* @param serviceName name of the service being requested
|
||||
*
|
||||
* @throws TransportException if there is an error while sending the request
|
||||
*/
|
||||
private void sendServiceRequest(String serviceName)
|
||||
@@ -432,7 +422,7 @@ public final class TransportImpl
|
||||
|
||||
@Override
|
||||
public void setDisconnectListener(DisconnectListener listener) {
|
||||
this.disconnectListener = listener == null ? nullDisconnectListener : listener;
|
||||
this.disconnectListener = listener == null ? this : listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -477,9 +467,9 @@ public final class TransportImpl
|
||||
log.debug("Sending SSH_MSG_DISCONNECT: reason=[{}], msg=[{}]", reason, message);
|
||||
try {
|
||||
write(new SSHPacket(Message.DISCONNECT)
|
||||
.putUInt32(reason.toInt())
|
||||
.putString(message)
|
||||
.putString(""));
|
||||
.putUInt32(reason.toInt())
|
||||
.putString(message)
|
||||
.putString(""));
|
||||
} catch (IOException worthless) {
|
||||
log.debug("Error writing packet: {}", worthless.toString());
|
||||
}
|
||||
@@ -497,7 +487,6 @@ public final class TransportImpl
|
||||
*
|
||||
* @param msg the message identifer
|
||||
* @param buf buffer containg rest of the packet
|
||||
*
|
||||
* @throws SSHException if an error occurs during handling (unrecoverable)
|
||||
*/
|
||||
@Override
|
||||
@@ -515,32 +504,27 @@ public final class TransportImpl
|
||||
|
||||
else
|
||||
switch (msg) {
|
||||
case DISCONNECT: {
|
||||
case DISCONNECT:
|
||||
gotDisconnect(buf);
|
||||
break;
|
||||
}
|
||||
case IGNORE: {
|
||||
case IGNORE:
|
||||
log.debug("Received SSH_MSG_IGNORE");
|
||||
break;
|
||||
}
|
||||
case UNIMPLEMENTED: {
|
||||
case UNIMPLEMENTED:
|
||||
gotUnimplemented(buf);
|
||||
break;
|
||||
}
|
||||
case DEBUG: {
|
||||
case DEBUG:
|
||||
gotDebug(buf);
|
||||
break;
|
||||
}
|
||||
case SERVICE_ACCEPT: {
|
||||
case SERVICE_ACCEPT:
|
||||
gotServiceAccept();
|
||||
break;
|
||||
}
|
||||
case USERAUTH_BANNER: {
|
||||
case USERAUTH_BANNER:
|
||||
log.debug("Received USERAUTH_BANNER");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
sendUnimplemented();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +557,7 @@ public final class TransportImpl
|
||||
try {
|
||||
if (!serviceAccept.hasWaiters())
|
||||
throw new TransportException(DisconnectReason.PROTOCOL_ERROR,
|
||||
"Got a service accept notification when none was awaited");
|
||||
"Got a service accept notification when none was awaited");
|
||||
serviceAccept.set();
|
||||
} finally {
|
||||
serviceAccept.unlock();
|
||||
@@ -584,7 +568,6 @@ public final class TransportImpl
|
||||
* Got an SSH_MSG_UNIMPLEMENTED, so lets see where we're at and act accordingly.
|
||||
*
|
||||
* @param packet The 'unimplemented' packet received
|
||||
*
|
||||
* @throws TransportException
|
||||
*/
|
||||
private void gotUnimplemented(SSHPacket packet)
|
||||
@@ -607,7 +590,7 @@ public final class TransportImpl
|
||||
try {
|
||||
if (!close.isSet()) {
|
||||
|
||||
log.error("Dying because - {}", ex);
|
||||
log.error("Dying because - {}", ex.getMessage(), ex);
|
||||
|
||||
final SSHException causeOfDeath = SSHException.chainer.chain(ex);
|
||||
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code aes128-cbc} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code aes128-cbc} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#AES128CBC()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class AES128CBC
|
||||
extends BlockCipher {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class AES128CBC
|
||||
public String getName() {
|
||||
return "aes128-cbc";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public AES128CBC() {
|
||||
|
||||
@@ -15,11 +15,18 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code aes128-ctr} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code aes128-ctr} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#AES128CTR()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class AES128CTR
|
||||
extends BlockCipher {
|
||||
|
||||
/** Named factory for AES128CBC Cipher */
|
||||
/** Named factory for AES128CTR Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class AES128CTR
|
||||
public String getName() {
|
||||
return "aes128-ctr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public AES128CTR() {
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code aes192-cbc} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code aes192-cbc} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#AES192CBC()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class AES192CBC
|
||||
extends BlockCipher {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class AES192CBC
|
||||
public String getName() {
|
||||
return "aes192-cbc";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public AES192CBC() {
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code aes192-ctr} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code aes192-ctr} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#AES192CTR()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class AES192CTR
|
||||
extends BlockCipher {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class AES192CTR
|
||||
public String getName() {
|
||||
return "aes192-ctr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public AES192CTR() {
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code aes256-ctr} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code aes256-cbc} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#AES256CBC()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class AES256CBC
|
||||
extends BlockCipher {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class AES256CBC
|
||||
public String getName() {
|
||||
return "aes256-cbc";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public AES256CBC() {
|
||||
|
||||
@@ -15,11 +15,18 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code aes256-ctr} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code aes256-ctr} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#AES256CTR()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class AES256CTR
|
||||
extends BlockCipher {
|
||||
|
||||
/** Named factory for AES256CBC Cipher */
|
||||
/** Named factory for AES256CTR Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class AES256CTR
|
||||
public String getName() {
|
||||
return "aes256-ctr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public AES256CTR() {
|
||||
|
||||
@@ -19,7 +19,6 @@ import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.cipher;
|
||||
|
||||
/** {@code blowfish-ctr} cipher */
|
||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
||||
|
||||
/**
|
||||
* {@code blowfish-bcb} cipher
|
||||
*
|
||||
* @deprecated Use {@link BlockCiphers#BlowfishCBC()}
|
||||
*/
|
||||
@Deprecated
|
||||
public class BlowfishCBC
|
||||
extends BlockCipher {
|
||||
|
||||
@@ -32,6 +39,11 @@ public class BlowfishCBC
|
||||
public String getName() {
|
||||
return "blowfish-cbc";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public BlowfishCBC() {
|
||||
|
||||
@@ -46,10 +46,12 @@ public class NoneCipher
|
||||
|
||||
@Override
|
||||
public void init(Mode mode, byte[] bytes, byte[] bytes1) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] input, int inputOffset, int inputLen) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user