Compare commits

..

55 Commits

Author SHA1 Message Date
Jeroen van Erp
2c3333d61c Add ProxySocketFactory 2023-02-21 09:42:22 +01:00
exceptionfactory
0d16fbe146 Replaced Curve25519 class with X25519 Key Agreement (#838)
Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2023-02-11 09:56:20 +01:00
Aaron Meriwether
154a202384 Remove jzlib dependency (#772)
* Remove jzlib dependency

* Document the Java 7 prerequisite
2023-02-11 09:17:00 +01:00
Jeroen van Erp
830a39dc24 Prepare release notes for 0.35.0 2023-01-30 14:27:03 +01:00
Jan S
dcfa1833d7 TimeoutException message improved (#835) 2023-01-06 10:05:33 +01:00
kegelh
6e7fb96d07 Support SSHClient.authPassword on FreeBSD (#815)
* Support SSHClient.authPassword on FreeBSD

FreeBSD "keyboard-interactive" prompt is "Password for user@host:"

* Add test for PasswordResponseProvider
2022-09-19 13:16:56 +02:00
kegelh
d5d6096d5d Fix #805: Prevent CHANNEL_CLOSE to be sent between Channel.isOpen and… (#813)
* Fix #805: Prevent CHANNEL_CLOSE to be sent between Channel.isOpen and a Transport.write call

Otherwise, a disconnect with a "packet referred to nonexistent channel" message can occur.

This particularly happens when the transport.Reader thread passes an eof from the server to the ChannelInputStream, the reading library-user thread returns, and closes the channel at the same time as the transport.Reader thread receives the subsequent CHANNEL_CLOSE from the server.

* Add integration test for #805
2022-09-17 07:11:11 +02:00
exceptionfactory
2551f8e559 Add Transport.isKeyExchangeRequired() to avoid unnecessary KEXINIT (#811)
* Added Transport.isKeyExchangeRequired() to avoid unnecessary KEXINIT

- Updated SSHClient.onConnect() to check isKeyExchangeRequired() before calling doKex()
- Added started timestamp in ThreadNameProvider for improved tracking

* Moved KeepAliveThread State check after authentication to avoid test timing issues
2022-09-16 15:04:26 +02:00
kegelh
430cbfcf13 Make all tests runnable on Windows (#814) 2022-09-16 12:25:28 +02:00
Jeroen van Erp
ec467a3875 Prepare release notes for 0.34.0 2022-08-10 10:23:18 +02:00
Geoffrey Thomas
1b258f0677 AuthGssApiWithMic: Use default client creds instead of remote username (#743)
Previously, AuthGssApiWithMic used params.getUsername() to create the
local client credential object. However, at least when using the native
GSS libraries (sun.security.jgss.native=true), the username would need 
to be something like "user@EXAMPLE.COM", not "user", or the library is 
unable to find credentials. Also, your remote username might not be your
local username.

Instead, and more simply, call the GSSManager#createCredential variant
that just uses default credentials, which should handle both of these 
cases.

Tested on Windows using SSPI. I haven't tested this patch on Linux but I
have confirmed that this form of call to createCredential works as I 
expect when using the native GSS/Kerberos library there too.

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2022-08-08 14:16:18 +02:00
Alex Heneveld
559384ac91 restore the interrupt flag whenever we catch InterruptedException (#801)
Co-authored-by: Alex Heneveld <alex@cloudsoft.io>
2022-08-08 14:09:18 +02:00
exceptionfactory
5674072666 Replace PKCS5 Key File Class with PKCS8 (#793)
* Replaced PKCS5 parsing with PKCS8

- Moved tests for PEM-encoded PKCS1 files to PKCS8
- Removed PKCS5 Key File implementation

* Added PKCS8 test to retry password after initial failure

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2022-07-14 11:36:42 +02:00
exceptionfactory
f33bfecbf5 Upgraded SLF4J to 1.7.36 and Logback to 1.2.11 (#792) 2022-07-13 08:06:03 +02:00
exceptionfactory
c0f6000ff5 Updated KeepAlive and RemotePF examples (#791)
- Set KeepAlive interval before connecting
2022-07-12 17:01:35 +02:00
Brent Tyler
3de0302c84 Added SFTP file transfer resume support on both PUT and GET. (#775)
* Added SFTP file transfer resume support on both PUT and GET. Internally SFTPFileTransfer has a few sanity checks to fall back to full replacement even if the resume flag is set. 

SCP file transfers have not been changed to support this at this time.

* Added JUnit tests for issue-700

* Throw SCPException when attempting to resume SCP transfers.

* Licensing

* Small bug resuming a completed file was restarting since the bytes were equal.

* Enhanced test cases to validate the expected bytes transferred for each scenario are the actual bytes transferred.

* Removed author info which was pre-filled from company IDE template

* Added "fall through" comment for switch

* Changed the API for requesting a resume from a boolean flag with some internal decisions to be a user-specified long byte offset. This is cleaner but puts the onus on the caller to know exactly what they're asking for in their circumstance, which is ultimately better for a library like sshj.

* Reverted some now-unnecessary changes to SFTPFileTransfer.Uploader.prepareFile()

* Fix gradle exclude path for test files

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2022-05-27 13:05:41 +02:00
Jeroen van Erp
d7e402c557 Prepare release notes for 0.33.0 2022-04-20 12:54:04 +02:00
Vladimir Lagunov
8ef996b406 Fix #777: Don't request excessive read-ahead packets in RemoteFile (#778)
Due to a bug in logic introduced by #769, RemoteFile.ReadAheadRemoteFileInputStream started to send new read ahead requests for file parts that had already been requested.

Every call to read() asked the server to send parts of the file from the point which is already downloaded. Instead, it should have asked to send parts after the last requested part. This commit adds exactly this logic.

The bug didn't cause content corruption. It only affected performance, both on servers and on clients.
2022-04-07 13:10:02 +02:00
Vladimir Lagunov
e9cb90901c Throw IOE instead of NPE if OpenSSHKeyV1KeyFile reads an empty file (#773)
There is a contract that FileKeyProvider.readKey throws an IOException if something goes wrong. Throwing an NPE is not expected by API users. Also, it is much more difficult to find out if the NPE is thrown due to a broken key file, or due to an internal bug.
2022-04-01 09:41:48 +02:00
Raymond Lai
69812e9a81 Add support for JuiceSSH generated ed25519 keys (#770)
Reported from https://github.com/TeamAmaze/AmazeFileManager/issues/2976, it was found the key uses aes-128-cbc which is currently not supported by sshj. This change adds support for it.

To enable support for this, also eliminated hardcoding byte array size for key and IV, as a result of BCrypt.pbkdf().
2022-03-07 10:13:15 +01:00
Vladimir Lagunov
9a939d029b Fix ReadAheadRemoteFileInputStream not reading the whole file if a buffer is too big (#769)
If an instance of ReadAheadRemoteFileInputStream before this change is wrapped into a BufferedInputStream with a big buffer, the SSH client requests big packets from the server. It turned out that if the server had sent a response smaller than requested, the client wouldn't have adjusted to decreased window size, and would have read the file incorrectly.

This change detects cases when the server is not able to fulfil client's requests. Since this change, the client adjusts the maximum request length, sends new read-ahead requests, and starts to ignore all read-ahead requests sent earlier.

Just specifying some allegedly small constant buffer size wouldn't have helped in all possible cases. There is no way to explicitly get the maximum request length inside a client. All that limits differ from server to server. For instance, OpenSSH defines SFTP_MAX_MSG_LENGTH as 256 * 1024. Apache SSHD defines MAX_READDATA_PACKET_LENGTH as 63 * 1024, and it allows to redefine that size.

Interestingly, a similar issue #183 was fixed many years ago, but the bug was actually in the code introduced for that fix.
2022-03-04 21:07:18 +01:00
exceptionfactory
50efeb6519 Remove deprecated proxy connect methods from SocketClient (#756)
* Removed deprecated proxy connect methods from SocketClient

- Removed custom Jdk7HttpProxySocket class

* Reverted removal of Jdk7HttpProxySocket to retain JDK 7 support for HTTP CONNECT

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2022-02-04 17:29:26 +01:00
Yves Langisch
aabb1be52e Try all public key algorithms available for a specific key type in SSH_MSG_USERAUTH_REQUEST. (#763) 2022-02-04 09:08:30 +01:00
exceptionfactory
32329e547e Add Codecov to GitHub workflow (#759)
* Added Codecov to GitHub workflow

* Added Codecov to GitHub workflow
2022-01-03 20:24:45 +01:00
David Kocher
8cf63a96a9 Add parameter to limit read ahead to maximum length. Allows to use mu… (#724)
* Add parameter to limit read ahead to maximum length. Allows to use multiple concurrent threads reading from the same file with an offset without reading too much ahead for a single segment.

* Review and add tests.

Signed-off-by: David Kocher <dkocher@iterate.ch>

Co-authored-by: Yves Langisch <yves@langisch.ch>
2021-12-23 22:24:52 +01:00
exceptionfactory
cab7731928 Added Thread naming based on remote socket address (#738) (#753)
- Added ThreadNameProvider to set name based on Thread Class and remote socket address
- Added RemoteAddressProvider to abstract access to Remote Socket Address
- Set Reader Thread name in TransportImpl
- Set SFTP PacketReader Thread name in SFTPEngine
- Set KeepAlive Thread name in SSHClient

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-12-23 15:42:23 +01:00
Damiano Albani
50073db6c1 Bump version to latest release in POM excerpt (#736)
Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-12-22 17:44:18 +01:00
exceptionfactory
90099bbf5e Updated SSHClient to interrupt KeepAlive Thread when disconnecting (#506) (#752)
- Changed KeepAlive.setKeepAliveInterval() to avoid starting Thread
- Updated SSHClient.onConnect() to start KeepAlive Thread when enabled
- Updated SSHClient.disconnect() to interrupt KeepAlive Thread
- Updated KeepAliveThreadTerminationTest to verify state of KeepAlive Thread

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-12-22 16:55:09 +01:00
exceptionfactory
ce0a7d5193 Avoid setting SFTP rename flags below version 5 (#751) (#754)
Fixes #750 
Fixes #751 

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-12-22 12:18:28 +01:00
exceptionfactory
ced27fc898 Upgraded Bouncy Castle to 1.70 and upgraded test dependencies (#755)
- Adjusted test classes to work with Apache SSHD 2.8.0
- Upgraded Bouncy Castle from 1.69 to 1.70
- Upgraded Apache SSHD from 2.1.0 to 2.8.0
- Upgraded JUnit from 4.12 to 4.13.2
- Upgraded Mockito from 2.28.2 to 4.2.0
- Upgraded Logback from 1.2.6 to 1.2.9
- Upgraded Apache HTTP Client from 4.5.9 to 4.5.14
2021-12-22 10:37:36 +01:00
Vladimir Lagunov
624747c527 Lean on Config.keyAlgorithms choosing between rsa-sha2-* and ssh-rsa (#742)
* Improve SshdContainer: log `docker build` to stdout, don't wait too long if container exited

* Fix #740: Lean on Config.keyAlgorithms choosing between rsa-sha2-* and ssh-rsa

Previously, there was a heuristic that was choosing rsa-sha2-512 after receiving a host key of type RSA. It didn't work well when a server doesn't have an RSA host key.

OpenSSH 8.8 introduced a breaking change: it removed ssh-rsa from the default list of supported public key signature algorithms. SSHJ was unable to connect to OpenSSH 8.8 server if the server has an EcDSA or Ed25519 host key.

Current behaviour behaves the same as OpenSSH 8.8 client does. SSHJ doesn't try to determine rsa-sha2-* support on the fly. Instead, it looks only on `Config.getKeyAlgorithms()`, which may or may not contain ssh-rsa and rsa-sha2-* in any order.

Sorry, this commit mostly reverts changes from #607.

* Introduce ConfigImpl.prioritizeSshRsaKeyAlgorithm to deal with broken backward compatibility

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-12-06 12:14:04 +01:00
David Kocher
d8697c2228 ByteBuffer.array() must not be used as it does not take the real buffer size into account and returns the whole buffer up to its capacity. Fixes #745. (#746)
Co-authored-by: Yves Langisch <yla@iterate.ch>
2021-11-22 09:51:15 +01:00
Vladimir Lagunov
7c14098f7d Fix: if the client knows CA key, it should send host key algo proposal for certificates (#733)
* Fix: if the client knows CA key, it should send host key algo proposal for certificates

* Run specific SSH server in KeyWithCertificateSpec

Required to verify the case with wrong host key algorithm proposals. See #733

* Split KeyWithCertificateSpec into HostKeyWithCertificateSpec and PublicKeyAuthWithCertificateSpec

Prevents from starting unnecessary SSHD containers, making the tests run a bit faster when they are launched separately.
2021-11-10 23:06:07 +01:00
Vladimir Lagunov
d5805a6c64 Use testcontainers (#741)
* Replace abstract class IntegrationBaseSpec with composition through IntegrationTestUtil

* Switch to testcontainers in integration tests

It allows running different SSH servers with different configurations in tests, giving ability to cover more bugs, like mentioned in #733.
2021-11-10 14:30:35 +01:00
Torbjørn Søiland
8a66dc5336 Close client connection when remote closes connection + testing (#686) (#687) 2021-10-19 16:34:59 +02:00
Henning Pöttker
a5c10ab50f Fix issue urls in release notes (#732) 2021-10-12 20:11:48 +02:00
Jeroen van Erp
3256f5336d Update builds and release pipeline 2021-10-12 11:16:51 +02:00
Jeroen van Erp
ad87db9196 Update release notes 2021-10-12 10:13:24 +02:00
Jeroen van Erp
781f2dc632 Update vscode config
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-10-12 09:58:34 +02:00
Jan S
b2115dea6f full support for encrypted PuTTY v3 files (#730)
* full support for encrypted PuTTY v3 files (Argon2 library not included)

* simplified the PuTTYKeyDerivation interface and provided an abstract PuTTYArgon2 class for an easy Argon2 integration

* use Argon2 implementation from Bouncy Castle

* missing license header added

* license header again

* unit tests extended to cover all Argon2 variants and non-standard Argon2 parameters; verify the loaded keys
2021-10-12 09:47:11 +02:00
Jan S
d6d6f0dd33 only process supported Putty v3 keys + minor optimizations (#729) 2021-10-02 18:03:21 +02:00
Jeroen van Erp
93de1ecf47 Add license header 2021-09-29 09:27:29 +02:00
Jeroen van Erp
46ca5375d0 Remove long deprecated code 2021-09-28 21:56:52 +02:00
Jeroen van Erp
771ac0e346 Remove duplicated code
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-28 21:50:01 +02:00
Jeroen van Erp
eb09a16aef Send EOF on channel Close (Fixes #143, #496, #553, #554)
Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-27 22:58:12 +02:00
Luca Milanesio
53d241e4e3 Enable renaming with flags (#652)
* Enable renaming with flags

The SFTP protocol allows to rename files by specifying
extra flags:

- OVERWRITE
- ATOMIC
- NATIVE

The flags are exposed through a new RenameFlags enum and
can be passed as parameters to the rename() method in
SFTPClient/SFTPEngine.

Relates to #563

* Update RenameFlags.java

* Update RenameFlags.java

* Align license header with all other files

* Make RenameFlags parameter in line with OpenMode(s)

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-27 13:33:16 +02:00
exceptionfactory
03dd1aaf49 Update OpenSSH Key V1 parsing using CRT information for RSA Private Keys (#726)
* Update OpenSSH Key V1 parsing using CRT information for RSA Private Keys

* Remove unndeeded BC call.

Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-27 12:53:16 +02:00
Jeroen van Erp
7742d9b661 Upgrade to asn-one 0.6.0 (Fixes #678) 2021-09-27 12:11:05 +02:00
Bernie
14bf93e677 Prefer known algorithm for known host (#721)
* Prefer known algorithm for known host

(#642, #635... 10? issues)

Try to find the Algorithm that was used when a known_host
entry was created and make that the first choice for the
current connection attempt.

If the current connection algorithm matches the
algorithm used when the known_host entry was created
we can get a fair verification.

* Add support for multiple matching hostkeys, in configuration order

Co-authored-by: Bernie Day <bday@jvncomm.com>
Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-23 13:09:14 +02:00
exceptionfactory
753e3a50e5 Upgraded SLF4J to 1.7.32 and Logback to 1.2.6 (#722) 2021-09-23 08:22:18 +02:00
Bernd Schuller
2e1ef9dbcd Support v3 PuTTY keys (#716)
* Support v3 PuTTY keys

* add test for putty v3 key

* Format PuTTYKeyFile to fix Codacy warnings

Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com>

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-09-20 12:20:30 +02:00
exceptionfactory
6f9873712f Move BCrypt class to avoid conflict with org.mindrot:jbcrypt (#717)
- Renamed BCryptTest and updated using JUnit Test annotations
2021-09-02 09:50:35 +02:00
exceptionfactory
8e8e04ff1f Updated Build and Code Quality badges (#714) 2021-08-28 15:48:20 +02:00
exceptionfactory
b47e6fa012 Add PKCS8 parsing for encrypted PEM ASN.1 Private Keys (#713)
- Added unit tests for encrypted PKCS8 RSA Private Key
2021-08-27 23:37:37 +02:00
Jeroen van Erp
f38fcbe57e Add Automatic-Module-Name to MANIFEST.MF (#712) 2021-08-27 16:29:51 +02:00
118 changed files with 3497 additions and 2619 deletions

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
*.bat text eol=crlf
src/itest/docker-image/** eol=lf

View File

@@ -24,19 +24,8 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew check
# java10:
# name: Build with Java 10
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - name: Set up JDK 10
# uses: actions/setup-java@v1
# with:
# java-version: 10
# - name: Grant execute permission for gradlew
# run: chmod +x gradlew
# - name: Build with Gradle
# run: ./gradlew check -xanimalsnifferMain -xanimalsnifferTest
- name: Codecov
uses: codecov/codecov-action@v2
integration:
name: Integration test

49
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: SSHJ Release
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
java12:
name: Build with Java 12
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 12
uses: actions/setup-java@v1
with:
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew check
release:
name: Release
needs: [java12]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v1
with:
java-version: 12
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Release
run: ./gradlew release -Prelease.disableChecks -Prelease.pushTagsOnly -Prelease.customUsername=${{ github.actor }} -Prelease.customPassword=${{ github.token }}
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNINGKEY }}
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.SIGNINGKEYID }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNINGPASSWORD }}
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }}

View File

@@ -2,5 +2,6 @@
"java.checkstyle.configuration": "${workspaceFolder}/gradle/config/checkstyle/checkstyle.xml",
"files.watcherExclude": {
"**/target": true
}
},
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@@ -1,12 +1,11 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.31.0
:sshj_version: 0.32.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://github.com/hierynomus/sshj/actions/workflows/gradle.yml/badge.svg[link="https://github.com/hierynomus/sshj/actions/workflows/gradle.yml"]
image:https://app.codacy.com/project/badge/Grade/2c8a5a67c6a54ed89c9a699fd6b27305["Codacy Grade", link="https://app.codacy.com/gh/hierynomus/sshj"]
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"]
@@ -96,7 +95,7 @@ If you need something that is not included, it shouldn't be too hard to add (do
http://ssh-comparison.quendi.de/comparison.html[SSH Implementation Comparison]
== Dependencies
Java 6+. http://www.slf4j.org/download.html[slf4j] is required. http://www.bouncycastle.org/java.html[bouncycastle] is highly recommended and required for using some of the crypto algorithms. http://www.jcraft.com/jzlib/[jzlib] is required for using zlib compression.
Java 7+. http://www.slf4j.org/download.html[slf4j] is required. http://www.bouncycastle.org/java.html[bouncycastle] is highly recommended and required for using some of the crypto algorithms.
== Reporting bugs
Issue tracker: https://github.com/hierynomus/sshj/issues
@@ -105,6 +104,48 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
Fork away!
== Release history
SSHJ 0.35.0 (2023-01-30)::
* Merged https://github.com/hierynomus/sshj/pull/835[#835]: TimeoutException message improved
* Merged https://github.com/hierynomus/sshj/pull/815[#815]: Support authPassword on FreeBSD
* Merged https://github.com/hierynomus/sshj/pull/813[#813]: Prevent `CHANNEL_CLOSE` between isOpen and write call.
* Merged https://github.com/hierynomus/sshj/pull/811[#811]: Add `Transport.isKeyExchangeREquired` to prevent unnecessary KEXINIT
SSHJ 0.34.0 (2022-08-10)::
* Merged https://github.com/hierynomus/sshj/pull/743[#743]: Use default client credentials for AuthGssApiWithMic
* Merged https://github.com/hierynomus/sshj/pull/801[#801]: Restore thread interrupt status after catching InterruptedException
* Merged https://github.com/hierynomus/sshj/pull/793[#793]: Merge PKCS5 and PKCS8 classes
* Upgraded dependencies SLF4J (1.7.36) and Logback (1.2.11)
* Merged https://github.com/hierynomus/sshj/pull/791[#791]: Update KeepAlive examples
* Merged https://github.com/hierynomus/sshj/pull/775[#775]: Add SFTP resume support
SSHJ 0.33.0 (2022-04-22)::
* Upgraded dependencies BouncyCastle (1.70)
* Merged https://github.com/hierynomus/sshj/pull/687[#687]: Correctly close connection when remote closes connection.
* Merged https://github.com/hierynomus/sshj/pull/741[#741]: Add support for testcontainers in test setup to test more scenarios
* Merged https://github.com/hierynomus/sshj/pull/733[#733]: Send correct key proposal if client knows CA key
* Merged https://github.com/hierynomus/sshj/pull/746[#746]: Fix bug in reading Putty private key file with passphrase
* Merged https://github.com/hierynomus/sshj/pull/742[#742]: Use Config.keyAlgorithms to determine rsa-sha2 support
* Merged https://github.com/hierynomus/sshj/pull/754[#754]: Use SFTP protocol version to set FXP rename flags conditionally
* Merged https://github.com/hierynomus/sshj/pull/752[#752]: Correctly start and terminate KeepAlive thread
* Merged https://github.com/hierynomus/sshj/pull/753[#753]: Provide better thread names
* Merged https://github.com/hierynomus/sshj/pull/724[#724]: Add parameter to limit read ahead length
* Merged https://github.com/hierynomus/sshj/pull/763[#763]: Try all public key algorithms for a specific key type
* Merged https://github.com/hierynomus/sshj/pull/756[#756]: Remove deprecated proxy connect methods
* Merged https://github.com/hierynomus/sshj/pull/770[#770]: Add support for `ed25519` `aes-128-cbc` keys
* Merged https://github.com/hierynomus/sshj/pull/773[#773]: Fix NPE when reading empty OpenSSHKeyV1KeyFile
* Merged https://github.com/hierynomus/sshj/pull/777[#777]: Don't request too many read-ahead packets
SSHJ 0.32.0 (2021-10-12)::
* Send EOF on channel close (Fixes https://github.com/hierynomus/sshj/issues/143[#143], https://github.com/hierynomus/sshj/issues/496[#496], https://github.com/hierynomus/sshj/issues/553[#553], https://github.com/hierynomus/sshj/issues/554[#554])
* Merged https://github.com/hierynomus/sshj/pull/726[#726]: Parse OpenSSH v1 keys with full CRT information present
* Merged https://github.com/hierynomus/sshj/pull/721[#721]: Prefer known host key algorithm for host key verification
* Merged https://github.com/hierynomus/sshj/pull/716[#716], https://github.com/hierynomus/sshj/pull/729[#729] and https://github.com/hierynomus/sshj/pull/730[#730]: Add full support for PuTTY v3 key files.
* Merged https://github.com/hierynomus/sshj/pull/708[#708] and https://github.com/hierynomus/sshj/pull/713[#71]: Add support for PKCS#8 private keys
* Merged https://github.com/hierynomus/sshj/pull/703[#703]: Support host certificate keys
* Upgraded dependencies BouncyCastle (1.69), SLF4j (1.7.32), Logback (1.2.6), asn-one (0.6.0)
* Merged https://github.com/hierynomus/sshj/pull/702[#702]: Support Public key authentication using certificates
* Merged https://github.com/hierynomus/sshj/pull/691[#691]: Fix for writing negative unsigned integers to Buffer
* Merged https://github.com/hierynomus/sshj/pull/682[#682]: Support for chacha20-poly1305@openssh.com cipher
* Merged https://github.com/hierynomus/sshj/pull/680[#680]: Configurable preserve mtimes for SCP transfers
SSHJ 0.31.0 (2021-02-08)::
* Bump dependencies (asn-one 0.5.0, BouncyCastle 1.68, slf4j-api 1.7.30)
* Merged https://github.com/hierynomus/sshj/pull/660[#660]: Support ED25519 and ECDSA keys in PuTTY format

View File

@@ -23,7 +23,6 @@ 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"
testCompile "junit:junit:4.11"
testCompile "org.mockito:mockito-core:1.9.5"

View File

@@ -1,23 +1,23 @@
import java.text.SimpleDateFormat
import com.bmuschko.gradle.docker.tasks.container.*
import com.bmuschko.gradle.docker.tasks.image.*
plugins {
id "java"
id "groovy"
id "jacoco"
id "com.github.blindpirate.osgi" version '0.0.3'
id "com.github.blindpirate.osgi" version '0.0.6'
id "maven-publish"
id 'pl.allegro.tech.build.axion-release' version '1.11.0'
id "com.bmuschko.docker-remote-api" version "6.4.0"
id "com.github.hierynomus.license" version "0.12.1"
id "com.jfrog.bintray" version "1.8.5"
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 "signing"
id 'pl.allegro.tech.build.axion-release' version '1.13.3'
id "com.bmuschko.docker-remote-api" version "7.1.0"
id "com.github.hierynomus.license" version "0.16.1"
id 'ru.vyarus.github-info' version '1.2.0'
id "io.github.gradle-nexus.publish-plugin" version "1.0.0"
}
repositories {
mavenCentral()
}
group = "com.hierynomus"
defaultTasks ["build"]
ext.moduleName = "${project.group}.${project.name}"
scmVersion {
@@ -33,36 +33,29 @@ scmVersion {
project.version = scmVersion.version
defaultTasks "build"
configurations.implementation.transitive = false
repositories {
mavenCentral()
}
configurations.compile.transitive = false
def bouncycastleVersion = "1.69"
def sshdVersion = "2.1.0"
def bouncycastleVersion = "1.70"
def sshdVersion = "2.8.0"
dependencies {
implementation "org.slf4j:slf4j-api:1.7.30"
implementation "org.slf4j:slf4j-api:1.7.36"
implementation "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion"
implementation "com.jcraft:jzlib:1.1.3"
implementation "com.hierynomus:asn-one:0.5.0"
implementation "com.hierynomus:asn-one:0.6.0"
implementation "net.i2p.crypto:eddsa:0.3.0"
testImplementation "junit:junit:4.12"
testImplementation "junit:junit:4.13.2"
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.4'
testImplementation "org.mockito:mockito-core:2.28.2"
testImplementation "org.mockito:mockito-core:4.2.0"
testImplementation "org.apache.sshd:sshd-core:$sshdVersion"
testImplementation "org.apache.sshd:sshd-sftp:$sshdVersion"
testImplementation "org.apache.sshd:sshd-scp:$sshdVersion"
testRuntimeOnly "ch.qos.logback:logback-classic:1.2.3"
testImplementation "ch.qos.logback:logback-classic:1.2.11"
testImplementation 'org.glassfish.grizzly:grizzly-http-server:2.4.4'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.9'
testImplementation 'org.apache.httpcomponents:httpclient:4.5.13'
testImplementation 'org.testcontainers:testcontainers:1.16.2'
}
license {
@@ -71,7 +64,11 @@ license {
mapping {
java = 'SLASHSTAR_STYLE'
}
excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java', '**/org/mindrot/jbcrypt/*.java'])
excludes([
'**/sshj/common/Base64.java',
'**/com/hierynomus/sshj/userauth/keyprovider/bcrypt/*.java',
'**/files/test_file_*.txt',
])
}
if (!JavaVersion.current().isJava9Compatible()) {
@@ -85,7 +82,6 @@ if (JavaVersion.current().isJava8Compatible()) {
}
}
compileJava {
options.compilerArgs.addAll(['--release', '7'])
}
@@ -123,6 +119,12 @@ jar {
}
}
java {
withJavadocJar()
withSourcesJar()
}
sourcesJar {
manifest {
attributes(
@@ -188,71 +190,82 @@ github {
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"
}
}
}
publishing {
publications {
maven(MavenPublication) {
from(components.java)
}
}
}
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}"
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
project.signing {
required { project.gradle.taskGraph.hasTask("release") }
sign publishing.publications.maven
if (project.hasProperty("signingKeyId") || project.hasProperty("signingKey")) {
def signingKeyId = project.findProperty("signingKeyId")
def signingKey = project.findProperty("signingKey")
def signingPassword = project.findProperty("signingPassword")
if (signingKeyId) {
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
} else if (signingKey) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
}
}
project.plugins.withType(MavenPublishPlugin).all {
PublishingExtension publishing = project.extensions.getByType(PublishingExtension)
publishing.publications.withType(MavenPublication).all { mavenPublication ->
mavenPublication.pom {
name = "${project.name}"
description = 'SSHv2 library for Java'
inceptionYear = '2009'
url = "https://github.com/hierynomus/${project.name}"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0"
}
}
developers {
developer {
id = "hierynomus"
name = "Jeroen van Erp"
email = "jeroen@hierynomus.com"
}
developer {
id = "shikhar"
name = "Shikhar Bhushan"
email = "shikhar@schmizz.net"
url = "http://schmizz.net"
roles = ["Previous Lead developer"]
}
developer {
id = "iterate"
name = "David Kocher"
email = "dkocher@iterate.ch"
organization = "iterate GmbH"
organizationUrl = "https://iterate.ch"
roles = ["Developer"]
}
}
scm {
url = "https://github.com/hierynomus/${project.name}"
connection = "scm:git@github.com:hierynomus/${project.name}.git"
developerConnection = "scm:git@github.com:hierynomus/${project.name}.git"
}
}
}
}
}
}
nexusPublishing {
repositories {
sonatype() //sonatypeUsername and sonatypePassword properties are used automatically
}
connectTimeout = Duration.ofMinutes(3)
clientTimeout = Duration.ofMinutes(3)
}
jacocoTestReport {
@@ -262,48 +275,11 @@ jacocoTestReport {
}
}
task buildItestImage(type: DockerBuildImage) {
inputDir = file('src/itest/docker-image')
images.add('sshj/sshd-itest:latest')
}
task createItestContainer(type: DockerCreateContainer) {
dependsOn buildItestImage
targetImageId buildItestImage.getImageId()
hostConfig.portBindings = ['2222:22']
hostConfig.autoRemove = true
}
task startItestContainer(type: DockerStartContainer) {
dependsOn createItestContainer
targetContainerId createItestContainer.getContainerId()
}
task logItestContainer(type: DockerLogsContainer) {
dependsOn createItestContainer
targetContainerId createItestContainer.getContainerId()
showTimestamps = true
stdErr = true
stdOut = true
tailAll = true
}
task stopItestContainer(type: DockerStopContainer) {
targetContainerId createItestContainer.getContainerId()
}
task forkedUploadRelease(type: GradleBuild) {
buildFile = project.buildFile
tasks = ["bintrayUpload"]
tasks = ["clean", "publishToSonatype", "closeAndReleaseSonatypeStagingRepository"]
}
project.tasks.integrationTest.dependsOn(startItestContainer)
project.tasks.integrationTest.finalizedBy(stopItestContainer)
// Being enabled, it pollutes logs on CI. Uncomment when debugging some test to get sshd logs.
// project.tasks.stopItestContainer.dependsOn(logItestContainer)
project.tasks.release.dependsOn([project.tasks.integrationTest, project.tasks.build])
project.tasks.release.finalizedBy(project.tasks.forkedUploadRelease)
project.tasks.jacocoTestReport.dependsOn(project.tasks.test)

View File

@@ -24,7 +24,7 @@
<groupId>com.hierynomus</groupId>
<artifactId>sshj-examples</artifactId>
<packaging>jar</packaging>
<version>0.19.1</version>
<version>0.33.0</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.31.0</version>
<version>0.33.0</version>
</dependency>
</dependencies>

View File

@@ -19,8 +19,9 @@ public class KeepAlive {
final SSHClient ssh = new SSHClient(defaultConfig);
try {
ssh.addHostKeyVerifier(new PromiscuousVerifier());
// Set interval to enable keep-alive before connecting
ssh.getConnection().getKeepAlive().setKeepAliveInterval(5);
ssh.connect(args[0]);
ssh.getConnection().getKeepAlive().setKeepAliveInterval(5); //every 60sec
ssh.authPassword(args[1], args[2]);
Session session = ssh.startSession();
session.allocateDefaultPTY();

View File

@@ -19,6 +19,7 @@ public class RemotePF {
client.loadKnownHosts();
client.connect("localhost");
client.getConnection().getKeepAlive().setKeepAliveInterval(5);
try {
client.authPublickey(System.getProperty("user.name"));
@@ -33,8 +34,6 @@ public class RemotePF {
// what we do with incoming connections that are forwarded to us
new SocketForwardingConnectListener(new InetSocketAddress("google.com", 80)));
client.getTransport().setHeartbeatInterval(30);
// Something to hang on to so that the forwarding stays
client.getTransport().join();

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,24 +0,0 @@
FROM sickp/alpine-sshd:7.5-r2
ADD authorized_keys /home/sshj/.ssh/authorized_keys
ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key
ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub
ADD test-container/ssh_host_ed25519_key /etc/ssh/ssh_host_ed25519_key
ADD test-container/ssh_host_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub
ADD test-container/sshd_config /etc/ssh/sshd_config
COPY test-container/trusted_ca_keys /etc/ssh/trusted_ca_keys
COPY test-container/host_keys/* /etc/ssh/
RUN apk add --no-cache tini
RUN \
echo "root:smile" | chpasswd && \
adduser -D -s /bin/ash sshj && \
passwd -u sshj && \
echo "sshj:ultrapassword" | chpasswd && \
chmod 600 /home/sshj/.ssh/authorized_keys && \
chmod 600 /etc/ssh/ssh_host_*_key && \
chmod 644 /etc/ssh/*.pub && \
chown -R sshj:sshj /home/sshj
ENTRYPOINT ["/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2"]

View File

@@ -3,6 +3,7 @@ ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHQiZm0w
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8ww4hJG/gHJYdkjTTBDF1GNz+228nuWprPV+NbQauA ajvanerp@Heimdall.local
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaWrwt3drIOjeBq2LSHRavxAT7ja2f+5soOUJl/zKSI ajvanerp@Heimdall.xebialabs.com
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBi sshjtest@TranceLove
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKRyZAtOJJfAhPU6xE6ZXY564vwErAI3n3Yn4lTHL9bxev9Ily6eCqPLcV0WbSV04pztngFn9MjT7yb8mcXheHpIaWEH569sMpmpOtyfn4p68SceuXBGyyPGMIcfOTknkASd1JYSD4EPkd9rZmCzcx3vEnLu8ChnA/G221xSVQ5VC/jD/c/CgNUayhQ+xbn57qHKKtZwfTa21QmwIabGYJNwlVjlKTCdddeVnZfKqKrG7cxHQApsxd21rhM9IT/C/f4Y/Tx3WUUVeam0iZ265oiPHoPALqJIWSQIUheRYAxYAQqJwSQ0Or9MM8XXun2Iy3RUSGk6eIvrCsFbNURsHNs7Pu0UnpYv6FZ3vCkFep/1pAT6fQvY7pDOOWDHKXArD4watc9gIWaQBH73wDW/KgBcnMRSoGWgQjsYqIamP4oV1+HqUI3lRAsXZaX+eiBGt3+3A5KebP27UJ1YUwhwlzs7wzTKaCu0OaL+hOsP1F2AxAa995bgFksMd23645ux3YCJKXG4sGpJ1Z/Hs49K72gv+QjLZVxXqY623c8+3OUhlixqoEFd4iG7UMc5a552ch/VA+jaspmLZoFhPz99aBRVb1oCSPxSwLw+Q/wxv6pZmT+14rqTzY2farjU53hM+CsUPh7dnWXhGG7RuA5wCdeOXOYjuksfzAoHIZhPqTgQ== ajvanerp@Heimdall.local
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMvfRYSe44VQGwxexOMibcM3+fWeUP1jrBofOxFDRRrzRF8dK/vll2svqTPXMRnITnT1UoemEcB5OHtvH4hzfh/HFeDxJ5S7UncYxoClTSa8MeMFG2Zj9CoUZs1SHbwSGg== root@sshj

View File

@@ -1,158 +0,0 @@
# $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
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1
macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com
TrustedUserCAKeys /etc/ssh/trusted_ca_keys
Ciphers 3des-cbc,blowfish-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
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
HostKey /etc/ssh/ssh_host_ecdsa_256_key
HostCertificate /etc/ssh/ssh_host_ecdsa_256_key-cert.pub
HostKey /etc/ssh/ssh_host_ecdsa_384_key
HostCertificate /etc/ssh/ssh_host_ecdsa_384_key-cert.pub
HostKey /etc/ssh/ssh_host_ecdsa_521_key
HostCertificate /etc/ssh/ssh_host_ecdsa_521_key-cert.pub
HostKey /etc/ssh/ssh_host_ed25519_384_key
HostCertificate /etc/ssh/ssh_host_ed25519_384_key-cert.pub
HostKey /etc/ssh/ssh_host_rsa_2048_key
HostCertificate /etc/ssh/ssh_host_rsa_2048_key-cert.pub
LogLevel DEBUG2

View File

@@ -1,42 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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/itest/resources/keyfiles/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())
}
}

View File

@@ -20,9 +20,15 @@ import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.transport.TransportException
import net.schmizz.sshj.userauth.UserAuthException
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class IntegrationSpec extends IntegrationBaseSpec {
class IntegrationSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "should accept correct key for #signatureName"() {
@@ -33,7 +39,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
sshClient.addHostKeyVerifier(fingerprint) // test-containers/ssh_host_ecdsa_key's fingerprint
when:
sshClient.connect(SERVER_IP, DOCKER_PORT)
sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort)
then:
sshClient.isConnected()
@@ -50,7 +56,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
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)
sshClient.connect(sshd.containerIpAddress, sshd.firstMappedPort)
then:
thrown(TransportException.class)
@@ -59,11 +65,11 @@ class IntegrationSpec extends IntegrationBaseSpec {
@Unroll
def "should authenticate with key #key"() {
given:
SSHClient client = getConnectedClient()
SSHClient client = sshd.getConnectedClient()
when:
def keyProvider = passphrase != null ? client.loadKeys("src/itest/resources/keyfiles/$key", passphrase) : client.loadKeys("src/itest/resources/keyfiles/$key")
client.authPublickey(USERNAME, keyProvider)
client.authPublickey(IntegrationTestUtil.USERNAME, keyProvider)
then:
client.isAuthenticated()
@@ -74,6 +80,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
"id_ecdsa_opensshv1" | null
"id_ed25519_opensshv1" | null
"id_ed25519_opensshv1_aes256cbc.pem" | "foobar"
"id_ed25519_opensshv1_aes128cbc.pem" | "sshjtest"
"id_ed25519_opensshv1_protected" | "sshjtest"
"id_rsa" | null
"id_rsa_opensshv1" | null
@@ -83,7 +90,7 @@ class IntegrationSpec extends IntegrationBaseSpec {
def "should not authenticate with wrong key"() {
given:
SSHClient client = getConnectedClient()
SSHClient client = sshd.getConnectedClient()
when:
client.authPublickey("sshj", "src/itest/resources/keyfiles/id_unknown_key")

View File

@@ -0,0 +1,21 @@
/*
* 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
class IntegrationTestUtil {
static final String USERNAME = "sshj"
static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa"
}

View File

@@ -0,0 +1,74 @@
/*
* 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.SSHClient
import net.schmizz.sshj.common.IOUtils
import net.schmizz.sshj.connection.channel.direct.Session
import spock.lang.Specification
import java.util.concurrent.*
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
class ManyChannelsSpec extends Specification {
def "should work with many channels without nonexistent channel error (GH issue #805)"() {
given:
SshdContainer sshd = new SshdContainer.Builder()
.withSshdConfig("""${SshdContainer.Builder.DEFAULT_SSHD_CONFIG}
MaxSessions 200
""".stripMargin())
.build()
sshd.start()
SSHClient client = sshd.getConnectedClient()
client.authPublickey("sshj", "src/test/resources/id_rsa")
when:
List<Future<Exception>> futures = []
ExecutorService executorService = Executors.newCachedThreadPool()
for (int i in 0..20) {
futures.add(executorService.submit((Callable<Exception>) {
return execute(client)
}))
}
executorService.shutdown()
executorService.awaitTermination(1, TimeUnit.DAYS)
then:
futures*.get().findAll { it != null }.empty
cleanup:
client.close()
}
private static Exception execute(SSHClient sshClient) {
try {
for (def i in 0..100) {
withCloseable (sshClient.startSession()) {sshSession ->
Session.Command sshCommand = sshSession.exec("ls -la")
IOUtils.readFully(sshCommand.getInputStream()).toString()
sshCommand.close()
}
}
} catch (Exception e) {
return e
}
return null
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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 com.hierynomus.sshj.key.KeyAlgorithms
import net.schmizz.sshj.Config
import net.schmizz.sshj.DefaultConfig
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder
import spock.lang.Specification
import spock.lang.Unroll
import java.nio.file.Paths
/**
* Checks that SSHJ is able to work with OpenSSH 8.8, which removed ssh-rsa signature from the default setup.
*/
class RsaShaKeySignatureTest extends Specification {
private static final Map<String, KeyAlgorithms.Factory> SSH_HOST_KEYS_AND_FACTORIES = [
'ssh_host_ecdsa_256_key': KeyAlgorithms.ECDSASHANistp256(),
'ssh_host_ecdsa_384_key': KeyAlgorithms.ECDSASHANistp384(),
'ssh_host_ecdsa_521_key': KeyAlgorithms.ECDSASHANistp521(),
'ssh_host_ed25519_384_key': KeyAlgorithms.EdDSA25519(),
'ssh_host_rsa_2048_key': KeyAlgorithms.RSASHA512(),
]
private static void dockerfileBuilder(DockerfileBuilder it, String hostKey, String pubkeyAcceptedAlgorithms) {
it.from("archlinux:base")
it.run('yes | pacman -Sy core/openssh' +
' && (' +
' V=$(echo $(/usr/sbin/sshd -h 2>&1) | grep -o \'OpenSSH_[0-9][0-9]*[.][0-9][0-9]*p[0-9]\');' +
' if [[ "$V" < OpenSSH_8.8p1 ]]; then' +
' echo $V is too old 1>&2;' +
' exit 1;' +
' fi' +
')' +
' && set -o pipefail ' +
' && useradd --create-home sshj' +
' && echo \"sshj:ultrapassword\" | chpasswd')
it.add("authorized_keys", "/home/sshj/.ssh/")
it.add(hostKey, '/etc/ssh/')
it.run('chmod go-rwx /etc/ssh/ssh_host_*' +
' && chown -R sshj /home/sshj/.ssh' +
' && chmod -R go-rwx /home/sshj/.ssh')
it.expose(22)
def cmd = [
'/usr/sbin/sshd',
'-D',
'-e',
'-f', '/dev/null',
'-o', 'LogLevel=DEBUG2',
'-o', "HostKey=/etc/ssh/$hostKey",
]
if (pubkeyAcceptedAlgorithms != null) {
cmd += ['-o', "PubkeyAcceptedAlgorithms=$pubkeyAcceptedAlgorithms"]
}
it.cmd(cmd as String[])
}
private static SshdContainer makeSshdContainer(String hostKey, String pubkeyAcceptedAlgorithms) {
return new SshdContainer(new SshdContainer.DebugLoggingImageFromDockerfile()
.withFileFromPath("authorized_keys", Paths.get("src/itest/docker-image/authorized_keys"))
.withFileFromPath(hostKey, Paths.get("src/itest/docker-image/test-container/host_keys/$hostKey"))
.withDockerfileFromBuilder {
dockerfileBuilder(it, hostKey, pubkeyAcceptedAlgorithms)
})
}
@Unroll
def "connect to a server with host key #hostKey that does not support ssh-rsa"() {
given:
SshdContainer sshd = makeSshdContainer(hostKey, "rsa-sha2-512,rsa-sha2-256,ssh-ed25519")
sshd.start()
and:
Config config = new DefaultConfig()
config.keyAlgorithms = [
KeyAlgorithms.RSASHA512(),
KeyAlgorithms.RSASHA256(),
SSH_HOST_KEYS_AND_FACTORIES[hostKey],
]
when:
def sshClient = sshd.getConnectedClient(config)
sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1")
then:
sshClient.isAuthenticated()
cleanup:
sshClient?.disconnect()
sshd.stop()
where:
hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet()
}
@Unroll
def "connect to a default server with host key #hostKey using a default config"() {
given:
SshdContainer sshd = makeSshdContainer(hostKey, null)
sshd.start()
when:
def sshClient = sshd.getConnectedClient()
sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1")
then:
sshClient.isAuthenticated()
cleanup:
sshClient?.disconnect()
sshd.stop()
where:
hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet()
}
@Unroll
def "connect to a server with host key #hostkey that supports only ssh-rsa"() {
given:
SshdContainer sshd = makeSshdContainer(hostKey, "ssh-rsa,ssh-ed25519")
sshd.start()
and:
Config config = new DefaultConfig()
config.keyAlgorithms = [
KeyAlgorithms.SSHRSA(),
SSH_HOST_KEYS_AND_FACTORIES[hostKey],
]
when:
def sshClient = sshd.getConnectedClient(config)
sshClient.authPublickey("sshj", "src/itest/resources/keyfiles/id_rsa_opensshv1")
then:
sshClient.isAuthenticated()
cleanup:
sshClient.disconnect()
sshd.stop()
where:
hostKey << SSH_HOST_KEYS_AND_FACTORIES.keySet()
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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 org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
/**
* A wait strategy designed for {@link SshdContainer} to wait until the SSH server is ready, to avoid races when a test
* tries to connect to a server before the server has started.
*/
public class SshServerWaitStrategy implements WaitStrategy {
private Duration startupTimeout = Duration.ofMinutes(1);
@Override
public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {
long expectedEnd = System.nanoTime() + startupTimeout.toNanos();
while (waitStrategyTarget.isRunning()) {
long attemptStart = System.nanoTime();
IOException error = null;
byte[] buffer = new byte[7];
try (Socket socket = new Socket()) {
socket.setSoTimeout(500);
socket.connect(new InetSocketAddress(
waitStrategyTarget.getHost(), waitStrategyTarget.getFirstMappedPort()));
// Haven't seen any SSH server that sends the version in two or more packets.
//noinspection ResultOfMethodCallIgnored
socket.getInputStream().read(buffer);
if (!Arrays.equals(buffer, "SSH-2.0".getBytes(StandardCharsets.UTF_8))) {
error = new IOException("The version message doesn't look like an SSH server version");
}
} catch (IOException err) {
error = err;
}
if (error == null) {
break;
} else if (System.nanoTime() >= expectedEnd) {
throw new RuntimeException(error);
}
try {
//noinspection BusyWait
Thread.sleep(Math.max(0L, 500L - (System.nanoTime() - attemptStart) / 1_000_000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
@Override
public WaitStrategy withStartupTimeout(Duration startupTimeout) {
this.startupTimeout = startupTimeout;
return this;
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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 ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
import org.testcontainers.utility.DockerLoggerFactory;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.Future;
/**
* A JUnit4 rule for launching a generic SSH server container.
*/
public class SshdContainer extends GenericContainer<SshdContainer> {
/**
* A workaround for strange logger names of testcontainers. They contain no dots, but contain slashes,
* square brackets, and even emoji. It's uneasy to set the logging level via the XML file of logback, the
* result would be less readable than the code below.
*/
public static class DebugLoggingImageFromDockerfile extends ImageFromDockerfile {
public DebugLoggingImageFromDockerfile() {
super();
Logger logger = (Logger) LoggerFactory.getILoggerFactory()
.getLogger(DockerLoggerFactory.getLogger(getDockerImageName()).getName());
logger.setLevel(Level.DEBUG);
}
}
public static class Builder {
public static final String DEFAULT_SSHD_CONFIG = "" +
"PermitRootLogin yes\n" +
"AuthorizedKeysFile .ssh/authorized_keys\n" +
"Subsystem sftp /usr/lib/ssh/sftp-server\n" +
"KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1\n" +
"macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,hmac-ripemd160@openssh.com\n" +
"TrustedUserCAKeys /etc/ssh/trusted_ca_keys\n" +
"Ciphers 3des-cbc,blowfish-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com\n" +
"HostKey /etc/ssh/ssh_host_rsa_key\n" +
"HostKey /etc/ssh/ssh_host_dsa_key\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_key\n" +
"HostKey /etc/ssh/ssh_host_ed25519_key\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_256_key\n" +
"HostCertificate /etc/ssh/ssh_host_ecdsa_256_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_384_key\n" +
"HostCertificate /etc/ssh/ssh_host_ecdsa_384_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_ecdsa_521_key\n" +
"HostCertificate /etc/ssh/ssh_host_ecdsa_521_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_ed25519_384_key\n" +
"HostCertificate /etc/ssh/ssh_host_ed25519_384_key-cert.pub\n" +
"HostKey /etc/ssh/ssh_host_rsa_2048_key\n" +
"HostCertificate /etc/ssh/ssh_host_rsa_2048_key-cert.pub\n" +
"LogLevel DEBUG2\n";
public static void defaultDockerfileBuilder(@NotNull DockerfileBuilder builder) {
builder.from("sickp/alpine-sshd:7.5-r2");
builder.add("authorized_keys", "/home/sshj/.ssh/authorized_keys");
builder.add("test-container/ssh_host_ecdsa_key", "/etc/ssh/ssh_host_ecdsa_key");
builder.add("test-container/ssh_host_ecdsa_key.pub", "/etc/ssh/ssh_host_ecdsa_key.pub");
builder.add("test-container/ssh_host_ed25519_key", "/etc/ssh/ssh_host_ed25519_key");
builder.add("test-container/ssh_host_ed25519_key.pub", "/etc/ssh/ssh_host_ed25519_key.pub");
builder.copy("test-container/trusted_ca_keys", "/etc/ssh/trusted_ca_keys");
builder.copy("test-container/host_keys/*", "/etc/ssh/");
builder.run("apk add --no-cache tini"
+ " && echo \"root:smile\" | chpasswd"
+ " && adduser -D -s /bin/ash sshj"
+ " && passwd -u sshj"
+ " && echo \"sshj:ultrapassword\" | chpasswd"
+ " && chmod 600 /home/sshj/.ssh/authorized_keys"
+ " && chmod 600 /etc/ssh/ssh_host_*_key"
+ " && chmod 644 /etc/ssh/*.pub"
+ " && chown -R sshj:sshj /home/sshj");
builder.entryPoint("/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2");
builder.add("sshd_config", "/etc/ssh/sshd_config");
}
private @NotNull String sshdConfig = DEFAULT_SSHD_CONFIG;
public @NotNull Builder withSshdConfig(@NotNull String sshdConfig) {
this.sshdConfig = sshdConfig;
return this;
}
public @NotNull SshdContainer build() {
return new SshdContainer(buildInner());
}
private @NotNull Future<String> buildInner() {
return new DebugLoggingImageFromDockerfile()
.withDockerfileFromBuilder(Builder::defaultDockerfileBuilder)
.withFileFromPath(".", Paths.get("src/itest/docker-image"))
.withFileFromString("sshd_config", sshdConfig);
}
}
@SuppressWarnings("unused") // Used dynamically by Spock
public SshdContainer() {
this(new SshdContainer.Builder().buildInner());
}
public SshdContainer(@NotNull Future<String> future) {
super(future);
withExposedPorts(22);
setWaitStrategy(new SshServerWaitStrategy());
withLogConsumer(outputFrame -> {
switch (outputFrame.getType()) {
case STDOUT:
logger().info("sshd stdout: {}", outputFrame.getUtf8String().stripTrailing());
break;
case STDERR:
logger().info("sshd stderr: {}", outputFrame.getUtf8String().stripTrailing());
break;
}
});
}
public SSHClient getConnectedClient(Config config) throws IOException {
SSHClient sshClient = new SSHClient(config);
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
sshClient.connect("127.0.0.1", getFirstMappedPort());
return sshClient;
}
public SSHClient getConnectedClient() throws IOException {
return getConnectedClient(new DefaultConfig());
}
}

View File

@@ -15,21 +15,28 @@
*/
package com.hierynomus.sshj.sftp
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.sftp.OpenMode
import net.schmizz.sshj.sftp.RemoteFile
import net.schmizz.sshj.sftp.SFTPClient
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import java.nio.charset.StandardCharsets
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
class FileWriteSpec extends IntegrationBaseSpec {
class FileWriteSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
def "should append to file (GH issue #390)"() {
given:
SSHClient client = getConnectedClient()
SSHClient client = sshd.getConnectedClient()
client.authPublickey("sshj", "src/test/resources/id_rsa")
SFTPClient sftp = client.newSFTPClient()
def file = "/home/sshj/test.txt"

View File

@@ -0,0 +1,83 @@
/*
* 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.signature
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts
import spock.lang.Specification
import spock.lang.Unroll
import java.nio.file.Files
/**
* This is a brief test for verifying connection to a server using keys with certificates.
*
* Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}.
*/
class HostKeyWithCertificateSpec extends Specification {
@Unroll
def "accepting a signed host public key #hostKey"() {
given:
SshdContainer sshd = new SshdContainer.Builder()
.withSshdConfig("""
PasswordAuthentication yes
HostKey /etc/ssh/$hostKey
HostCertificate /etc/ssh/${hostKey}-cert.pub
""".stripMargin())
.build()
sshd.start()
and:
File knownHosts = Files.createTempFile("known_hosts", "").toFile()
knownHosts.deleteOnExit()
and:
File caPubKey = new File("src/itest/resources/keyfiles/certificates/CA_rsa.pem.pub")
def address = "127.0.0.1"
String knownHostsFileContents = "" +
"@cert-authority ${ address} ${caPubKey.text}" +
"\n@cert-authority [${address}]:${sshd.firstMappedPort} ${caPubKey.text}"
knownHosts.write(knownHostsFileContents)
and:
SSHClient sshClient = new SSHClient(new DefaultConfig())
sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(knownHosts))
sshClient.connect(address, sshd.firstMappedPort)
when:
sshClient.authPassword("sshj", "ultrapassword")
then:
sshClient.authenticated
and:
knownHosts.getText() == knownHostsFileContents
cleanup:
sshd.stop()
where:
hostKey << [
"ssh_host_ecdsa_256_key",
"ssh_host_ecdsa_384_key",
"ssh_host_ecdsa_521_key",
"ssh_host_ed25519_384_key",
"ssh_host_rsa_2048_key",
]
}
}

View File

@@ -15,29 +15,34 @@
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import java.nio.file.Files
import java.util.stream.Collectors
/**
* This is a brief test for verifying connection to a server using keys with certificates.
*
* Also, take a look at the unit test {@link net.schmizz.sshj.transport.verification.KeyWithCertificateUnitSpec}.
*/
class KeyWithCertificateSpec extends IntegrationBaseSpec {
class PublicKeyAuthWithCertificateSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "authorising with a signed public key #keyName"() {
given:
def client = getConnectedClient()
SSHClient client = new SSHClient(new DefaultConfig())
client.addHostKeyVerifier(new PromiscuousVerifier())
client.connect("127.0.0.1", sshd.firstMappedPort)
when:
client.authPublickey(USERNAME, "src/itest/resources/keyfiles/certificates/$keyName")
client.authPublickey("sshj", "src/itest/resources/keyfiles/certificates/$keyName")
then:
client.authenticated
@@ -73,43 +78,4 @@ class KeyWithCertificateSpec extends IntegrationBaseSpec {
"id_ed25519_384_rfc4716_signed_by_rsa",
]
}
@Unroll
def "accepting a signed host public key with type #hostKeyAlgo"() {
given:
File knownHosts = Files.createTempFile("known_hosts", "").toFile()
knownHosts.deleteOnExit()
and:
File caPubKey = new File("src/itest/resources/keyfiles/certificates/CA_rsa.pem.pub")
String knownHostsFileContents = "" +
"@cert-authority $SERVER_IP ${caPubKey.text}" +
"\n@cert-authority [$SERVER_IP]:$DOCKER_PORT ${caPubKey.text}"
knownHosts.write(knownHostsFileContents)
and:
def config = new DefaultConfig()
config.keyAlgorithms = config.keyAlgorithms.stream()
.filter { it.name == hostKeyAlgo }
.collect(Collectors.toList())
SSHClient sshClient = new SSHClient(config)
sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(knownHosts))
sshClient.connect(SERVER_IP, DOCKER_PORT)
when:
sshClient.authPassword("sshj", "ultrapassword")
then:
sshClient.authenticated
and:
knownHosts.getText() == knownHostsFileContents
where:
hostKeyAlgo << [
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ssh-ed25519-cert-v01@openssh.com",
"ssh-rsa-cert-v01@openssh.com",
]
}
}

View File

@@ -15,18 +15,25 @@
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.DefaultConfig
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class RsaSignatureClientKeySpec extends IntegrationBaseSpec {
class RsaSignatureClientKeySpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "should correctly connect using publickey auth with RSA key with signature"() {
given:
def client = getConnectedClient(new DefaultConfig())
def client = sshd.getConnectedClient()
when:
client.authPublickey(USERNAME, "src/itest/resources/keyfiles/id_rsa2")
client.authPublickey(IntegrationTestUtil.USERNAME, "src/itest/resources/keyfiles/id_rsa2")
then:
client.authenticated

View File

@@ -15,22 +15,29 @@
*/
package com.hierynomus.sshj.signature
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import com.hierynomus.sshj.key.KeyAlgorithms
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class SignatureSpec extends IntegrationBaseSpec {
class SignatureSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "should correctly connect with #sig Signature"() {
given:
def cfg = new DefaultConfig()
cfg.setKeyAlgorithms(Collections.singletonList(sigFactory))
def client = getConnectedClient(cfg)
def client = sshd.getConnectedClient(cfg)
when:
client.authPublickey(USERNAME, KEYFILE)
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
then:
client.authenticated

View File

@@ -15,21 +15,28 @@
*/
package com.hierynomus.sshj.transport.cipher
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class CipherSpec extends IntegrationBaseSpec {
class CipherSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "should correctly connect with #cipher Cipher"() {
given:
def cfg = new DefaultConfig()
cfg.setCipherFactories(cipherFactory)
def client = getConnectedClient(cfg)
def client = sshd.getConnectedClient(cfg)
when:
client.authPublickey(USERNAME, KEYFILE)
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
then:
client.authenticated

View File

@@ -15,29 +15,32 @@
*/
package com.hierynomus.sshj.transport.kex
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.transport.mac.Macs
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.transport.kex.Curve25519DH
import net.schmizz.sshj.transport.kex.Curve25519SHA256
import net.schmizz.sshj.transport.kex.DH
import net.schmizz.sshj.transport.kex.DHGexSHA1
import net.schmizz.sshj.transport.kex.DHGexSHA256
import net.schmizz.sshj.transport.kex.ECDH
import net.schmizz.sshj.transport.kex.ECDHNistP
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class KexSpec extends IntegrationBaseSpec {
class KexSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "should correctly connect with #kex Key Exchange"() {
given:
def cfg = new DefaultConfig()
cfg.setKeyExchangeFactories(kexFactory)
def client = getConnectedClient(cfg)
def client = sshd.getConnectedClient(cfg)
when:
client.authPublickey(USERNAME, KEYFILE)
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
then:
client.authenticated

View File

@@ -15,21 +15,28 @@
*/
package com.hierynomus.sshj.transport.mac
import com.hierynomus.sshj.IntegrationBaseSpec
import com.hierynomus.sshj.IntegrationTestUtil
import com.hierynomus.sshj.SshdContainer
import net.schmizz.sshj.DefaultConfig
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
class MacSpec extends IntegrationBaseSpec {
class MacSpec extends Specification {
@Shared
@ClassRule
SshdContainer sshd
@Unroll
def "should correctly connect with #mac MAC"() {
given:
def cfg = new DefaultConfig()
cfg.setMACFactories(macFactory)
def client = getConnectedClient(cfg)
def client = sshd.getConnectedClient(cfg)
when:
client.authPublickey(USERNAME, KEYFILE)
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
then:
client.authenticated
@@ -47,10 +54,10 @@ class MacSpec extends IntegrationBaseSpec {
given:
def cfg = new DefaultConfig()
cfg.setMACFactories(macFactory)
def client = getConnectedClient(cfg)
def client = sshd.getConnectedClient(cfg)
when:
client.authPublickey(USERNAME, KEYFILE)
client.authPublickey(IntegrationTestUtil.USERNAME, IntegrationTestUtil.KEYFILE)
then:
client.authenticated

View File

@@ -0,0 +1,3 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfrL8SxDyrkNlsJdAmc7Z0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAICYfPGSYFOHuSzTJ67H0ynvKJDfgDmwPOj7iJaLGbIBiAAAAkLVqaDIfs+sPBNyy7ytdLnP/xH7Nt5FIXx3Upw6wKuMGdBzbFQLcvu60Le+SFP3uUfXE8TcHramXbH0n+UBMW6raCAKOkHUU1BtrKxPG1eKU/LBx3Bk5FxyKm7fo0XsCUmqSVK25EHOJfYq1QwIbWICkvQUNu+2Hg8/MQKoFJMentI+GqjdaG76f6Wf+aj9UwA==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,78 @@
/*
* 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.common;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
/**
* A {@link SocketFactory} that creates sockets using a {@link Proxy}.
*/
class ProxySocketFactory extends SocketFactory {
private Proxy proxy;
public ProxySocketFactory(Proxy proxy) {
this.proxy = proxy;
}
public ProxySocketFactory(Proxy.Type proxyType, InetSocketAddress proxyAddress) {
this(new Proxy(proxyType, proxyAddress));
}
@Override
public Socket createSocket() throws IOException {
return new Socket(proxy);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
Socket s = createSocket();
s.bind(new InetSocketAddress(localAddress, localPort));
s.connect(new InetSocketAddress(address, port));
return s;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket s = createSocket();
s.connect(new InetSocketAddress(host, port));
return s;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket s = createSocket();
s.connect(new InetSocketAddress(host, port));
return s;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
Socket s = createSocket();
s.bind(new InetSocketAddress(localHost, localPort));
s.connect(new InetSocketAddress(host, port));
return s;
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.common;
import java.net.InetSocketAddress;
public interface RemoteAddressProvider {
/**
* Get Remote Socket Address associated with transport connection
*
* @return Remote Socket Address or null when not connected
*/
InetSocketAddress getRemoteSocketAddress();
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.common;
import java.net.InetSocketAddress;
public class ThreadNameProvider {
private static final String DISCONNECTED = "DISCONNECTED";
/**
* Set Thread Name prefixed with sshj followed by class and remote address when connected
*
* @param thread Class of Thread being named
* @param remoteAddressProvider Remote Address Provider associated with Thread
*/
public static void setThreadName(final Thread thread, final RemoteAddressProvider remoteAddressProvider) {
final InetSocketAddress remoteSocketAddress = remoteAddressProvider.getRemoteSocketAddress();
final String address = remoteSocketAddress == null ? DISCONNECTED : remoteSocketAddress.toString();
final long started = System.currentTimeMillis();
final String threadName = String.format("sshj-%s-%s-%d", thread.getClass().getSimpleName(), address, started);
thread.setName(threadName);
}
}

View File

@@ -27,8 +27,6 @@ import java.util.List;
public class KeyAlgorithms {
public static List<String> SSH_RSA_SHA2_ALGORITHMS = Arrays.asList("rsa-sha2-512", "rsa-sha2-256");
public static Factory SSHRSA() { return new Factory("ssh-rsa", new SignatureRSA.FactorySSHRSA(), KeyType.RSA); }
public static Factory SSHRSACertV01() { return new Factory("ssh-rsa-cert-v01@openssh.com", new SignatureRSA.FactoryCERT(), KeyType.RSA_CERT); }
public static Factory RSASHA256() { return new Factory("rsa-sha2-256", new SignatureRSA.FactoryRSASHA256(), KeyType.RSA); }
@@ -61,6 +59,10 @@ public class KeyAlgorithms {
return algorithmName;
}
public KeyType getKeyType() {
return keyType;
}
@Override
public KeyAlgorithm create() {
return new BaseKeyAlgorithm(algorithmName, signatureFactory, keyType);

View File

@@ -30,7 +30,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.mindrot.jbcrypt.BCrypt;
import com.hierynomus.sshj.userauth.keyprovider.bcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Arrays;
/**
@@ -177,11 +177,11 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
Arrays.fill(charBuffer.array(), '\u0000');
Arrays.fill(byteBuffer.array(), (byte) 0);
}
byte[] keyiv = new byte[48];
byte[] keyiv = new byte[cipher.getIVSize()+ cipher.getBlockSize()];
new BCrypt().pbkdf(passphrase, opts.readBytes(), opts.readUInt32AsInt(), keyiv);
Arrays.fill(passphrase, (byte) 0);
byte[] key = Arrays.copyOfRange(keyiv, 0, 32);
byte[] iv = Arrays.copyOfRange(keyiv, 32, 48);
byte[] key = Arrays.copyOfRange(keyiv, 0, cipher.getBlockSize());
byte[] iv = Arrays.copyOfRange(keyiv, cipher.getBlockSize(), cipher.getIVSize() + cipher.getBlockSize());
cipher.init(Cipher.Mode.Decrypt, key, iv);
} else {
throw new IllegalStateException("No support for KDF '" + kdfName + "'.");
@@ -193,6 +193,8 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
return BlockCiphers.AES256CTR().create();
} else if (cipherName.equals(BlockCiphers.AES256CBC().getName())) {
return BlockCiphers.AES256CBC().create();
} else if (cipherName.equals(BlockCiphers.AES128CBC().getName())) {
return BlockCiphers.AES128CBC().create();
}
throw new IllegalStateException("Cipher '" + cipherName + "' not currently implemented for openssh-key-v1 format");
}
@@ -216,6 +218,9 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
while (line != null && !line.startsWith(BEGIN)) {
line = reader.readLine();
}
if (line == null) {
return false;
}
line = line.substring(BEGIN.length());
return line.startsWith(OPENSSH_PRIVATE_KEY);
}
@@ -245,13 +250,9 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
break;
case RSA:
BigInteger n = keyBuffer.readMPInt(); // Modulus
keyBuffer.readMPInt(); // Public Exponent
BigInteger d = keyBuffer.readMPInt(); // Private Exponent
keyBuffer.readMPInt(); // iqmp (q^-1 mod p)
keyBuffer.readMPInt(); // p (Prime 1)
keyBuffer.readMPInt(); // q (Prime 2)
kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(new RSAPrivateKeySpec(n, d)));
final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
final PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(rsaPrivateCrtKeySpec);
kp = new KeyPair(publicKey, privateKey);
break;
case ECDSA256:
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
@@ -284,6 +285,35 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
}
/**
* Read RSA Private CRT Key Spec according to OpenSSH sshkey_private_deserialize in sshkey.c
*
* @param buffer Buffer
* @return RSA Private CRT Key Specification
* @throws Buffer.BufferException Thrown on failure to read from buffer
*/
private RSAPrivateCrtKeySpec readRsaPrivateKeySpec(final PlainBuffer buffer) throws Buffer.BufferException {
final BigInteger modulus = buffer.readMPInt();
final BigInteger publicExponent = buffer.readMPInt();
final BigInteger privateExponent = buffer.readMPInt();
final BigInteger crtCoefficient = buffer.readMPInt(); // iqmp (q^-1 mod p)
final BigInteger primeP = buffer.readMPInt();
final BigInteger primeQ = buffer.readMPInt();
// Calculate Prime Exponent P and Prime Exponent Q according to RFC 8017 Section 3.2
final BigInteger primeExponentP = privateExponent.remainder(primeP.subtract(BigInteger.ONE));
final BigInteger primeExponentQ = privateExponent.remainder(primeQ.subtract(BigInteger.ONE));
return new RSAPrivateCrtKeySpec(
modulus,
publicExponent,
privateExponent,
primeP,
primeQ,
primeExponentP,
primeExponentQ,
crtCoefficient
);
}
}

View File

@@ -12,7 +12,7 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package org.mindrot.jbcrypt;
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
import java.io.UnsupportedEncodingException;
import java.security.DigestException;

View File

@@ -1,918 +0,0 @@
/* 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
);
}

View File

@@ -136,7 +136,7 @@ public class Promise<V, T extends Throwable> {
throws T {
final V value = tryRetrieve(timeout, unit);
if (value == null)
throw chainer.chain(new TimeoutException("Timeout expired"));
throw chainer.chain(new TimeoutException("Timeout expired: " + timeout + " " + unit));
else
return value;
}
@@ -176,6 +176,7 @@ public class Promise<V, T extends Throwable> {
}
return val;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw chainer.chain(ie);
} finally {
lock.unlock();

View File

@@ -24,7 +24,7 @@ final class Heartbeater
extends KeepAlive {
Heartbeater(ConnectionImpl conn) {
super(conn, "heartbeater");
super(conn, "sshj-Heartbeater");
}
@Override

View File

@@ -20,6 +20,8 @@ import net.schmizz.sshj.connection.ConnectionImpl;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import java.util.concurrent.TimeUnit;
public abstract class KeepAlive extends Thread {
protected final Logger log;
protected final ConnectionImpl conn;
@@ -33,40 +35,49 @@ public abstract class KeepAlive extends Thread {
setDaemon(true);
}
/**
* KeepAlive enabled based on KeepAlive interval
*
* @return Enabled when KeepInterval is greater than 0
*/
public boolean isEnabled() {
return keepAliveInterval > 0;
}
/**
* Get KeepAlive interval in seconds
*
* @return KeepAlive interval in seconds defaults to 0
*/
public synchronized int getKeepAliveInterval() {
return keepAliveInterval;
}
/**
* Set KeepAlive interval in seconds
*
* @param keepAliveInterval KeepAlive interval in seconds
*/
public synchronized void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
if (keepAliveInterval > 0 && getState() == State.NEW) {
start();
}
notify();
}
synchronized protected int getPositiveInterval()
throws InterruptedException {
while (keepAliveInterval <= 0) {
wait();
}
return keepAliveInterval;
}
@Override
public void run() {
log.debug("Starting {}, sending keep-alive every {} seconds", getClass().getSimpleName(), keepAliveInterval);
log.debug("{} Started with interval [{} seconds]", getClass().getSimpleName(), keepAliveInterval);
try {
while (!isInterrupted()) {
final int hi = getPositiveInterval();
final int interval = getKeepAliveInterval();
if (conn.getTransport().isRunning()) {
log.debug("Sending keep-alive since {} seconds elapsed", hi);
log.debug("{} Sending after interval [{} seconds]", getClass().getSimpleName(), interval);
doKeepAlive();
}
Thread.sleep(hi * 1000);
TimeUnit.SECONDS.sleep(interval);
}
} catch (InterruptedException e) {
// Interrupt signal may be catched when sleeping.
// this is almost certainly a planned interruption, but even so, no harm in setting the interrupt flag
Thread.currentThread().interrupt();
log.trace("{} Interrupted while sleeping", getClass().getSimpleName());
} 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.
@@ -74,9 +85,7 @@ public abstract class KeepAlive extends Thread {
conn.getTransport().die(e);
}
}
log.debug("Stopping {}", getClass().getSimpleName());
log.debug("{} Stopped", getClass().getSimpleName());
}
protected abstract void doKeepAlive() throws TransportException, ConnectionException;

View File

@@ -37,7 +37,7 @@ public class KeepAliveRunner extends KeepAlive {
new LinkedList<Promise<SSHPacket, ConnectionException>>();
KeepAliveRunner(ConnectionImpl conn) {
super(conn, "keep-alive");
super(conn, "sshj-KeepAliveRunner");
}
synchronized public int getMaxAliveCount() {

View File

@@ -26,6 +26,7 @@ import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.random.Random;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -188,4 +189,30 @@ public class ConfigImpl
public void setVerifyHostKeyCertificates(boolean value) {
verifyHostKeyCertificates = value;
}
/**
* Modern servers neglect the key algorithm ssh-rsa. OpenSSH 8.8 even dropped its support by default in favour
* of rsa-sha2-*. However, there are legacy servers like Apache SSHD that don't support the newer replacements
* for ssh-rsa.
*
* If ssh-rsa factory is in {@link #getKeyAlgorithms()}, this methods makes ssh-rsa key algorithm more preferred
* than any of rsa-sha2-*. Otherwise, nothing happens.
*/
public void prioritizeSshRsaKeyAlgorithm() {
List<Factory.Named<KeyAlgorithm>> keyAlgorithms = getKeyAlgorithms();
for (int sshRsaIndex = 0; sshRsaIndex < keyAlgorithms.size(); ++ sshRsaIndex) {
if ("ssh-rsa".equals(keyAlgorithms.get(sshRsaIndex).getName())) {
for (int i = 0; i < sshRsaIndex; ++i) {
final String algo = keyAlgorithms.get(i).getName();
if ("rsa-sha2-256".equals(algo) || "rsa-sha2-512".equals(algo)) {
keyAlgorithms = new ArrayList<>(keyAlgorithms);
keyAlgorithms.add(i, keyAlgorithms.remove(sshRsaIndex));
setKeyAlgorithms(keyAlgorithms);
break;
}
}
break;
}
}
}
}

View File

@@ -36,11 +36,9 @@ 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.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;
@@ -162,7 +160,6 @@ public class DefaultConfig
setFileKeyProviderFactories(
new OpenSSHKeyV1KeyFile.Factory(),
new PKCS8KeyFile.Factory(),
new PKCS5KeyFile.Factory(),
new OpenSSHKeyFile.Factory(),
new PuTTYKeyFile.Factory());
}

View File

@@ -15,6 +15,8 @@
*/
package net.schmizz.sshj;
import net.schmizz.keepalive.KeepAlive;
import com.hierynomus.sshj.common.ThreadNameProvider;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
@@ -55,6 +57,7 @@ import javax.security.auth.login.LoginContext;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.security.KeyPair;
@@ -140,7 +143,7 @@ public class SSHClient
super(DEFAULT_PORT);
loggerFactory = config.getLoggerFactory();
log = loggerFactory.getLogger(getClass());
this.trans = new TransportImpl(config, this);
this.trans = new TransportImpl(config);
this.auth = new UserAuthImpl(trans);
this.conn = new ConnectionImpl(trans, config.getKeepAliveProvider());
}
@@ -424,6 +427,7 @@ public class SSHClient
@Override
public void disconnect()
throws IOException {
conn.getKeepAlive().interrupt();
for (LocalPortForwarder forwarder : forwarders) {
try {
forwarder.close();
@@ -441,6 +445,16 @@ public class SSHClient
return conn;
}
/**
* Get Remote Socket Address from Transport
*
* @return Remote Socket Address or null when not connected
*/
@Override
public InetSocketAddress getRemoteSocketAddress() {
return trans.getRemoteSocketAddress();
}
/**
* Returns the character set used to communicate with the remote machine for certain strings (like paths).
*
@@ -537,7 +551,7 @@ public class SSHClient
* 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>PEM-encoded PKCS1</li>
* <li>Putty keyfile</li>
* <li>openssh-key-v1 (New OpenSSH keyfile format)</li>
* </ul>
@@ -791,7 +805,17 @@ public class SSHClient
throws IOException {
super.onConnect();
trans.init(getRemoteHostname(), getRemotePort(), getInputStream(), getOutputStream());
doKex();
final KeepAlive keepAliveThread = conn.getKeepAlive();
if (keepAliveThread.isEnabled()) {
ThreadNameProvider.setThreadName(conn.getKeepAlive(), trans);
keepAliveThread.start();
}
if (trans.isKeyExchangeRequired()) {
log.debug("Initiating Key Exchange for new connection");
doKex();
} else {
log.debug("Key Exchange already completed for new connection");
}
}
/**

View File

@@ -15,8 +15,6 @@
*/
package net.schmizz.sshj;
import com.hierynomus.sshj.backport.JavaVersion;
import com.hierynomus.sshj.backport.Jdk7HttpProxySocket;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.direct.DirectConnection;
@@ -26,7 +24,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
public abstract class SocketClient {
@@ -57,73 +54,6 @@ public abstract class SocketClient {
return new InetSocketAddress(hostname, port);
}
/**
* 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;
this.port = port;
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
socket = new Jdk7HttpProxySocket(proxy);
} else {
socket = new Socket(proxy);
}
socket.connect(makeInetSocketAddress(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.
* @param host The host address to connect to.
* @param port The port to connect to.
* @param proxy The proxy to connect via.
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
this.port = port;
if (JavaVersion.isJava7OrEarlier() && proxy.type() == Proxy.Type.HTTP) {
// Java7 and earlier have no support for HTTP Connect proxies, return our custom socket.
socket = new Jdk7HttpProxySocket(proxy);
} else {
socket = new Socket(proxy);
}
socket.connect(new InetSocketAddress(host, port), connectTimeout);
onConnect();
}
public void connect(String hostname) throws IOException {
connect(hostname, defaultPort);
}

View File

@@ -145,8 +145,14 @@ public class StreamCopier {
final double sizeKiB = count / 1024.0;
log.debug(String.format("%1$,.1f KiB transferred in %2$,.1f seconds (%3$,.2f KiB/s)", sizeKiB, timeSeconds, (sizeKiB / timeSeconds)));
if (length != -1 && read == -1)
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");
// Did we encounter EOF?
if (read == -1) {
// If InputStream was closed we should also close OutputStream
out.close();
if (length != -1)
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");
}
return count;
}

View File

@@ -304,6 +304,25 @@ public abstract class AbstractChannel
}
}
// Prevent CHANNEL_CLOSE to be sent between isOpen and a Transport.write call in the runnable, otherwise
// a disconnect with a "packet referred to nonexistent channel" message can occur.
//
// This particularly happens when the transport.Reader thread passes an eof from the server to the
// ChannelInputStream, the reading library-user thread returns, and closes the channel at the same time as the
// transport.Reader thread receives the subsequent CHANNEL_CLOSE from the server.
boolean whileOpen(TransportRunnable runnable) throws TransportException, ConnectionException {
openCloseLock.lock();
try {
if (isOpen()) {
runnable.run();
return true;
}
} finally {
openCloseLock.unlock();
}
return false;
}
private void gotChannelRequest(SSHPacket buf)
throws ConnectionException, TransportException {
final String reqType;
@@ -427,5 +446,8 @@ public abstract class AbstractChannel
+ rwin + " >";
}
public interface TransportRunnable {
void run() throws TransportException, ConnectionException;
}
}

View File

@@ -105,6 +105,7 @@ public final class ChannelInputStream
try {
buf.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw (IOException) new InterruptedIOException().initCause(e);
}
}

View File

@@ -22,6 +22,7 @@ import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* {@link OutputStream} for channels. Buffers data upto the remote window's maximum packet size. Data can also be
@@ -29,14 +30,14 @@ import java.io.OutputStream;
*/
public final class ChannelOutputStream extends OutputStream implements ErrorNotifiable {
private final Channel chan;
private final AbstractChannel chan;
private final Transport trans;
private final Window.Remote win;
private final DataBuffer buffer = new DataBuffer();
private final byte[] b = new byte[1];
private boolean closed;
private AtomicBoolean closed;
private SSHException error;
private final class DataBuffer {
@@ -46,6 +47,12 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
private final SSHPacket packet = new SSHPacket(Message.CHANNEL_DATA);
private final Buffer.PlainBuffer leftOvers = new Buffer.PlainBuffer();
private final AbstractChannel.TransportRunnable packetWriteRunnable = new AbstractChannel.TransportRunnable() {
@Override
public void run() throws TransportException {
trans.write(packet);
}
};
DataBuffer() {
headerOffset = packet.rpos();
@@ -98,8 +105,9 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
if (leftOverBytes > 0) {
leftOvers.putRawBytes(packet.array(), packet.wpos(), leftOverBytes);
}
trans.write(packet);
if (!chan.whileOpen(packetWriteRunnable)) {
throwStreamClosed();
}
win.consume(writeNow);
packet.rpos(headerOffset);
@@ -118,10 +126,11 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
}
public ChannelOutputStream(Channel chan, Transport trans, Window.Remote win) {
public ChannelOutputStream(AbstractChannel chan, Transport trans, Window.Remote win) {
this.chan = chan;
this.trans = trans;
this.win = win;
this.closed = new AtomicBoolean(false);
}
@Override
@@ -151,24 +160,26 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
private void checkClose() throws SSHException {
// Check whether either the Stream is closed, or the underlying channel is closed
if (closed || !chan.isOpen()) {
if (error != null)
if (closed.get() || !chan.isOpen()) {
if (error != null) {
throw error;
else
throw new ConnectionException("Stream closed");
} else {
throwStreamClosed();
}
}
}
@Override
public synchronized void close() throws IOException {
// Not closed yet, and underlying channel is open to flush the data to.
if (!closed && chan.isOpen()) {
try {
buffer.flush(false);
// trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
} finally {
closed = true;
}
if (!closed.getAndSet(true)) {
chan.whileOpen(new AbstractChannel.TransportRunnable() {
@Override
public void run() throws TransportException, ConnectionException {
buffer.flush(false);
trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
}
});
}
}
@@ -189,4 +200,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
return "< ChannelOutputStream for Channel #" + chan.getID() + " >";
}
private static void throwStreamClosed() throws ConnectionException {
throw new ConnectionException("Stream closed");
}
}

View File

@@ -93,6 +93,7 @@ public abstract class Window {
throw new ConnectionException("Timeout when trying to expand the window size");
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new ConnectionException(ie);
}
}

View File

@@ -15,10 +15,11 @@
*/
package net.schmizz.sshj.connection.channel.direct;
import com.hierynomus.sshj.common.RemoteAddressProvider;
import net.schmizz.sshj.common.SSHException;
/** A factory interface for creating SSH {@link Session session channels}. */
public interface SessionFactory {
public interface SessionFactory extends RemoteAddressProvider {
/**
* Opens a {@code session} channel. The returned {@link Session} instance allows {@link Session#exec(String)
@@ -27,7 +28,7 @@ public interface SessionFactory {
*
* @return the opened {@code session} channel
*
* @throws SSHException
* @throws SSHException Thrown on session initialization failures
* @see Session
*/
Session startSession()

View File

@@ -142,6 +142,10 @@ public class RemotePortForwarder
// Listen on all IPv4
return true;
}
if ("0.0.0.0".equals(address) && "0:0:0:0:0:0:0:0".equals(channelForward.address)) {
// Handle IPv4 requests on IPv6 channel forward
return true;
}
return false;
}

View File

@@ -41,7 +41,7 @@ public class PacketReader extends Thread {
this.engine = engine;
log = engine.getLoggerFactory().getLogger(getClass());
this.in = engine.getSubsystem().getInputStream();
setName("sftp reader");
setName("sshj-PacketReader");
setDaemon(true);
}

View File

@@ -23,6 +23,8 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
@@ -220,49 +222,104 @@ public class RemoteFile
public class ReadAheadRemoteFileInputStream
extends InputStream {
private class UnconfirmedRead {
private final long offset;
private final Promise<Response, SFTPException> promise;
private final int length;
private UnconfirmedRead(long offset, int length, Promise<Response, SFTPException> promise) {
this.offset = offset;
this.length = length;
this.promise = promise;
}
UnconfirmedRead(long offset, int length) throws IOException {
this(offset, length, RemoteFile.this.asyncRead(offset, length));
}
public long getOffset() {
return offset;
}
public Promise<Response, SFTPException> getPromise() {
return promise;
}
public int getLength() {
return length;
}
}
private final byte[] b = new byte[1];
private final int maxUnconfirmedReads;
private final Queue<Promise<Response, SFTPException>> unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
private final Queue<Long> unconfirmedReadOffsets = new LinkedList<Long>();
private final long readAheadLimit;
private final Deque<UnconfirmedRead> unconfirmedReads = new ArrayDeque<>();
private long requestOffset;
private long responseOffset;
private long currentOffset;
private int maxReadLength = Integer.MAX_VALUE;
private boolean eof;
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads) {
assert 0 <= maxUnconfirmedReads;
this.maxUnconfirmedReads = maxUnconfirmedReads;
this(maxUnconfirmedReads, 0L);
}
/**
*
* @param maxUnconfirmedReads Maximum number of unconfirmed requests to send
* @param fileOffset Initial offset in file to read from
*/
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset) {
this(maxUnconfirmedReads, fileOffset, -1L);
}
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset) {
/**
*
* @param maxUnconfirmedReads Maximum number of unconfirmed requests to send
* @param fileOffset Initial offset in file to read from
* @param readAheadLimit Read ahead is disabled after this limit has been reached
*/
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset, long readAheadLimit) {
assert 0 <= maxUnconfirmedReads;
assert 0 <= fileOffset;
this.maxUnconfirmedReads = maxUnconfirmedReads;
this.requestOffset = this.responseOffset = fileOffset;
this.currentOffset = fileOffset;
this.readAheadLimit = readAheadLimit > 0 ? fileOffset + readAheadLimit : Long.MAX_VALUE;
}
private ByteArrayInputStream pending = new ByteArrayInputStream(new byte[0]);
private boolean retrieveUnconfirmedRead(boolean blocking) throws IOException {
if (unconfirmedReads.size() <= 0) {
final UnconfirmedRead unconfirmedRead = unconfirmedReads.peek();
if (unconfirmedRead == null || !blocking && !unconfirmedRead.getPromise().isDelivered()) {
return false;
}
unconfirmedReads.remove(unconfirmedRead);
if (!blocking && !unconfirmedReads.peek().isDelivered()) {
return false;
}
unconfirmedReadOffsets.remove();
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
final Response res = unconfirmedRead.promise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
switch (res.getType()) {
case DATA:
int recvLen = res.readUInt32AsInt();
responseOffset += recvLen;
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
if (unconfirmedRead.offset == currentOffset) {
currentOffset += recvLen;
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
if (recvLen < unconfirmedRead.length) {
// The server returned a packet smaller than the client had requested.
// It can be caused by at least one of the following:
// * The file has been read fully. Then, few futile read requests can be sent during
// the next read(), but the file will be downloaded correctly anyway.
// * The server shapes the request length. Then, the read window will be adjusted,
// and all further read-ahead requests won't be shaped.
// * The file on the server is not a regular file, it is something like fifo.
// Then, the window will shrink, and the client will start reading the file slower than it
// hypothetically can. It must be a rare case, and it is not worth implementing a sort of
// congestion control algorithm here.
maxReadLength = recvLen;
unconfirmedReads.clear();
}
}
break;
case STATUS:
@@ -290,40 +347,31 @@ public class RemoteFile
// we also need to go here for len <= 0, because pending may be at
// EOF in which case it would return -1 instead of 0
long requestOffset;
if (unconfirmedReads.isEmpty()) {
requestOffset = currentOffset;
}
else {
final UnconfirmedRead lastRequest = unconfirmedReads.getLast();
requestOffset = lastRequest.offset + lastRequest.length;
}
while (unconfirmedReads.size() <= maxUnconfirmedReads) {
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
int reqLen = Math.max(1024, len); // don't be shy!
unconfirmedReads.add(RemoteFile.this.asyncRead(requestOffset, reqLen));
unconfirmedReadOffsets.add(requestOffset);
int reqLen = Math.min(Math.max(1024, len), maxReadLength);
if (readAheadLimit > requestOffset) {
long remaining = readAheadLimit - requestOffset;
if (reqLen > remaining) {
reqLen = (int) remaining;
}
}
unconfirmedReads.add(new UnconfirmedRead(requestOffset, reqLen));
requestOffset += reqLen;
if (requestOffset >= readAheadLimit) {
break;
}
}
long nextOffset = unconfirmedReadOffsets.peek();
if (responseOffset != nextOffset) {
// the server could not give us all the data we needed, so
// we try to fill the gap synchronously
assert responseOffset < nextOffset;
assert 0 < (nextOffset - responseOffset);
assert (nextOffset - responseOffset) <= Integer.MAX_VALUE;
byte[] buf = new byte[(int) (nextOffset - responseOffset)];
int recvLen = RemoteFile.this.read(responseOffset, buf, 0, buf.length);
if (recvLen < 0) {
eof = true;
return -1;
}
if (0 == recvLen) {
// avoid infinite loops
throw new SFTPException("Unexpected response size (0), bailing out");
}
responseOffset += recvLen;
pending = new ByteArrayInputStream(buf, 0, recvLen);
} else if (!retrieveUnconfirmedRead(true /*blocking*/)) {
if (!retrieveUnconfirmedRead(true /*blocking*/)) {
// this may happen if we change prefetch strategy
// currently, we should never get here...

View File

@@ -13,14 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.backport;
package net.schmizz.sshj.sftp;
public class JavaVersion {
public static boolean isJava7OrEarlier() {
String property = System.getProperty("java.specification.version");
float diff = Float.parseFloat(property) - 1.7f;
public enum RenameFlags {
OVERWRITE(1),
ATOMIC(2),
NATIVE(4);
return diff < 0.01;
private final long flag;
RenameFlags(long flag) {
this.flag = flag;
}
public long longValue() {
return flag;
}
}

View File

@@ -115,9 +115,13 @@ public class SFTPClient
}
}
public void rename(String oldpath, String newpath)
public void rename(String oldpath, String newpath) throws IOException {
rename(oldpath, newpath, EnumSet.noneOf(RenameFlags.class));
}
public void rename(String oldpath, String newpath, Set<RenameFlags> renameFlags)
throws IOException {
engine.rename(oldpath, newpath);
engine.rename(oldpath, newpath, renameFlags);
}
public void rm(String filename)
@@ -228,21 +232,41 @@ public class SFTPClient
throws IOException {
xfer.download(source, dest);
}
public void get(String source, String dest, long byteOffset)
throws IOException {
xfer.download(source, dest, byteOffset);
}
public void put(String source, String dest)
throws IOException {
xfer.upload(source, dest);
}
public void put(String source, String dest, long byteOffset)
throws IOException {
xfer.upload(source, dest, byteOffset);
}
public void get(String source, LocalDestFile dest)
throws IOException {
xfer.download(source, dest);
}
public void get(String source, LocalDestFile dest, long byteOffset)
throws IOException {
xfer.download(source, dest, byteOffset);
}
public void put(LocalSourceFile source, String dest)
throws IOException {
xfer.upload(source, dest);
}
public void put(LocalSourceFile source, String dest, long byteOffset)
throws IOException {
xfer.upload(source, dest, byteOffset);
}
@Override
public void close()

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.sftp;
import com.hierynomus.sshj.common.ThreadNameProvider;
import net.schmizz.concurrent.Promise;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.LoggerFactory;
@@ -68,6 +69,7 @@ public class SFTPEngine
sub = session.startSubsystem("sftp");
out = sub.getOutputStream();
reader = new PacketReader(this);
ThreadNameProvider.setThreadName(reader, ssh);
pathHelper = new PathHelper(new PathHelper.Canonicalizer() {
@Override
public String canonicalize(String path)
@@ -230,13 +232,22 @@ public class SFTPEngine
return stat(PacketType.LSTAT, path);
}
public void rename(String oldPath, String newPath)
public void rename(String oldPath, String newPath, Set<RenameFlags> flags)
throws IOException {
if (operativeVersion < 1)
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
doRequest(
newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset())
).ensureStatusPacketIsOK();
final Request request = newRequest(PacketType.RENAME).putString(oldPath, sub.getRemoteCharset()).putString(newPath, sub.getRemoteCharset());
// SFTP Version 5 introduced rename flags according to Section 6.5 of the specification
if (operativeVersion >= 5) {
long renameFlagMask = 0L;
for (RenameFlags flag : flags) {
renameFlagMask = renameFlagMask | flag.longValue();
}
request.putUInt32(renameFlagMask);
}
doRequest(request).ensureStatusPacketIsOK();
}
public String canonicalize(String path)

View File

@@ -50,25 +50,47 @@ public class SFTPFileTransfer
@Override
public void upload(String source, String dest)
throws IOException {
upload(new FileSystemFile(source), dest);
upload(source, dest, 0);
}
@Override
public void upload(String source, String dest, long byteOffset)
throws IOException {
upload(new FileSystemFile(source), dest, byteOffset);
}
@Override
public void download(String source, String dest)
throws IOException {
download(source, new FileSystemFile(dest));
download(source, dest, 0);
}
@Override
public void download(String source, String dest, long byteOffset)
throws IOException {
download(source, new FileSystemFile(dest), byteOffset);
}
@Override
public void upload(LocalSourceFile localFile, String remotePath) throws IOException {
new Uploader(localFile, remotePath).upload(getTransferListener());
upload(localFile, remotePath, 0);
}
@Override
public void upload(LocalSourceFile localFile, String remotePath, long byteOffset) throws IOException {
new Uploader(localFile, remotePath).upload(getTransferListener(), byteOffset);
}
@Override
public void download(String source, LocalDestFile dest) throws IOException {
download(source, dest, 0);
}
@Override
public void download(String source, LocalDestFile dest, long byteOffset) throws IOException {
final PathComponents pathComponents = engine.getPathHelper().getComponents(source);
final FileAttributes attributes = engine.stat(source);
new Downloader().download(getTransferListener(), new RemoteResourceInfo(pathComponents, attributes), dest);
new Downloader().download(getTransferListener(), new RemoteResourceInfo(pathComponents, attributes), dest, byteOffset);
}
public void setUploadFilter(LocalFileFilter uploadFilter) {
@@ -92,7 +114,8 @@ public class SFTPFileTransfer
@SuppressWarnings("PMD.MissingBreakInSwitch")
private void download(final TransferListener listener,
final RemoteResourceInfo remote,
final LocalDestFile local) throws IOException {
final LocalDestFile local,
final long byteOffset) throws IOException {
final LocalDestFile adjustedFile;
switch (remote.getAttributes().getType()) {
case DIRECTORY:
@@ -101,8 +124,9 @@ public class SFTPFileTransfer
case UNKNOWN:
log.warn("Server did not supply information about the type of file at `{}` " +
"-- assuming it is a regular file!", remote.getPath());
// fall through
case REGULAR:
adjustedFile = downloadFile(listener.file(remote.getName(), remote.getAttributes().getSize()), remote, local);
adjustedFile = downloadFile(listener.file(remote.getName(), remote.getAttributes().getSize()), remote, local, byteOffset);
break;
default:
throw new IOException(remote + " is not a regular file or directory");
@@ -119,7 +143,7 @@ public class SFTPFileTransfer
final RemoteDirectory rd = engine.openDir(remote.getPath());
try {
for (RemoteResourceInfo rri : rd.scan(getDownloadFilter()))
download(listener, rri, adjusted.getChild(rri.getName()));
download(listener, rri, adjusted.getChild(rri.getName()), 0); // not supporting individual byte offsets for these files
} finally {
rd.close();
}
@@ -128,13 +152,15 @@ public class SFTPFileTransfer
private LocalDestFile downloadFile(final StreamCopier.Listener listener,
final RemoteResourceInfo remote,
final LocalDestFile local)
final LocalDestFile local,
final long byteOffset)
throws IOException {
final LocalDestFile adjusted = local.getTargetFile(remote.getName());
final RemoteFile rf = engine.open(remote.getPath());
try {
final RemoteFile.ReadAheadRemoteFileInputStream rfis = rf.new ReadAheadRemoteFileInputStream(16);
final OutputStream os = adjusted.getOutputStream();
log.debug("Attempting to download {} with offset={}", remote.getPath(), byteOffset);
final RemoteFile.ReadAheadRemoteFileInputStream rfis = rf.new ReadAheadRemoteFileInputStream(16, byteOffset);
final OutputStream os = adjusted.getOutputStream(byteOffset != 0);
try {
new StreamCopier(rfis, os, engine.getLoggerFactory())
.bufSize(engine.getSubsystem().getLocalMaxPacketSize())
@@ -173,17 +199,17 @@ public class SFTPFileTransfer
this.remote = remote;
}
private void upload(final TransferListener listener) throws IOException {
private void upload(final TransferListener listener, long byteOffset) throws IOException {
if (source.isDirectory()) {
makeDirIfNotExists(remote); // Ensure that the directory exists
uploadDir(listener.directory(source.getName()), source, remote);
setAttributes(source, remote);
} else if (source.isFile() && isDirectory(remote)) {
String adjustedRemote = engine.getPathHelper().adjustForParent(this.remote, source.getName());
uploadFile(listener.file(source.getName(), source.getLength()), source, adjustedRemote);
uploadFile(listener.file(source.getName(), source.getLength()), source, adjustedRemote, byteOffset);
setAttributes(source, adjustedRemote);
} else if (source.isFile()) {
uploadFile(listener.file(source.getName(), source.getLength()), source, remote);
uploadFile(listener.file(source.getName(), source.getLength()), source, remote, byteOffset);
setAttributes(source, remote);
} else {
throw new IOException(source + " is not a file or directory");
@@ -192,13 +218,14 @@ public class SFTPFileTransfer
private void upload(final TransferListener listener,
final LocalSourceFile local,
final String remote)
final String remote,
final long byteOffset)
throws IOException {
final String adjustedPath;
if (local.isDirectory()) {
adjustedPath = uploadDir(listener.directory(local.getName()), local, remote);
} else if (local.isFile()) {
adjustedPath = uploadFile(listener.file(local.getName(), local.getLength()), local, remote);
adjustedPath = uploadFile(listener.file(local.getName(), local.getLength()), local, remote, byteOffset);
} else {
throw new IOException(local + " is not a file or directory");
}
@@ -217,22 +244,34 @@ public class SFTPFileTransfer
throws IOException {
makeDirIfNotExists(remote);
for (LocalSourceFile f : local.getChildren(getUploadFilter()))
upload(listener, f, engine.getPathHelper().adjustForParent(remote, f.getName()));
upload(listener, f, engine.getPathHelper().adjustForParent(remote, f.getName()), 0); // not supporting individual byte offsets for these files
return remote;
}
private String uploadFile(final StreamCopier.Listener listener,
final LocalSourceFile local,
final String remote)
final String remote,
final long byteOffset)
throws IOException {
final String adjusted = prepareFile(local, remote);
final String adjusted = prepareFile(local, remote, byteOffset);
RemoteFile rf = null;
InputStream fis = null;
RemoteFile.RemoteFileOutputStream rfos = null;
EnumSet<OpenMode> modes;
try {
rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC));
if (byteOffset == 0) {
// Starting at the beginning, overwrite/create
modes = EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC);
} else {
// Starting at some offset, append
modes = EnumSet.of(OpenMode.WRITE, OpenMode.APPEND);
}
log.debug("Attempting to upload {} with offset={}", local.getName(), byteOffset);
rf = engine.open(adjusted, modes);
fis = local.getInputStream();
rfos = rf.new RemoteFileOutputStream(0, 16);
fis.skip(byteOffset);
rfos = rf.new RemoteFileOutputStream(byteOffset, 16);
new StreamCopier(fis, rfos, engine.getLoggerFactory())
.bufSize(engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead())
.keepFlushing(false)
@@ -294,7 +333,7 @@ public class SFTPFileTransfer
}
}
private String prepareFile(final LocalSourceFile local, final String remote)
private String prepareFile(final LocalSourceFile local, final String remote, final long byteOffset)
throws IOException {
final FileAttributes attrs;
try {
@@ -309,7 +348,7 @@ public class SFTPFileTransfer
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY) {
throw new IOException("Trying to upload file " + local.getName() + " to path " + remote + " but that is a directory");
} else {
log.debug("probeFile: {} is a {} file that will be replaced", remote, attrs.getMode().getType());
log.debug("probeFile: {} is a {} file that will be {}", remote, attrs.getMode().getType(), byteOffset > 0 ? "resumed" : "replaced");
return remote;
}
}

View File

@@ -117,9 +117,9 @@ public class StatefulSFTPClient
}
@Override
public void rename(String oldpath, String newpath)
public void rename(String oldpath, String newpath, Set<RenameFlags> renameFlags)
throws IOException {
super.rename(cwdify(oldpath), cwdify(newpath));
super.rename(cwdify(oldpath), cwdify(newpath), renameFlags);
}
@Override

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.signature;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import com.hierynomus.asn1.ASN1OutputStream;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.IOUtils;
public abstract class AbstractSignatureDSA extends AbstractSignature {
protected AbstractSignatureDSA(String algorithm, String signatureName) {
super(algorithm, signatureName);
}
/**
* Get ASN.1 Signature encoded using DER Sequence of integers
*
* @param r DSA Signature R
* @param s DSA Signature S
* @return ASN.1 Encoded Signature
* @throws IOException Thrown when failing to write signature integers
*/
@SuppressWarnings("rawtypes")
protected byte[] encodeAsnSignature(final BigInteger r, final BigInteger s) throws IOException {
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new com.hierynomus.asn1.types.primitive.ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ASN1OutputStream asn1OutputStream = new ASN1OutputStream(new DEREncoder(), baos);
try {
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
} finally {
IOUtils.closeQuietly(asn1OutputStream);
}
return baos.toByteArray();
}
}

View File

@@ -15,26 +15,19 @@
*/
package net.schmizz.sshj.signature;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* DSA {@link Signature}
*/
public class SignatureDSA
extends AbstractSignature {
extends AbstractSignatureDSA {
/**
* A named factory for DSA signature
@@ -90,32 +83,14 @@ public class SignatureDSA
public boolean verify(byte[] sig) {
try {
byte[] sigBlob = extractSig(sig, "ssh-dss");
return signature.verify(asnEncode(sigBlob));
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sigBlob, 0, 20));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sigBlob, 20, 40));
return signature.verify(encodeAsnSignature(r, s));
} 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 {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sigBlob, 0, 20));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sigBlob, 20, 40));
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new com.hierynomus.asn1.types.primitive.ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
com.hierynomus.asn1.ASN1OutputStream asn1OutputStream = new com.hierynomus.asn1.ASN1OutputStream(new DEREncoder(), baos);
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
return baos.toByteArray();
}
}

View File

@@ -15,9 +15,8 @@
*/
package net.schmizz.sshj.signature;
import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.encodingrules.der.DEREncoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import net.schmizz.sshj.common.Buffer;
@@ -26,15 +25,12 @@ import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.List;
/** ECDSA {@link Signature} */
public class SignatureECDSA extends AbstractSignature {
public class SignatureECDSA extends AbstractSignatureDSA {
/** A named factory for ECDSA-256 signature */
public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> {
@@ -91,7 +87,7 @@ public class SignatureECDSA extends AbstractSignature {
@Override
public byte[] encode(byte[] sig) {
ByteArrayInputStream bais = new ByteArrayInputStream(sig);
com.hierynomus.asn1.ASN1InputStream asn1InputStream = new com.hierynomus.asn1.ASN1InputStream(new DERDecoder(), bais);
ASN1InputStream asn1InputStream = new ASN1InputStream(new DERDecoder(), bais);
try {
ASN1Sequence sequence = asn1InputStream.readObject();
ASN1Integer r = (ASN1Integer) sequence.get(0);
@@ -110,35 +106,14 @@ public class SignatureECDSA extends AbstractSignature {
public boolean verify(byte[] sig) {
try {
byte[] sigBlob = extractSig(sig, keyTypeName);
return signature.verify(asnEncode(sigBlob));
Buffer.PlainBuffer sigbuf = new Buffer.PlainBuffer(sigBlob);
BigInteger r = sigbuf.readMPInt();
BigInteger s = sigbuf.readMPInt();
return signature.verify(encodeAsnSignature(r, s));
} 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);
BigInteger r = sigbuf.readMPInt();
BigInteger s = sigbuf.readMPInt();
List<ASN1Object> vector = new ArrayList<ASN1Object>();
vector.add(new ASN1Integer(r));
vector.add(new ASN1Integer(s));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
com.hierynomus.asn1.ASN1OutputStream asn1OutputStream = new com.hierynomus.asn1.ASN1OutputStream(new DEREncoder(), baos);
try {
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
} finally {
IOUtils.closeQuietly(asn1OutputStream);
}
return baos.toByteArray();
}
}

View File

@@ -170,11 +170,22 @@ final class KeyExchanger
private void sendKexInit()
throws TransportException {
log.debug("Sending SSH_MSG_KEXINIT");
clientProposal = new Proposal(transport.getConfig());
List<String> knownHostAlgs = findKnownHostAlgs(transport.getRemoteHost(), transport.getRemotePort());
clientProposal = new Proposal(transport.getConfig(), knownHostAlgs);
transport.write(clientProposal.getPacket());
kexInitSent.set();
}
private List<String> findKnownHostAlgs(String hostname, int port) {
for (HostKeyVerifier hkv : hostVerifiers) {
List<String> keyTypes = hkv.findExistingAlgorithms(hostname, port);
if (keyTypes != null && !keyTypes.isEmpty()) {
return keyTypes;
}
}
return Collections.emptyList();
}
private void sendNewKeys()
throws TransportException {
log.debug("Sending SSH_MSG_NEWKEYS");
@@ -232,7 +243,6 @@ final class KeyExchanger
negotiatedAlgs.getKeyExchangeAlgorithm());
transport.setHostKeyAlgorithm(Factory.Named.Util.create(transport.getConfig().getKeyAlgorithms(),
negotiatedAlgs.getSignatureAlgorithm()));
transport.setRSASHA2Support(negotiatedAlgs.getRSASHA2Support());
try {
kex.init(transport,

View File

@@ -26,10 +26,8 @@ public final class NegotiatedAlgorithms {
private final String c2sComp;
private final String s2cComp;
private final boolean rsaSHA2Support;
NegotiatedAlgorithms(String kex, String sig, String c2sCipher, String s2cCipher, String c2sMAC, String s2cMAC,
String c2sComp, String s2cComp, boolean rsaSHA2Support) {
String c2sComp, String s2cComp) {
this.kex = kex;
this.sig = sig;
this.c2sCipher = c2sCipher;
@@ -38,7 +36,6 @@ public final class NegotiatedAlgorithms {
this.s2cMAC = s2cMAC;
this.c2sComp = c2sComp;
this.s2cComp = s2cComp;
this.rsaSHA2Support = rsaSHA2Support;
}
public String getKeyExchangeAlgorithm() {
@@ -73,10 +70,6 @@ public final class NegotiatedAlgorithms {
return s2cComp;
}
public boolean getRSASHA2Support() {
return rsaSHA2Support;
}
@Override
public String toString() {
return ("[ " +
@@ -88,7 +81,6 @@ public final class NegotiatedAlgorithms {
"s2cMAC=" + s2cMAC + "; " +
"c2sComp=" + c2sComp + "; " +
"s2cComp=" + s2cComp + "; " +
"rsaSHA2Support=" + rsaSHA2Support +
" ]");
}

View File

@@ -15,7 +15,6 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.key.KeyAlgorithms;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.Factory;
@@ -38,9 +37,9 @@ class Proposal {
private final List<String> s2cComp;
private final SSHPacket packet;
public Proposal(Config config) {
public Proposal(Config config, List<String> knownHostAlgs) {
kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
sig = Factory.Named.Util.getNames(config.getKeyAlgorithms());
sig = filterKnownHostKeyAlgorithms(Factory.Named.Util.getNames(config.getKeyAlgorithms()), knownHostAlgs);
c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());
@@ -127,34 +126,45 @@ class Proposal {
public NegotiatedAlgorithms negotiate(Proposal other)
throws TransportException {
return new NegotiatedAlgorithms(
firstMatch("KeyExchangeAlgorithms",
this.getKeyExchangeAlgorithms(),
other.getKeyExchangeAlgorithms()),
firstMatch("HostKeyAlgorithms",
this.getHostKeyAlgorithms(),
other.getHostKeyAlgorithms()),
firstMatch("Client2ServerCipherAlgorithms",
this.getClient2ServerCipherAlgorithms(),
other.getClient2ServerCipherAlgorithms()),
firstMatch("Server2ClientCipherAlgorithms",
this.getServer2ClientCipherAlgorithms(),
other.getServer2ClientCipherAlgorithms()),
firstMatch("Client2ServerMACAlgorithms",
this.getClient2ServerMACAlgorithms(),
other.getClient2ServerMACAlgorithms()),
firstMatch("Server2ClientMACAlgorithms",
this.getServer2ClientMACAlgorithms(),
other.getServer2ClientMACAlgorithms()),
firstMatch("Client2ServerCompressionAlgorithms",
this.getClient2ServerCompressionAlgorithms(),
other.getClient2ServerCompressionAlgorithms()),
firstMatch("Server2ClientCompressionAlgorithms",
this.getServer2ClientCompressionAlgorithms(),
other.getServer2ClientCompressionAlgorithms()),
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS)
firstMatch("KeyExchangeAlgorithms", this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
firstMatch("HostKeyAlgorithms", this.getHostKeyAlgorithms(), other.getHostKeyAlgorithms()),
firstMatch("Client2ServerCipherAlgorithms", this.getClient2ServerCipherAlgorithms(),
other.getClient2ServerCipherAlgorithms()),
firstMatch("Server2ClientCipherAlgorithms", this.getServer2ClientCipherAlgorithms(),
other.getServer2ClientCipherAlgorithms()),
firstMatch("Client2ServerMACAlgorithms", this.getClient2ServerMACAlgorithms(),
other.getClient2ServerMACAlgorithms()),
firstMatch("Server2ClientMACAlgorithms", this.getServer2ClientMACAlgorithms(),
other.getServer2ClientMACAlgorithms()),
firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
other.getClient2ServerCompressionAlgorithms()),
firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
other.getServer2ClientCompressionAlgorithms())
);
}
private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
if (knownHostKeyAlgorithms != null && !knownHostKeyAlgorithms.isEmpty()) {
List<String> preferredAlgorithms = new ArrayList<String>();
List<String> otherAlgorithms = new ArrayList<String>();
for (String configuredKeyAlgorithm : configuredKeyAlgorithms) {
if (knownHostKeyAlgorithms.contains(configuredKeyAlgorithm)) {
preferredAlgorithms.add(configuredKeyAlgorithm);
} else {
otherAlgorithms.add(configuredKeyAlgorithm);
}
}
preferredAlgorithms.addAll(otherAlgorithms);
return preferredAlgorithms;
} else {
return configuredKeyAlgorithms;
}
}
private static String firstMatch(String ofWhat, List<String> a, List<String> b)
throws TransportException {
for (String aa : a) {

View File

@@ -29,7 +29,7 @@ public final class Reader
public Reader(TransportImpl trans) {
this.trans = trans;
log = trans.getConfig().getLoggerFactory().getLogger(getClass());
setName("reader");
setName("sshj-Reader");
setDaemon(true);
}

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.common.RemoteAddressProvider;
import com.hierynomus.sshj.key.KeyAlgorithm;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.Service;
@@ -27,11 +28,12 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;
/** Transport layer of the SSH protocol. */
public interface Transport
extends SSHPacketHandler {
extends SSHPacketHandler, RemoteAddressProvider {
/**
* Sets the host information and the streams to be used by this transport. Identification information is exchanged
@@ -69,6 +71,13 @@ public interface Transport
void doKex()
throws TransportException;
/**
* Is Key Exchange required based on current transport status
*
* @return Key Exchange required status
*/
boolean isKeyExchangeRequired();
/** @return the version string used by this client to identify itself to an SSH server, e.g. "SSHJ_3_0" */
String getClientVersion();
@@ -85,22 +94,6 @@ public interface Transport
*/
void setTimeoutMs(int timeout);
/**
* @return the interval in seconds at which a heartbeat message is sent to the server
* @deprecated Moved to {@link net.schmizz.keepalive.KeepAlive#getKeepAliveInterval()}. This is accessible through the {@link net.schmizz.sshj.connection.Connection}.
* Scheduled to be removed in 0.12.0
*/
@Deprecated
int getHeartbeatInterval();
/**
* @param interval the interval in seconds, {@code 0} means no heartbeat
* @deprecated Moved to {@link net.schmizz.keepalive.KeepAlive#getKeepAliveInterval()}. This is accessible through the {@link net.schmizz.sshj.connection.Connection}.
* Scheduled to be removed in 0.12.0
*/
@Deprecated
void setHeartbeatInterval(int interval);
/** @return the hostname to which this transport is connected. */
String getRemoteHost();
@@ -224,7 +217,7 @@ public interface Transport
/**
* Specify a {@code listener} that will be notified upon disconnection.
*
* @param listener
* @param listener Disconnect Listener to be configured
*/
void setDisconnectListener(DisconnectListener listener);
@@ -239,5 +232,5 @@ public interface Transport
void die(Exception e);
KeyAlgorithm getHostKeyAlgorithm();
KeyAlgorithm getClientKeyAlgorithm(KeyType keyType) throws TransportException;
List<KeyAlgorithm> getClientKeyAlgorithms(KeyType keyType) throws TransportException;
}

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.common.ThreadNameProvider;
import com.hierynomus.sshj.key.KeyAlgorithm;
import com.hierynomus.sshj.key.KeyAlgorithms;
import com.hierynomus.sshj.transport.IdentificationStringParser;
@@ -22,7 +23,6 @@ import net.schmizz.concurrent.ErrorDeliveryUtil;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.Service;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
@@ -32,6 +32,8 @@ import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@@ -80,20 +82,12 @@ public final class TransportImpl
private final Reader reader;
/**
* @deprecated Moved to {@link net.schmizz.sshj.SSHClient}
*/
@Deprecated
private final SSHClient sshClient;
private final Encoder encoder;
private final Decoder decoder;
private KeyAlgorithm hostKeyAlgorithm;
private boolean rsaSHA2Support;
private final Event<TransportException> serviceAccept;
private final Event<TransportException> close;
@@ -136,8 +130,8 @@ public final class TransportImpl
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.serviceAccept = new Event<>("service accept", TransportException.chainer, loggerFactory);
this.close = new Event<>("transport close", TransportException.chainer, loggerFactory);
this.nullService = new NullService(this);
this.service = nullService;
this.log = loggerFactory.getLogger(getClass());
@@ -147,29 +141,6 @@ public final class TransportImpl
this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this);
this.clientID = String.format("SSH-2.0-%s", config.getVersion());
this.sshClient = null;
}
/*
* Temporary constructor until we remove support for the set/get Heartbeat interval from transport.
* @deprecated To be removed in 0.12.0
*/
@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, 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
@@ -194,9 +165,20 @@ public final class TransportImpl
throw new TransportException(e);
}
ThreadNameProvider.setThreadName(reader, this);
reader.start();
}
/**
* Get Remote Socket Address using Connection Information
*
* @return Remote Socket Address or null when not connected
*/
@Override
public InetSocketAddress getRemoteSocketAddress() {
return connInfo == null ? null : new InetSocketAddress(getRemoteHost(), getRemotePort());
}
/**
* TransportImpl implements its own default DisconnectListener.
*/
@@ -240,7 +222,7 @@ public final class TransportImpl
*
* @param buffer The buffer to read from.
* @return empty string if full ident string has not yet been received
* @throws IOException
* @throws IOException Thrown when protocol version is not supported
*/
private String readIdentification(Buffer.PlainBuffer buffer)
throws IOException {
@@ -272,6 +254,16 @@ public final class TransportImpl
kexer.startKex(true);
}
/**
* Is Key Exchange required returns true when Key Exchange is not done and when Key Exchange is not ongoing
*
* @return Key Exchange required status
*/
@Override
public boolean isKeyExchangeRequired() {
return !kexer.isKexDone() && !kexer.isKexOngoing();
}
public boolean isKexDone() {
return kexer.isKexDone();
}
@@ -286,20 +278,6 @@ public final class TransportImpl
this.timeoutMs = timeoutMs;
}
@Override
@Deprecated
public int getHeartbeatInterval() {
log.warn("**Deprecated**: Please use: sshClient.getConnection().getKeepAlive().getKeepAliveInterval()");
return sshClient.getConnection().getKeepAlive().getKeepAliveInterval();
}
@Override
@Deprecated
public void setHeartbeatInterval(int interval) {
log.warn("**Deprecated**: Please use: sshClient.getConnection().getKeepAlive().setKeepAliveInterval()");
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(interval);
}
@Override
public String getRemoteHost() {
return connInfo.host;
@@ -587,7 +565,7 @@ 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
* @throws TransportException Thrown when key exchange is ongoing
*/
private void gotUnimplemented(SSHPacket packet)
throws SSHException {
@@ -669,21 +647,19 @@ public final class TransportImpl
return this.hostKeyAlgorithm;
}
public void setRSASHA2Support(boolean rsaSHA2Support) {
this.rsaSHA2Support = rsaSHA2Support;
}
@Override
public KeyAlgorithm getClientKeyAlgorithm(KeyType keyType) throws TransportException {
if (keyType != KeyType.RSA || !rsaSHA2Support) {
return Factory.Named.Util.create(getConfig().getKeyAlgorithms(), keyType.toString());
}
public List<KeyAlgorithm> getClientKeyAlgorithms(KeyType keyType) throws TransportException {
List<Factory.Named<KeyAlgorithm>> factories = getConfig().getKeyAlgorithms();
List<KeyAlgorithm> available = new ArrayList<>();
if (factories != null)
for (Factory.Named<KeyAlgorithm> f : factories)
if (f.getName().equals("ssh-rsa") || KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS.contains(f.getName()))
return f.create();
throw new TransportException("Cannot find an available KeyAlgorithm for type " + keyType);
if (
f instanceof KeyAlgorithms.Factory && ((KeyAlgorithms.Factory) f).getKeyType().equals(keyType)
|| !(f instanceof KeyAlgorithms.Factory) && f.getName().equals(keyType.toString())
)
available.add(f.create());
if (available.isEmpty())
throw new TransportException("Cannot find an available KeyAlgorithm for type " + keyType);
return available;
}
}

View File

@@ -15,18 +15,16 @@
*/
package net.schmizz.sshj.transport.compression;
import com.jcraft.jzlib.Deflater;
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.compression.Compression;
/** ZLib based Compression. */
public class ZlibCompression
implements Compression {
public class ZlibCompression implements Compression {
/** Named factory for the ZLib Compression. */
public static class Factory
@@ -52,19 +50,15 @@ public class ZlibCompression
@Override
public void init(Mode mode) {
try {
switch (mode) {
case DEFLATE:
deflater = new Deflater(JZlib.Z_DEFAULT_COMPRESSION);
break;
case INFLATE:
inflater = new Inflater();
break;
default:
assert false;
}
} catch (GZIPException gze) {
switch (mode) {
case DEFLATE:
deflater = new Deflater(Deflater.DEFAULT_COMPRESSION);
break;
case INFLATE:
inflater = new Inflater();
break;
default:
assert false;
}
}
@@ -75,43 +69,28 @@ public class ZlibCompression
@Override
public void compress(Buffer buffer) {
deflater.setNextIn(buffer.array());
deflater.setNextInIndex(buffer.rpos());
deflater.setAvailIn(buffer.available());
deflater.setInput(buffer.array(), buffer.rpos(), buffer.available());
buffer.wpos(buffer.rpos());
do {
deflater.setNextOut(tempBuf);
deflater.setNextOutIndex(0);
deflater.setAvailOut(BUF_SIZE);
final int status = deflater.deflate(JZlib.Z_PARTIAL_FLUSH);
if (status == JZlib.Z_OK) {
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - deflater.getAvailOut());
} else {
throw new SSHRuntimeException("compress: deflate returned " + status);
}
} while (deflater.getAvailOut() == 0);
final int len = deflater.deflate(tempBuf, 0, BUF_SIZE, Deflater.SYNC_FLUSH);
buffer.putRawBytes(tempBuf, 0, len);
} while (!deflater.needsInput());
}
@Override
public void uncompress(Buffer from, Buffer to)
throws TransportException {
inflater.setNextIn(from.array());
inflater.setNextInIndex(from.rpos());
inflater.setAvailIn(from.available());
inflater.setInput(from.array(), from.rpos(), from.available());
while (true) {
inflater.setNextOut(tempBuf);
inflater.setNextOutIndex(0);
inflater.setAvailOut(BUF_SIZE);
final int status = inflater.inflate(JZlib.Z_PARTIAL_FLUSH);
switch (status) {
case JZlib.Z_OK:
to.putRawBytes(tempBuf, 0, BUF_SIZE - inflater.getAvailOut());
break;
case JZlib.Z_BUF_ERROR:
try {
int len = inflater.inflate(tempBuf, 0, BUF_SIZE);
if(len > 0) {
to.putRawBytes(tempBuf, 0, len);
} else {
return;
default:
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned " + status);
}
} catch (DataFormatException e) {
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned " + e.getMessage());
}
}
}

View File

@@ -15,50 +15,89 @@
*/
package net.schmizz.sshj.transport.kex;
import com.hierynomus.sshj.common.KeyAlgorithm;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.Random;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.jce.spec.ECParameterSpec;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* Key Exchange Method using Curve25519 as defined in RFC 8731
*/
public class Curve25519DH extends DHBase {
private byte[] secretKey;
private static final String ALGORITHM = "X25519";
private static final int ENCODED_ALGORITHM_ID_KEY_LENGTH = 44;
private static final int ALGORITHM_ID_LENGTH = 12;
private static final int KEY_LENGTH = 32;
private final byte[] algorithmId = new byte[ALGORITHM_ID_LENGTH];
public Curve25519DH() {
super(KeyAlgorithm.ECDSA, "ECDH");
}
@Override
void computeK(byte[] f) throws GeneralSecurityException {
byte[] k = new byte[32];
djb.Curve25519.curve(k, secretKey, f);
setK(new BigInteger(1, k));
}
@Override
public void init(AlgorithmParameterSpec params, Factory<Random> randomFactory) throws GeneralSecurityException {
Random random = randomFactory.create();
byte[] secretBytes = new byte[32];
random.fill(secretBytes);
byte[] publicBytes = new byte[32];
djb.Curve25519.keygen(publicBytes, null, secretBytes);
this.secretKey = Arrays.copyOf(secretBytes, secretBytes.length);
setE(publicBytes);
super(ALGORITHM, ALGORITHM);
}
/**
* TODO want to figure out why BouncyCastle does not work.
* @return The initialized curve25519 parameter spec
* Compute Shared Secret Key using Diffie-Hellman Curve25519 known as X25519
*
* @param peerPublicKey Peer public key bytes
* @throws GeneralSecurityException Thrown on key agreement failures
*/
public static AlgorithmParameterSpec getCurve25519Params() {
X9ECParameters ecP = CustomNamedCurves.getByName("curve25519");
return new ECParameterSpec(ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
@Override
void computeK(final byte[] peerPublicKey) throws GeneralSecurityException {
final KeyFactory keyFactory = SecurityUtils.getKeyFactory(ALGORITHM);
final KeySpec peerPublicKeySpec = getPeerPublicKeySpec(peerPublicKey);
final PublicKey generatedPeerPublicKey = keyFactory.generatePublic(peerPublicKeySpec);
agreement.doPhase(generatedPeerPublicKey, true);
final byte[] sharedSecretKey = agreement.generateSecret();
final BigInteger sharedSecretNumber = new BigInteger(BigInteger.ONE.signum(), sharedSecretKey);
setK(sharedSecretNumber);
}
/**
* Initialize Key Agreement with generated Public and Private Key Pair
*
* @param params Parameters not used
* @param randomFactory Random Factory not used
* @throws GeneralSecurityException Thrown on key agreement initialization failures
*/
@Override
public void init(final AlgorithmParameterSpec params, final Factory<Random> randomFactory) throws GeneralSecurityException {
final KeyPair keyPair = generator.generateKeyPair();
agreement.init(keyPair.getPrivate());
setPublicKey(keyPair.getPublic());
}
private void setPublicKey(final PublicKey publicKey) {
final byte[] encoded = publicKey.getEncoded();
// Encoded public key consists of the algorithm identifier and public key
if (encoded.length == ENCODED_ALGORITHM_ID_KEY_LENGTH) {
final byte[] publicKeyEncoded = new byte[KEY_LENGTH];
System.arraycopy(encoded, ALGORITHM_ID_LENGTH, publicKeyEncoded, 0, KEY_LENGTH);
setE(publicKeyEncoded);
// Save Algorithm Identifier byte array
System.arraycopy(encoded, 0, algorithmId, 0, ALGORITHM_ID_LENGTH);
} else {
throw new IllegalArgumentException(String.format("X25519 unsupported public key length [%d]", encoded.length));
}
}
private KeySpec getPeerPublicKeySpec(final byte[] peerPublicKey) {
final byte[] encodedKeySpec = new byte[ENCODED_ALGORITHM_ID_KEY_LENGTH];
System.arraycopy(algorithmId, 0, encodedKeySpec, 0, ALGORITHM_ID_LENGTH);
System.arraycopy(peerPublicKey, 0, encodedKeySpec, ALGORITHM_ID_LENGTH, KEY_LENGTH);
return new X509EncodedKeySpec(encodedKeySpec);
}
}

View File

@@ -56,6 +56,6 @@ public class Curve25519SHA256 extends AbstractDHG {
@Override
protected void initDH(DHBase dh) throws GeneralSecurityException {
dh.init(Curve25519DH.getCurve25519Params(), trans.getConfig().getRandomFactory());
dh.init(null, trans.getConfig().getRandomFactory());
}
}

View File

@@ -20,6 +20,8 @@ import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import net.schmizz.sshj.common.Base64;
@@ -74,6 +76,11 @@ public class FingerprintVerifier implements HostKeyVerifier {
public boolean verify(String h, int p, PublicKey k) {
return SecurityUtils.getFingerprint(k).equals(md5);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
});
} catch (SSHRuntimeException e) {
throw e;
@@ -120,8 +127,13 @@ public class FingerprintVerifier implements HostKeyVerifier {
return Arrays.equals(fingerprintData, digestData);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
@Override
public String toString() {
return "FingerprintVerifier{digestAlgorithm='" + digestAlgorithm + "'}";
}
}
}

View File

@@ -16,6 +16,7 @@
package net.schmizz.sshj.transport.verification;
import java.security.PublicKey;
import java.util.List;
/** Host key verification interface. */
public interface HostKeyVerifier {
@@ -35,4 +36,12 @@ public interface HostKeyVerifier {
*/
boolean verify(String hostname, int port, PublicKey key);
/**
* It is necessary to connect with the type of algorithm that matches an existing know_host entry.
* This will allow a match when we later verify with the negotiated key {@code HostKeyVerifier.verify}
* @param hostname remote hostname
* @param port remote port
* @return existing key types or empty list if no keys known for hostname
*/
List<String> findExistingAlgorithms(String hostname, int port);
}

View File

@@ -90,6 +90,10 @@ public class OpenSSHKnownHosts
}
}
private String adjustHostname(final String hostname, final int port) {
String lowerHN = hostname.toLowerCase();
return (port != 22) ? "[" + lowerHN + "]:" + port : lowerHN;
}
public File getFile() {
return khFile;
@@ -103,7 +107,7 @@ public class OpenSSHKnownHosts
return false;
}
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
final String adjustedHostname = adjustHostname(hostname, port);
boolean foundApplicableHostEntry = false;
for (KnownHostEntry e : entries) {
@@ -127,6 +131,34 @@ public class OpenSSHKnownHosts
return hostKeyUnverifiableAction(adjustedHostname, key);
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
final String adjustedHostname = adjustHostname(hostname, port);
List<String> knownHostAlgorithms = new ArrayList<String>();
for (KnownHostEntry e : entries) {
try {
if (e.appliesTo(adjustedHostname)) {
final KeyType type = e.getType();
if (e instanceof HostEntry && ((HostEntry) e).marker == Marker.CA_CERT) {
// Only the CA key type is known, but the type of the host key is not.
// Adding all supported types for keys with certificates.
for (final KeyType candidate : KeyType.values()) {
if (candidate.getParent() != null) {
knownHostAlgorithms.add(candidate.toString());
}
}
}
else {
knownHostAlgorithms.add(type.toString());
}
}
} catch (IOException ioe) {
}
}
return knownHostAlgorithms;
}
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
return false;
}

View File

@@ -16,6 +16,8 @@
package net.schmizz.sshj.transport.verification;
import java.security.PublicKey;
import java.util.Collections;
import java.util.List;
public final class PromiscuousVerifier
implements HostKeyVerifier {
@@ -25,4 +27,9 @@ public final class PromiscuousVerifier
return true;
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
}

View File

@@ -16,10 +16,9 @@
package net.schmizz.sshj.userauth.keyprovider;
/**
* @version $Id:$
* Key File Formats
*/
public enum KeyFormat {
PKCS5,
PKCS8,
OpenSSH,
OpenSSHv1,

View File

@@ -27,9 +27,9 @@ public class KeyProviderUtil {
* <p/>
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
*
* @param location
* @param location File Path to key
* @return name of the key file format
* @throws java.io.IOException
* @throws java.io.IOException Thrown on file processing failures
*/
public static KeyFormat detectKeyFileFormat(File location)
throws IOException {
@@ -45,7 +45,7 @@ public class KeyProviderUtil {
* @param privateKey Private key stored in a string
* @param separatePubKey Is the public key stored separately from the private key
* @return name of the key file format
* @throws java.io.IOException
* @throws java.io.IOException Thrown on file processing failures
*/
public static KeyFormat detectKeyFileFormat(String privateKey, boolean separatePubKey)
throws IOException {
@@ -60,7 +60,7 @@ public class KeyProviderUtil {
* @param privateKey Private key accessible through a {@code Reader}
* @param separatePubKey Is the public key stored separately from the private key
* @return name of the key file format
* @throws java.io.IOException
* @throws java.io.IOException Thrown on file processing failures
*/
public static KeyFormat detectKeyFileFormat(Reader privateKey, boolean separatePubKey)
throws IOException {
@@ -94,10 +94,8 @@ public class KeyProviderUtil {
} else if (separatePubKey) {
// Can delay asking for password since have unencrypted pubkey
return KeyFormat.OpenSSH;
} else if (header.contains("BEGIN PRIVATE KEY") || header.contains("BEGIN ENCRYPTED PRIVATE KEY")) {
return KeyFormat.PKCS8;
} else {
return KeyFormat.PKCS5;
return KeyFormat.PKCS8;
}
} else if (header.startsWith("PuTTY-User-Key-File-")) {
return KeyFormat.PuTTY;

View File

@@ -1,272 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.userauth.keyprovider;
import com.hierynomus.sshj.common.KeyAlgorithm;
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.transport.cipher.*;
import net.schmizz.sshj.transport.digest.Digest;
import net.schmizz.sshj.transport.digest.MD5;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
/**
* Represents a PKCS5-encoded key file. This is the format typically used by OpenSSH, OpenSSL, Amazon, etc.
*/
public class PKCS5KeyFile extends BaseFileKeyProvider {
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
@Override
public FileKeyProvider create() {
return new PKCS5KeyFile();
}
@Override
public String getName() {
return "PKCS5";
}
}
/**
* Indicates a format issue with PKCS5 data
*/
public static class FormatException
extends IOException {
FormatException(String msg) {
super(msg);
}
}
/**
* Indicates a problem decrypting the data
*/
public static class DecryptException
extends IOException {
DecryptException(String msg) {
super(msg);
}
}
protected byte[] data;
protected KeyPair readKeyPair()
throws IOException {
BufferedReader reader = new BufferedReader(resource.getReader());
try {
String line = null;
Cipher cipher = new NoneCipher();
StringBuffer sb = new StringBuffer();
byte[] iv = new byte[0]; // salt
while ((line = reader.readLine()) != null) {
if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) {
int end = line.length() - 17;
if (end > 11) {
String s = line.substring(11, line.length() - 17);
if ("RSA".equals(s)) {
type = KeyType.RSA;
} else if ("DSA".equals(s)) {
type = KeyType.DSA;
} else if ("DSS".equals(s)) {
type = KeyType.DSA;
} else {
throw new FormatException("Unrecognized PKCS5 key type");
}
} else {
throw new FormatException("Bad header; possibly PKCS8 format?");
}
} else if (line.startsWith("-----END")) {
break;
} else if (type != null) {
if (line.startsWith("Proc-Type: ")) {
if (!"4,ENCRYPTED".equals(line.substring(11))) {
throw new FormatException("Unrecognized Proc-Type");
}
} else if (line.startsWith("DEK-Info: ")) {
int ptr = line.indexOf(",");
if (ptr == -1) {
throw new FormatException("Unrecognized DEK-Info");
} else {
String algorithm = line.substring(10, ptr);
if ("DES-EDE3-CBC".equals(algorithm)) {
cipher = BlockCiphers.TripleDESCBC().create();
} else if ("AES-128-CBC".equals(algorithm)) {
cipher = BlockCiphers.AES128CBC().create();
} else if ("AES-192-CBC".equals(algorithm)) {
cipher = BlockCiphers.AES192CBC().create();
} else if ("AES-256-CBC".equals(algorithm)) {
cipher = BlockCiphers.AES256CBC().create();
} else {
throw new FormatException("Not a supported algorithm: " + algorithm);
}
iv = Arrays.copyOfRange(ByteArrayUtils.parseHex(line.substring(ptr + 1)), 0, cipher.getIVSize());
}
} else if (line.length() > 0) {
sb.append(line);
}
}
}
if (type == null) {
throw new FormatException("PKCS5 header not found");
}
ASN1Data asn = new ASN1Data(data = decrypt(Base64.decode(sb.toString()), cipher, iv));
switch (type) {
case RSA: {
KeyFactory factory = KeyFactory.getInstance(KeyAlgorithm.RSA);
asn.readNext();
BigInteger modulus = asn.readNext();
BigInteger pubExp = asn.readNext();
BigInteger prvExp = asn.readNext();
PublicKey pubKey = factory.generatePublic(new RSAPublicKeySpec(modulus, pubExp));
PrivateKey prvKey = factory.generatePrivate(new RSAPrivateKeySpec(modulus, prvExp));
return new KeyPair(pubKey, prvKey);
}
case DSA: {
KeyFactory factory = KeyFactory.getInstance(KeyAlgorithm.DSA);
asn.readNext();
BigInteger p = asn.readNext();
BigInteger q = asn.readNext();
BigInteger g = asn.readNext();
BigInteger pub = asn.readNext();
BigInteger prv = asn.readNext();
PublicKey pubKey = factory.generatePublic(new DSAPublicKeySpec(pub, p, q, g));
PrivateKey prvKey = factory.generatePrivate(new DSAPrivateKeySpec(prv, p, q, g));
return new KeyPair(pubKey, prvKey);
}
default:
throw new IOException("Unrecognized PKCS5 key type: " + type);
}
} catch (NoSuchAlgorithmException e) {
throw new IOException(e);
} catch (InvalidKeySpecException e) {
throw new IOException(e);
} finally {
reader.close();
}
}
@Override
public String toString() {
return "PKCS5KeyFile{resource=" + resource + "}";
}
private byte[] getPassphraseBytes() {
CharBuffer cb = CharBuffer.wrap(pwdf.reqPassword(resource));
ByteBuffer bb = IOUtils.UTF8.encode(cb);
byte[] result = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());
Arrays.fill(cb.array(), '\u0000');
Arrays.fill(bb.array(), (byte) 0);
return result;
}
private byte[] decrypt(byte[] raw, Cipher cipher, byte[] iv) throws DecryptException {
if (pwdf == null) {
return raw;
}
Digest md5 = new MD5();
int bsize = cipher.getBlockSize();
int hsize = md5.getBlockSize();
int hnlen = bsize / hsize * hsize + (bsize % hsize == 0 ? 0 : hsize);
do {
md5.init();
byte[] hn = new byte[hnlen];
byte[] tmp = null;
byte[] passphrase = getPassphraseBytes();
for (int i = 0; i + hsize <= hn.length; ) {
if (tmp != null) {
md5.update(tmp, 0, tmp.length);
}
md5.update(passphrase, 0, passphrase.length);
md5.update(iv, 0, iv.length > 8 ? 8 : iv.length);
tmp = md5.digest();
System.arraycopy(tmp, 0, hn, i, tmp.length);
i += tmp.length;
}
Arrays.fill(passphrase, (byte) 0);
byte[] key = Arrays.copyOfRange(hn, 0, bsize);
cipher.init(Cipher.Mode.Decrypt, key, iv);
Arrays.fill(key, (byte) 0);
byte[] decrypted = Arrays.copyOf(raw, raw.length);
cipher.update(decrypted, 0, decrypted.length);
if (ASN1Data.MAGIC == decrypted[0]) {
return decrypted;
}
} while (pwdf.shouldRetry(resource));
throw new DecryptException("Decryption failed");
}
class ASN1Data {
static final byte MAGIC = (byte) 0x30;
private byte[] buff;
private int index, length;
ASN1Data(byte[] buff) throws FormatException {
this.buff = buff;
index = 0;
if (buff[index++] != MAGIC) {
throw new FormatException("Not ASN.1 data");
}
length = buff[index++] & 0xff;
if ((length & 0x80) != 0) {
int counter = length & 0x7f;
length = 0;
while (counter-- > 0) {
length = (length << 8) + (buff[index++] & 0xff);
}
}
if ((index + length) > buff.length) {
throw new FormatException("Length mismatch: " + buff.length + " != " + (index + length));
}
}
BigInteger readNext() throws IOException {
if (index >= length) {
throw new EOFException();
} else if (buff[index++] != 0x02) {
throw new IOException("Not an int code: " + Integer.toHexString(0xff & buff[index]));
}
int length = buff[index++] & 0xff;
if ((length & 0x80) != 0) {
int counter = length & 0x7f;
length = 0;
while (counter-- > 0) {
length = (length << 8) + (buff[index++] & 0xff);
}
}
byte[] sequence = new byte[length];
System.arraycopy(buff, index, sequence, 0, length);
index += length;
return new BigInteger(sequence);
}
}
}

View File

@@ -27,14 +27,21 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.KeyPair;
/** Represents a PKCS8-encoded key file. This is the format used by (old-style) OpenSSH and OpenSSL. */
/**
* Key File implementation supporting PEM-encoded PKCS8 and PKCS1 formats with or without password-based encryption
*/
public class PKCS8KeyFile extends BaseFileKeyProvider {
public static class Factory
@@ -53,8 +60,6 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected char[] passphrase; // for blanking out
protected KeyPairConverter<PrivateKeyInfo> privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter();
protected KeyPair readKeyPair()
@@ -74,22 +79,19 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
if (o instanceof PEMEncryptedKeyPair) {
final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o;
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
if (SecurityUtils.getSecurityProvider() != null) {
decryptorBuilder.setProvider(SecurityUtils.getSecurityProvider());
}
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
kp = pemConverter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorBuilder.build(passphrase)));
} finally {
PasswordUtils.blankOut(passphrase);
}
final PEMKeyPair pemKeyPair = readEncryptedKeyPair(encryptedKeyPair);
kp = pemConverter.getKeyPair(pemKeyPair);
} else if (o instanceof PEMKeyPair) {
kp = pemConverter.getKeyPair((PEMKeyPair) o);
} else if (o instanceof PrivateKeyInfo) {
final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) o;
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
kp = pemConverter.getKeyPair(pemKeyPair);
} else if (o instanceof PKCS8EncryptedPrivateKeyInfo) {
final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) o;
final PrivateKeyInfo privateKeyInfo = readEncryptedPrivateKeyInfo(encryptedPrivateKeyInfo);
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
kp = pemConverter.getKeyPair(pemKeyPair);
} else {
log.warn("Unexpected PKCS8 PEM Object [{}]", o);
}
@@ -114,4 +116,37 @@ public class PKCS8KeyFile extends BaseFileKeyProvider {
public String toString() {
return "PKCS8KeyFile{resource=" + resource + "}";
}
private PEMKeyPair readEncryptedKeyPair(final PEMEncryptedKeyPair encryptedKeyPair) throws IOException {
final JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
if (SecurityUtils.getSecurityProvider() != null) {
builder.setProvider(SecurityUtils.getSecurityProvider());
}
char[] passphrase = null;
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
return encryptedKeyPair.decryptKeyPair(builder.build(passphrase));
} finally {
PasswordUtils.blankOut(passphrase);
}
}
private PrivateKeyInfo readEncryptedPrivateKeyInfo(final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) throws EncryptionException {
final JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder();
if (SecurityUtils.getSecurityProvider() != null) {
builder.setProvider(SecurityUtils.getSecurityProvider());
}
char[] passphrase = null;
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
final InputDecryptorProvider inputDecryptorProvider = builder.build(passphrase);
return encryptedPrivateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
} catch (final OperatorCreationException e) {
throw new EncryptionException("Loading Password for Encrypted Private Key Failed", e);
} catch (final PKCSException e) {
throw new EncryptionException("Reading Encrypted Private Key Failed", e);
} finally {
PasswordUtils.blankOut(passphrase);
}
}
}

View File

@@ -29,6 +29,8 @@ import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex;
@@ -40,11 +42,15 @@ import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* <h2>Sample PuTTY file format</h2>
*
* <pre>
* PuTTY-User-Key-File-2: ssh-rsa
* Encryption: none
@@ -70,8 +76,7 @@ import java.util.Map;
*/
public class PuTTYKeyFile extends BaseFileKeyProvider {
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
public static class Factory implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
@Override
public FileKeyProvider create() {
@@ -84,38 +89,46 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
}
private Integer keyFileVersion;
private byte[] privateKey;
private byte[] publicKey;
private byte[] verifyHmac; // only used by v3 keys
/**
* Key type. Either "ssh-rsa" for RSA key, or "ssh-dss" for DSA key.
* Key type
*/
@Override
public KeyType getType() throws IOException {
return KeyType.fromString(headers.get("PuTTY-User-Key-File-2"));
String headerName = String.format("PuTTY-User-Key-File-%d", this.keyFileVersion);
return KeyType.fromString(headers.get(headerName));
}
public boolean isEncrypted() {
// Currently the only supported encryption types are "aes256-cbc" and "none".
return "aes256-cbc".equals(headers.get("Encryption"));
public boolean isEncrypted() throws IOException {
// Currently, the only supported encryption types are "aes256-cbc" and "none".
String encryption = headers.get("Encryption");
if ("none".equals(encryption)) {
return false;
}
if ("aes256-cbc".equals(encryption)) {
return true;
}
throw new IOException(String.format("Unsupported encryption: %s", encryption));
}
private Map<String, String> payload
= new HashMap<String, String>();
private Map<String, String> payload = new HashMap<String, String>();
/**
* For each line that looks like "Xyz: vvv", it will be stored in this map.
*/
private final Map<String, String> headers
= new HashMap<String, String>();
private final Map<String, String> headers = new HashMap<String, String>();
protected KeyPair readKeyPair() throws IOException {
this.parseKeyPair();
final Buffer.PlainBuffer publicKeyReader = new Buffer.PlainBuffer(publicKey);
final Buffer.PlainBuffer privateKeyReader = new Buffer.PlainBuffer(privateKey);
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
if (KeyType.RSA.equals(this.getType())) {
final KeyType keyType = this.getType();
publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
if (KeyType.RSA.equals(keyType)) {
// public key exponent
BigInteger e = publicKeyReader.readMPInt();
// modulus
@@ -131,15 +144,13 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(s.getMessage(), s);
}
try {
return new KeyPair(
factory.generatePublic(new RSAPublicKeySpec(n, e)),
factory.generatePrivate(new RSAPrivateKeySpec(n, d))
);
return new KeyPair(factory.generatePublic(new RSAPublicKeySpec(n, e)),
factory.generatePrivate(new RSAPrivateKeySpec(n, d)));
} catch (InvalidKeySpecException i) {
throw new IOException(i.getMessage(), i);
}
}
if (KeyType.DSA.equals(this.getType())) {
if (KeyType.DSA.equals(keyType)) {
BigInteger p = publicKeyReader.readMPInt();
BigInteger q = publicKeyReader.readMPInt();
BigInteger g = publicKeyReader.readMPInt();
@@ -155,22 +166,20 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(s.getMessage(), s);
}
try {
return new KeyPair(
factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g))
);
return new KeyPair(factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)));
} catch (InvalidKeySpecException e) {
throw new IOException(e.getMessage(), e);
}
}
if (KeyType.ED25519.equals(this.getType())) {
if (KeyType.ED25519.equals(keyType)) {
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519);
EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519);
return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec));
}
final String ecdsaCurve;
switch (this.getType()) {
switch (keyType) {
case ECDSA256:
ecdsaCurve = "P-256";
break;
@@ -187,12 +196,12 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
if (ecdsaCurve != null) {
BigInteger s = new BigInteger(1, privateKeyReader.readBytes());
X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve);
ECNamedCurveSpec ecCurveSpec =
new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(),
ecParams.getN());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
try {
PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
return new KeyPair(getType().readPubKeyFromBuffer(publicKeyReader), privateKey);
return new KeyPair(keyType.readPubKeyFromBuffer(publicKeyReader), privateKey);
} catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e);
}
@@ -201,6 +210,7 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
protected void parseKeyPair() throws IOException {
this.keyFileVersion = null;
BufferedReader r = new BufferedReader(resource.getReader());
// Parse the text into headers and payloads
try {
@@ -211,6 +221,9 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
if (idx > 0) {
headerName = line.substring(0, idx);
headers.put(headerName, line.substring(idx + 2));
if (headerName.startsWith("PuTTY-User-Key-File-")) {
this.keyFileVersion = Integer.parseInt(headerName.substring(20));
}
} else {
String s = payload.get(headerName);
if (s == null) {
@@ -226,6 +239,9 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} finally {
r.close();
}
if (this.keyFileVersion == null) {
throw new IOException("Invalid key file format: missing \"PuTTY-User-Key-File-?\" entry");
}
// Retrieve keys from payload
publicKey = Base64.decode(payload.get("Public-Lines"));
if (this.isEncrypted()) {
@@ -236,8 +252,14 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
passphrase = "".toCharArray();
}
try {
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase));
this.verify(new String(passphrase));
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), passphrase);
Mac mac;
if (this.keyFileVersion <= 2) {
mac = this.prepareVerifyMacV2(passphrase);
} else {
mac = this.prepareVerifyMacV3();
}
this.verify(mac);
} finally {
PasswordUtils.blankOut(passphrase);
}
@@ -247,77 +269,158 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
/**
* Converts a passphrase into a key, by following the convention that PuTTY uses.
* <p/>
* <p/>
* Converts a passphrase into a key, by following the convention that PuTTY
* uses. Only PuTTY v1/v2 key files
* <p><p/>
* This is used to decrypt the private key when it's encrypted.
*/
private byte[] toKey(final String passphrase) throws IOException {
private void initCipher(final char[] passphrase, Cipher cipher) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException {
// The field Key-Derivation has been introduced with Putty v3 key file format
// For v3 the algorithms are "Argon2i" "Argon2d" and "Argon2id"
String kdfAlgorithm = headers.get("Key-Derivation");
if (kdfAlgorithm != null) {
kdfAlgorithm = kdfAlgorithm.toLowerCase();
byte[] keyData = this.argon2(kdfAlgorithm, passphrase);
if (keyData == null) {
throw new IOException(String.format("Unsupported key derivation function: %s", kdfAlgorithm));
}
byte[] key = new byte[32];
byte[] iv = new byte[16];
byte[] tag = new byte[32]; // Hmac key
System.arraycopy(keyData, 0, key, 0, 32);
System.arraycopy(keyData, 32, iv, 0, 16);
System.arraycopy(keyData, 48, tag, 0, 32);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
new IvParameterSpec(iv));
verifyHmac = tag;
return;
}
// Key file format v1 + v2
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
// The encryption key is derived from the passphrase by means of a succession of SHA-1 hashes.
// The encryption key is derived from the passphrase by means of a succession of
// SHA-1 hashes.
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
// Sequence number 0
digest.update(new byte[]{0, 0, 0, 0});
digest.update(passphrase.getBytes());
digest.update(encodedPassphrase);
byte[] key1 = digest.digest();
// Sequence number 1
digest.update(new byte[]{0, 0, 0, 1});
digest.update(passphrase.getBytes());
digest.update(encodedPassphrase);
byte[] key2 = digest.digest();
byte[] r = new byte[32];
System.arraycopy(key1, 0, r, 0, 20);
System.arraycopy(key2, 0, r, 20, 12);
Arrays.fill(encodedPassphrase, (byte) 0);
byte[] expanded = new byte[32];
System.arraycopy(key1, 0, expanded, 0, 20);
System.arraycopy(key2, 0, expanded, 20, 12);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"),
new IvParameterSpec(new byte[16])); // initial vector=0
return r;
} catch (NoSuchAlgorithmException e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* Verify the MAC.
* Uses BouncyCastle Argon2 implementation
*/
private void verify(final String passphrase) throws IOException {
private byte[] argon2(String algorithm, final char[] passphrase) throws IOException {
int type;
if ("argon2i".equals(algorithm)) {
type = Argon2Parameters.ARGON2_i;
} else if ("argon2d".equals(algorithm)) {
type = Argon2Parameters.ARGON2_d;
} else if ("argon2id".equals(algorithm)) {
type = Argon2Parameters.ARGON2_id;
} else {
return null;
}
byte[] salt = Hex.decode(headers.get("Argon2-Salt"));
int iterations = Integer.parseInt(headers.get("Argon2-Passes"));
int memory = Integer.parseInt(headers.get("Argon2-Memory"));
int parallelism = Integer.parseInt(headers.get("Argon2-Parallelism"));
Argon2Parameters a2p = new Argon2Parameters.Builder(type)
.withVersion(Argon2Parameters.ARGON2_VERSION_13)
.withIterations(iterations)
.withMemoryAsKB(memory)
.withParallelism(parallelism)
.withSalt(salt).build();
Argon2BytesGenerator generator = new Argon2BytesGenerator();
generator.init(a2p);
byte[] output = new byte[80];
int bytes = generator.generateBytes(passphrase, output);
if (bytes != output.length) {
throw new IOException("Failed to generate key via Argon2");
}
return output;
}
/**
* Verify the MAC (only required for v1/v2 keys. v3 keys are automatically
* verified as part of the decryption process.
*/
private void verify(final Mac mac) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
final DataOutputStream data = new DataOutputStream(out);
// name of algorithm
String keyType = this.getType().toString();
data.writeInt(keyType.length());
data.writeBytes(keyType);
data.writeInt(headers.get("Encryption").length());
data.writeBytes(headers.get("Encryption"));
data.writeInt(headers.get("Comment").length());
data.writeBytes(headers.get("Comment"));
data.writeInt(publicKey.length);
data.write(publicKey);
data.writeInt(privateKey.length);
data.write(privateKey);
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
final String reference = headers.get("Private-MAC");
if (!encoded.equals(reference)) {
throw new IOException("Invalid passphrase");
}
}
private Mac prepareVerifyMacV2(final char[] passphrase) throws IOException {
// The key to the MAC is itself a SHA-1 hash of (v1/v2 key only):
try {
// The key to the MAC is itself a SHA-1 hash of:
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update("putty-private-key-file-mac-key".getBytes());
if (passphrase != null) {
digest.update(passphrase.getBytes());
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
digest.update(encodedPassphrase);
Arrays.fill(encodedPassphrase, (byte) 0);
}
final byte[] key = digest.digest();
final Mac mac = Mac.getInstance("HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IOException(e.getMessage(), e);
}
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final DataOutputStream data = new DataOutputStream(out);
// name of algorithm
data.writeInt(this.getType().toString().length());
data.writeBytes(this.getType().toString());
data.writeInt(headers.get("Encryption").length());
data.writeBytes(headers.get("Encryption"));
data.writeInt(headers.get("Comment").length());
data.writeBytes(headers.get("Comment"));
data.writeInt(publicKey.length);
data.write(publicKey);
data.writeInt(privateKey.length);
data.write(privateKey);
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
final String reference = headers.get("Private-MAC");
if (!encoded.equals(reference)) {
throw new IOException("Invalid passphrase");
}
} catch (GeneralSecurityException e) {
private Mac prepareVerifyMacV3() throws IOException {
// for v3 keys the hMac key is included in the Argon output
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(this.verifyHmac, 0, 32, mac.getAlgorithm()));
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IOException(e.getMessage(), e);
}
}
@@ -325,17 +428,21 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
/**
* Decrypt private key
*
* @param privateKey the SSH private key to be decrypted
* @param passphrase To decrypt
*/
private byte[] decrypt(final byte[] key, final String passphrase) throws IOException {
private byte[] decrypt(final byte[] privateKey, final char[] passphrase) throws IOException {
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
final byte[] expanded = this.toKey(passphrase);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"),
new IvParameterSpec(new byte[16])); // initial vector=0
return cipher.doFinal(key);
this.initCipher(passphrase, cipher);
return cipher.doFinal(privateKey);
} catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e);
}
}
public int getKeyFileVersion() {
return keyFileVersion;
}
}

View File

@@ -84,8 +84,7 @@ public class AuthGssApiWithMic
@Override
public GSSContext run() throws GSSException {
GSSName clientName = manager.createName(params.getUsername(), GSSName.NT_USER_NAME);
GSSCredential clientCreds = manager.createCredential(clientName, GSSContext.DEFAULT_LIFETIME, selectedOid, GSSCredential.INITIATE_ONLY);
GSSCredential clientCreds = manager.createCredential(GSSCredential.INITIATE_ONLY);
GSSName peerName = manager.createName("host@" + params.getTransport().getRemoteHost(), GSSName.NT_HOSTBASED_SERVICE);
GSSContext context = manager.createContext(peerName, selectedOid, clientCreds, GSSContext.DEFAULT_LIFETIME);

View File

@@ -27,17 +27,36 @@ import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.LinkedList;
import java.util.Queue;
public abstract class KeyedAuthMethod
extends AbstractAuthMethod {
protected final KeyProvider kProv;
private Queue<KeyAlgorithm> available;
public KeyedAuthMethod(String name, KeyProvider kProv) {
super(name);
this.kProv = kProv;
}
private KeyAlgorithm getPublicKeyAlgorithm(KeyType keyType) throws TransportException {
if (available == null) {
available = new LinkedList<>(params.getTransport().getClientKeyAlgorithms(keyType));
}
return available.peek();
}
@Override
public boolean shouldRetry() {
if (available != null) {
available.poll();
return !available.isEmpty();
}
return false;
}
protected SSHPacket putPubKey(SSHPacket reqBuf)
throws UserAuthException {
PublicKey key;
@@ -50,13 +69,16 @@ public abstract class KeyedAuthMethod
// public key as 2 strings: [ key type | key blob ]
KeyType keyType = KeyType.fromKey(key);
try {
KeyAlgorithm ka = params.getTransport().getClientKeyAlgorithm(keyType);
reqBuf.putString(ka.getKeyAlgorithm())
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
return reqBuf;
KeyAlgorithm ka = getPublicKeyAlgorithm(keyType);
if (ka != null) {
reqBuf.putString(ka.getKeyAlgorithm())
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
return reqBuf;
}
} catch (IOException ioe) {
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType);
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType, ioe);
}
throw new UserAuthException("No KeyAlgorithm configured for key " + keyType);
}
protected SSHPacket putSig(SSHPacket reqBuf)
@@ -71,7 +93,7 @@ public abstract class KeyedAuthMethod
final KeyType kt = KeyType.fromKey(key);
Signature signature;
try {
signature = params.getTransport().getClientKeyAlgorithm(kt).newSignature();
signature = getPublicKeyAlgorithm(kt).newSignature();
} catch (TransportException e) {
throw new UserAuthException("No KeyAlgorithm configured for key " + kt);
}

View File

@@ -27,7 +27,8 @@ import java.util.regex.Pattern;
public class PasswordResponseProvider
implements ChallengeResponseProvider {
public static final Pattern DEFAULT_PROMPT_PATTERN = Pattern.compile(".*[pP]assword:\\s?\\z", Pattern.DOTALL);
// FreeBSD prompt is "Password for user@host:"
public static final Pattern DEFAULT_PROMPT_PATTERN = Pattern.compile(".*[pP]assword(?: for .*)?:\\s?\\z", Pattern.DOTALL);
private static final char[] EMPTY_RESPONSE = new char[0];

View File

@@ -15,6 +15,9 @@
*/
package net.schmizz.sshj.userauth.password;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/** Static utility method and factories */
@@ -54,4 +57,17 @@ public class PasswordUtils {
};
}
/**
* Converts a password to a UTF-8 encoded byte array
*
* @param password
* @return
*/
public static byte[] toByteArray(char[] password) {
CharBuffer charBuffer = CharBuffer.wrap(password);
final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes, 0, bytes.length);
return bytes;
}
}

View File

@@ -71,7 +71,13 @@ public class FileSystemFile
@Override
public OutputStream getOutputStream()
throws IOException {
return new FileOutputStream(file);
return getOutputStream(false);
}
@Override
public OutputStream getOutputStream(boolean append)
throws IOException {
return new FileOutputStream(file, append);
}
@Override

View File

@@ -31,6 +31,19 @@ public interface FileTransfer {
void upload(String localPath, String remotePath)
throws IOException;
/**
* This is meant to delegate to {@link #upload(LocalSourceFile, String)} with the {@code localPath} wrapped as e.g.
* a {@link FileSystemFile}. Appends to existing if {@code byteOffset} &gt; 0.
*
* @param localPath
* @param remotePath
* @param byteOffset
*
* @throws IOException
*/
void upload(String localPath, String remotePath, long byteOffset)
throws IOException;
/**
* This is meant to delegate to {@link #download(String, LocalDestFile)} with the {@code localPath} wrapped as e.g.
* a {@link FileSystemFile}.
@@ -43,6 +56,19 @@ public interface FileTransfer {
void download(String remotePath, String localPath)
throws IOException;
/**
* This is meant to delegate to {@link #download(String, LocalDestFile)} with the {@code localPath} wrapped as e.g.
* a {@link FileSystemFile}. Appends to existing if {@code byteOffset} &gt; 0.
*
* @param localPath
* @param remotePath
* @param byteOffset
*
* @throws IOException
*/
void download(String remotePath, String localPath, long byteOffset)
throws IOException;
/**
* Upload {@code localFile} to {@code remotePath}.
*
@@ -54,6 +80,18 @@ public interface FileTransfer {
void upload(LocalSourceFile localFile, String remotePath)
throws IOException;
/**
* Upload {@code localFile} to {@code remotePath}. Appends to existing if {@code byteOffset} &gt; 0.
*
* @param localFile
* @param remotePath
* @param byteOffset
*
* @throws IOException
*/
void upload(LocalSourceFile localFile, String remotePath, long byteOffset)
throws IOException;
/**
* Download {@code remotePath} to {@code localFile}.
*
@@ -65,6 +103,18 @@ public interface FileTransfer {
void download(String remotePath, LocalDestFile localFile)
throws IOException;
/**
* Download {@code remotePath} to {@code localFile}. Appends to existing if {@code byteOffset} &gt; 0.
*
* @param localFile
* @param remotePath
* @param byteOffset
*
* @throws IOException
*/
void download(String remotePath, LocalDestFile localFile, long byteOffset)
throws IOException;
TransferListener getTransferListener();
void setTransferListener(TransferListener listener);

View File

@@ -20,8 +20,13 @@ import java.io.OutputStream;
public interface LocalDestFile {
long getLength();
OutputStream getOutputStream()
throws IOException;
OutputStream getOutputStream(boolean append)
throws IOException;
/** @return A child file/directory of this directory with given {@code name}. */
LocalDestFile getChild(String name);

View File

@@ -52,24 +52,49 @@ public class SCPFileTransfer
@Override
public void upload(String localPath, String remotePath)
throws IOException {
newSCPUploadClient().copy(new FileSystemFile(localPath), remotePath);
upload(localPath, remotePath, 0);
}
@Override
public void upload(String localFile, String remotePath, long byteOffset)
throws IOException {
upload(new FileSystemFile(localFile), remotePath, byteOffset);
}
@Override
public void download(String remotePath, String localPath)
throws IOException {
download(remotePath, new FileSystemFile(localPath));
download(remotePath, localPath, 0);
}
@Override
public void download(String remotePath, String localPath, long byteOffset) throws IOException {
download(remotePath, new FileSystemFile(localPath), byteOffset);
}
@Override
public void download(String remotePath, LocalDestFile localFile)
throws IOException {
download(remotePath, localFile, 0);
}
@Override
public void download(String remotePath, LocalDestFile localFile, long byteOffset)
throws IOException {
checkByteOffsetSupport(byteOffset);
newSCPDownloadClient().copy(remotePath, localFile);
}
@Override
public void upload(LocalSourceFile localFile, String remotePath)
throws IOException {
upload(localFile, remotePath, 0);
}
@Override
public void upload(LocalSourceFile localFile, String remotePath, long byteOffset)
throws IOException {
checkByteOffsetSupport(byteOffset);
newSCPUploadClient().copy(localFile, remotePath);
}
@@ -79,4 +104,11 @@ public class SCPFileTransfer
}
return this;
}
private void checkByteOffsetSupport(long byteOffset) throws IOException {
// TODO - implement byte offsets on SCP, if possible.
if (byteOffset > 0) {
throw new SCPException("Byte offset on SCP file transfers is not supported.");
}
}
}

View File

@@ -16,6 +16,7 @@
package com.hierynomus.sshj.userauth.keyprovider
import com.hierynomus.sshj.test.SshFixture
import net.schmizz.sshj.DefaultConfig
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.userauth.keyprovider.KeyFormat
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator
@@ -39,7 +40,14 @@ class FileKeyProviderSpec extends Specification {
@Unroll
def "should have #format FileKeyProvider enabled by default"() {
given:
SSHClient client = fixture.setupConnectedDefaultClient()
// `fixture` is backed by Apache SSHD server. Looks like it doesn't support rsa-sha2-512 public key signature.
// Changing the default config to prioritize the former default implementation of RSA signature.
def config = new DefaultConfig()
config.prioritizeSshRsaKeyAlgorithm()
and:
SSHClient client = fixture.setupClient(config)
fixture.connectClient(client)
when:
client.authPublickey("jeroen", keyfile)
@@ -48,11 +56,11 @@ class FileKeyProviderSpec extends Specification {
client.isAuthenticated()
cleanup:
client.disconnect()
client?.disconnect()
where:
format | keyfile
KeyFormat.PKCS5 | "src/test/resources/keyformats/pkcs5"
KeyFormat.PKCS8 | "src/test/resources/keyformats/pkcs8"
KeyFormat.OpenSSH | "src/test/resources/keyformats/openssh"
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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
import com.hierynomus.sshj.key.KeyAlgorithms
import spock.lang.Specification
class ConfigImplSpec extends Specification {
static def ECDSA = KeyAlgorithms.ECDSASHANistp521()
static def ED25519 = KeyAlgorithms.EdDSA25519()
static def RSA_SHA_256 = KeyAlgorithms.RSASHA256()
static def RSA_SHA_512 = KeyAlgorithms.RSASHA512()
static def SSH_RSA = KeyAlgorithms.SSHRSA()
def "prioritizeSshRsaKeyAlgorithm does nothing if there is no ssh-rsa"() {
given:
def config = new DefaultConfig()
config.keyAlgorithms = [RSA_SHA_512, ED25519]
when:
config.prioritizeSshRsaKeyAlgorithm()
then:
config.keyAlgorithms == [RSA_SHA_512, ED25519]
}
def "prioritizeSshRsaKeyAlgorithm does nothing if there is no rsa-sha2-any"() {
given:
def config = new DefaultConfig()
config.keyAlgorithms = [ED25519, SSH_RSA, ECDSA]
when:
config.prioritizeSshRsaKeyAlgorithm()
then:
config.keyAlgorithms == [ED25519, SSH_RSA, ECDSA]
}
def "prioritizeSshRsaKeyAlgorithm does nothing if ssh-rsa already has higher priority"() {
given:
def config = new DefaultConfig()
config.keyAlgorithms = [ED25519, SSH_RSA, RSA_SHA_512, ECDSA]
when:
config.prioritizeSshRsaKeyAlgorithm()
then:
config.keyAlgorithms == [ED25519, SSH_RSA, RSA_SHA_512, ECDSA]
}
def "prioritizeSshRsaKeyAlgorithm prioritizes ssh-rsa if there is one rsa-sha2-any is prioritized"() {
given:
def config = new DefaultConfig()
config.keyAlgorithms = [ED25519, RSA_SHA_512, ECDSA, SSH_RSA]
when:
config.prioritizeSshRsaKeyAlgorithm()
then:
config.keyAlgorithms == [ED25519, SSH_RSA, RSA_SHA_512, ECDSA]
}
def "prioritizeSshRsaKeyAlgorithm prioritizes ssh-rsa if there are two rsa-sha2-any is prioritized"() {
given:
def config = new DefaultConfig()
config.keyAlgorithms = [ED25519, RSA_SHA_512, ECDSA, RSA_SHA_256, SSH_RSA]
when:
config.prioritizeSshRsaKeyAlgorithm()
then:
config.keyAlgorithms == [ED25519, SSH_RSA, RSA_SHA_512, ECDSA, RSA_SHA_256]
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.connection.channel.forwarded;
import com.hierynomus.sshj.test.HttpServer;
import com.hierynomus.sshj.test.SshFixture;
import com.hierynomus.sshj.test.util.FileUtil;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder;
import net.schmizz.sshj.connection.channel.direct.Parameters;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class LocalPortForwarderTest {
private static final Logger log = LoggerFactory.getLogger(LocalPortForwarderTest.class);
@Rule
public SshFixture fixture = new SshFixture();
@Rule
public HttpServer httpServer = new HttpServer();
@Before
public void setUp() throws IOException {
fixture.getServer().setForwardingFilter(new AcceptAllForwardingFilter());
File file = httpServer.getDocRoot().newFile("index.html");
FileUtil.writeToFile(file, "<html><head/><body><h1>Hi!</h1></body></html>");
}
@Test
public void shouldHaveWorkingHttpServer() throws IOException {
// Just to check that we have a working http server...
assertThat(httpGet("127.0.0.1", 8080), equalTo(200));
}
@Test
public void shouldHaveHttpServerThatClosesConnectionAfterResponse() throws IOException {
// Just to check that the test server does close connections before we try through the forwarder...
httpGetAndAssertConnectionClosedByServer(8080);
}
@Test(timeout = 10_000)
public void shouldCloseConnectionWhenRemoteServerClosesConnection() throws IOException {
SSHClient sshClient = getFixtureClient();
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress("0.0.0.0", 12345));
LocalPortForwarder localPortForwarder = sshClient.newLocalPortForwarder(new Parameters("0.0.0.0", 12345, "localhost", 8080), serverSocket);
new Thread(() -> {
try {
localPortForwarder.listen();
} catch (IOException e) {
throw new RuntimeException(e);
}
}, "local port listener").start();
// Test once to prove that the local HTTP connection is closed when the remote HTTP connection is closed.
httpGetAndAssertConnectionClosedByServer(12345);
// Test again to prove that the tunnel is still open, even after HTTP connection was closed.
httpGetAndAssertConnectionClosedByServer(12345);
}
public static void httpGetAndAssertConnectionClosedByServer(int port) throws IOException {
System.out.println("HTTP GET to port: " + port);
try (Socket socket = new Socket("localhost", port)) {
// Send a basic HTTP GET
// It returns 400 Bad Request because it's missing a bunch of info, but the HTTP response doesn't matter, we just want to test the connection closing.
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(outputStream);
writer.println("GET / HTTP/1.1");
writer.println("");
writer.flush();
// Read the HTTP response
InputStream inputStream = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream);
int buf = -2;
while (true) {
buf = reader.read();
System.out.print((char)buf);
if (buf == -1) {
break;
}
}
// Attempt to read more. If the server has closed the connection this will return -1
int read = inputStream.read();
// Assert input stream was closed by server.
Assert.assertEquals(-1, read);
}
}
private int httpGet(String server, int port) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
String urlString = "http://" + server + ":" + port;
log.info("Trying: GET " + urlString);
HttpResponse execute = client.execute(new HttpGet(urlString));
return execute.getStatusLine().getStatusCode();
}
private SSHClient getFixtureClient() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("jeroen", "jeroen");
return sshClient;
}
}

View File

@@ -37,14 +37,14 @@ import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertEquals;
public class RemotePortForwarderTest {
private static final Logger log = LoggerFactory.getLogger(RemotePortForwarderTest.class);
private static final PortRange RANGE = new PortRange(9000, 9999);
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress("127.0.0.1", 8080);
private static final String LOCALHOST = "127.0.0.1";
private static final InetSocketAddress HTTP_SERVER_SOCKET_ADDR = new InetSocketAddress(LOCALHOST, 8080);
@Rule
public SshFixture fixture = new SshFixture();
@@ -62,61 +62,63 @@ public class RemotePortForwarderTest {
@Test
public void shouldHaveWorkingHttpServer() throws IOException {
// Just to check that we have a working http server...
assertThat(httpGet("127.0.0.1", 8080), equalTo(200));
assertEquals(200, httpGet( 8080));
}
@Test
public void shouldDynamicallyForwardPortForLocalhost() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", new SinglePort(0));
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
assertHttpGetSuccess(bind);
}
@Test
public void shouldDynamicallyForwardPortForAllIPv4() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", new SinglePort(0));
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
assertHttpGetSuccess(bind);
}
@Test
public void shouldDynamicallyForwardPortForAllProtocols() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", new SinglePort(0));
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
assertHttpGetSuccess(bind);
}
@Test
public void shouldForwardPortForLocalhost() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "127.0.0.1", RANGE);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
assertHttpGetSuccess(bind);
}
@Test
public void shouldForwardPortForAllIPv4() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "0.0.0.0", RANGE);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
assertHttpGetSuccess(bind);
}
@Test
public void shouldForwardPortForAllProtocols() throws IOException {
SSHClient sshClient = getFixtureClient();
RemotePortForwarder.Forward bind = forwardPort(sshClient, "", RANGE);
assertThat(httpGet("127.0.0.1", bind.getPort()), equalTo(200));
assertHttpGetSuccess(bind);
}
private void assertHttpGetSuccess(final RemotePortForwarder.Forward bind) throws IOException {
assertEquals(200, httpGet(bind.getPort()));
}
private RemotePortForwarder.Forward forwardPort(SSHClient sshClient, String address, PortRange portRange) throws IOException {
while (true) {
try {
RemotePortForwarder.Forward forward = sshClient.getRemotePortForwarder().bind(
return sshClient.getRemotePortForwarder().bind(
// where the server should listen
new RemotePortForwarder.Forward(address, portRange.nextPort()),
// what we do with incoming connections that are forwarded to us
new SocketForwardingConnectListener(HTTP_SERVER_SOCKET_ADDR));
return forward;
} catch (ConnectionException ce) {
if (!portRange.hasNext()) {
throw ce;
@@ -125,9 +127,9 @@ public class RemotePortForwarderTest {
}
}
private int httpGet(String server, int port) throws IOException {
private int httpGet(int port) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
String urlString = "http://" + server + ":" + port;
String urlString = "http://" + LOCALHOST + ":" + port;
log.info("Trying: GET " + urlString);
HttpResponse execute = client.execute(new HttpGet(urlString));
return execute.getStatusLine().getStatusCode();
@@ -140,7 +142,7 @@ public class RemotePortForwarderTest {
}
private static class PortRange {
private int upper;
private final int upper;
private int current;
public PortRange(int lower, int upper) {

View File

@@ -15,54 +15,67 @@
*/
package com.hierynomus.sshj.keepalive;
import com.hierynomus.sshj.test.KnownFailingTests;
import com.hierynomus.sshj.test.SlowTests;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.keepalive.KeepAlive;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.UserAuthException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class KeepAliveThreadTerminationTest {
private static final int KEEP_ALIVE_SECONDS = 1;
private static final long STOP_SLEEP = 1500;
@Rule
public SshFixture fixture = new SshFixture();
@Test
@Category({SlowTests.class, KnownFailingTests.class})
public void shouldCorrectlyTerminateThreadOnDisconnect() throws IOException, InterruptedException {
DefaultConfig defaultConfig = new DefaultConfig();
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
for (int i = 0; i < 10; i++) {
SSHClient sshClient = fixture.setupClient(defaultConfig);
fixture.connectClient(sshClient);
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(1);
try {
sshClient.authPassword("bad", "credentials");
fail("Should not auth.");
} catch (UserAuthException e) {
// OK
}
fixture.stopClient();
Thread.sleep(2000);
}
public void shouldNotStartThreadOnSetKeepAliveInterval() {
final SSHClient sshClient = setupClient();
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
for (long l : threadMXBean.getAllThreadIds()) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(l);
if (threadInfo.getThreadName().equals("keep-alive") && threadInfo.getThreadState() != Thread.State.TERMINATED) {
fail("Found alive keep-alive thread in state " + threadInfo.getThreadState());
}
}
final KeepAlive keepAlive = sshClient.getConnection().getKeepAlive();
assertTrue(keepAlive.isDaemon());
assertFalse(keepAlive.isAlive());
assertEquals(Thread.State.NEW, keepAlive.getState());
}
@Test
public void shouldStartThreadOnConnectAndInterruptOnDisconnect() throws IOException, InterruptedException {
final SSHClient sshClient = setupClient();
final KeepAlive keepAlive = sshClient.getConnection().getKeepAlive();
assertTrue(keepAlive.isDaemon());
assertEquals(Thread.State.NEW, keepAlive.getState());
fixture.connectClient(sshClient);
assertThrows(UserAuthException.class, () -> sshClient.authPassword("bad", "credentials"));
assertEquals(Thread.State.TIMED_WAITING, keepAlive.getState());
fixture.stopClient();
Thread.sleep(STOP_SLEEP);
assertFalse(keepAlive.isAlive());
assertEquals(Thread.State.TERMINATED, keepAlive.getState());
}
private SSHClient setupClient() {
final DefaultConfig defaultConfig = new DefaultConfig();
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
final SSHClient sshClient = fixture.setupClient(defaultConfig);
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(KEEP_ALIVE_SECONDS);
return sshClient;
}
}

View File

@@ -17,21 +17,25 @@ package com.hierynomus.sshj.sftp;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.sftp.OpenMode;
import net.schmizz.sshj.sftp.RemoteFile;
import net.schmizz.sshj.sftp.SFTPEngine;
import net.schmizz.sshj.sftp.SFTPException;
import org.apache.sshd.common.util.io.IoUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.security.SecureRandom;
import java.util.EnumSet;
import java.util.Random;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class RemoteFileTest {
@Rule
@@ -84,4 +88,141 @@ public class RemoteFileTest {
assertThat("The written and received data should match", data, equalTo(test2));
}
@Test
public void shouldNotReadAheadAfterLimitInputStream() throws IOException {
SSHClient ssh = fixture.setupConnectedDefaultClient();
ssh.authPassword("test", "test");
SFTPEngine sftp = new SFTPEngine(ssh).init();
RemoteFile rf;
File file = temp.newFile("SftpReadAheadLimitTest.bin");
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
byte[] data = new byte[8192];
new Random(53).nextBytes(data);
data[3072] = 1;
rf.write(0, data, 0, data.length);
rf.close();
assertThat("The file should exist", file.exists());
rf = sftp.open(file.getPath());
InputStream rs = rf.new ReadAheadRemoteFileInputStream(16 /*maxUnconfirmedReads*/,0, 3072);
byte[] test = new byte[4097];
int n = 0;
while (n < 2048) {
n += rs.read(test, n, 2048 - n);
}
rf.close();
while (n < 3072) {
n += rs.read(test, n, 3072 - n);
}
assertThat("buffer overrun", test[3072] == 0);
try {
rs.read(test, n, test.length - n);
fail("Content must not be buffered");
} catch (SFTPException e){
// expected
}
}
@Test
public void limitedReadAheadInputStream() throws IOException {
SSHClient ssh = fixture.setupConnectedDefaultClient();
ssh.authPassword("test", "test");
SFTPEngine sftp = new SFTPEngine(ssh).init();
RemoteFile rf;
File file = temp.newFile("SftpReadAheadLimitedTest.bin");
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
byte[] data = new byte[8192];
new Random(53).nextBytes(data);
data[3072] = 1;
rf.write(0, data, 0, data.length);
rf.close();
assertThat("The file should exist", file.exists());
rf = sftp.open(file.getPath());
InputStream rs = rf.new ReadAheadRemoteFileInputStream(16 /*maxUnconfirmedReads*/,0, 3072);
byte[] test = new byte[4097];
int n = 0;
while (n < 2048) {
n += rs.read(test, n, 2048 - n);
}
while (n < 3072) {
n += rs.read(test, n, 3072 - n);
}
assertThat("buffer overrun", test[3072] == 0);
n += rs.read(test, n, test.length - n); // --> ArrayIndexOutOfBoundsException
byte[] test2 = new byte[data.length];
System.arraycopy(test, 0, test2, 0, test.length);
while (n < data.length) {
n += rs.read(test2, n, data.length - n);
}
assertThat("The written and received data should match", data, equalTo(test2));
}
@Test
public void shouldReadCorrectlyWhenWrappedInBufferedStream_FullSizeBuffer() throws IOException {
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 1024 * 1024);
}
@Test
public void shouldReadCorrectlyWhenWrappedInBufferedStream_HalfSizeBuffer() throws IOException {
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 512 * 1024);
}
@Test
public void shouldReadCorrectlyWhenWrappedInBufferedStream_QuarterSizeBuffer() throws IOException {
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 256 * 1024);
}
@Test
public void shouldReadCorrectlyWhenWrappedInBufferedStream_SmallSizeBuffer() throws IOException {
doTestShouldReadCorrectlyWhenWrappedInBufferedStream(1024 * 1024, 1024);
}
private void doTestShouldReadCorrectlyWhenWrappedInBufferedStream(int fileSize, int bufferSize) throws IOException {
SSHClient ssh = fixture.setupConnectedDefaultClient();
ssh.authPassword("test", "test");
SFTPEngine sftp = new SFTPEngine(ssh).init();
final byte[] expected = new byte[fileSize];
new SecureRandom(new byte[] { 31 }).nextBytes(expected);
File file = temp.newFile("shouldReadCorrectlyWhenWrappedInBufferedStream.bin");
try (OutputStream fStream = new FileOutputStream(file)) {
IoUtils.copy(new ByteArrayInputStream(expected), fStream);
}
RemoteFile rf = sftp.open(file.getPath());
final byte[] actual;
try (InputStream inputStream = new BufferedInputStream(
rf.new ReadAheadRemoteFileInputStream(10),
bufferSize)
) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IoUtils.copy(inputStream, baos, expected.length);
actual = baos.toByteArray();
}
assertEquals("The file should be fully read", expected.length, actual.length);
assertThat("The file should be read correctly",
ByteArrayUtils.equals(expected, 0, actual, 0, expected.length));
}
}

View File

@@ -19,21 +19,18 @@ import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.scp.server.ScpCommandFactory;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.server.scp.ScpCommandFactory;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.shell.ProcessShellFactory;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.junit.rules.ExternalResource;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -42,10 +39,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class SshFixture extends ExternalResource {
public static final String hostkey = "hostkey.pem";
public static final String fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
public static final String listCommand = OsUtils.isWin32() ? "cmd.exe /C dir" : "ls";
private SshServer server = defaultSshServer();
private final SshServer server = defaultSshServer();
private SSHClient client = null;
private AtomicBoolean started = new AtomicBoolean(false);
private final AtomicBoolean started = new AtomicBoolean(false);
private boolean autoStart = true;
public SshFixture(boolean autoStart) {
@@ -108,38 +106,23 @@ public class SshFixture extends ExternalResource {
sshServer.setPort(randomPort());
ClassLoadableResourceKeyPairProvider fileKeyPairProvider = new ClassLoadableResourceKeyPairProvider(hostkey);
sshServer.setKeyPairProvider(fileKeyPairProvider);
sshServer.setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
return username.equals(password);
}
});
sshServer.setPasswordAuthenticator((username, password, session) -> username.equals(password));
sshServer.setGSSAuthenticator(new BogusGSSAuthenticator());
sshServer.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
ScpCommandFactory commandFactory = new ScpCommandFactory();
commandFactory.setDelegateCommandFactory(new CommandFactory() {
@Override
public Command createCommand(String command) {
return new ProcessShellFactory(command.split(" ")).create();
}
});
commandFactory.setDelegateCommandFactory((session, command) -> new ProcessShellFactory(command, command.split(" ")).createShell(session));
sshServer.setCommandFactory(commandFactory);
sshServer.setShellFactory(new ProcessShellFactory("ls"));
sshServer.setShellFactory(new ProcessShellFactory(listCommand, listCommand.split(" ")));
return sshServer;
}
private int randomPort() {
try {
ServerSocket s = null;
try {
s = new ServerSocket(0);
try (final ServerSocket s = new ServerSocket(0)) {
return s.getLocalPort();
} finally {
if (s != null)
s.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
throw new UncheckedIOException(e);
}
}

View File

@@ -39,4 +39,8 @@ public class FileUtil {
IOUtils.closeQuietly(fileInputStream);
}
}
public static boolean compareFileContents(File f1, File f2) throws IOException {
return readFromFile(f1).equals(readFromFile(f2));
}
}

View File

@@ -22,9 +22,8 @@ import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.transport.kex.DHGexSHA1;
import net.schmizz.sshj.transport.kex.DHGexSHA256;
import net.schmizz.sshj.transport.kex.ECDHNistP;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.kex.DHGEXServer;
import org.apache.sshd.server.kex.DHGServer;
@@ -38,6 +37,7 @@ import java.util.Collections;
@RunWith(Parameterized.class)
public class KeyExchangeTest extends BaseAlgorithmTest {
@SuppressWarnings("deprecation")
@Parameterized.Parameters(name = "algorithm={0}")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][]{
@@ -56,10 +56,10 @@ public class KeyExchangeTest extends BaseAlgorithmTest {
});
}
private Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
private NamedFactory<KeyExchange> serverFactory;
private final Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory;
private final KeyExchangeFactory serverFactory;
public KeyExchangeTest(NamedFactory<KeyExchange> serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
public KeyExchangeTest(KeyExchangeFactory serverFactory, Factory.Named<net.schmizz.sshj.transport.kex.KeyExchange> clientFactory) {
this.clientFactory = clientFactory;
this.serverFactory = serverFactory;
}

View File

@@ -12,19 +12,23 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package org.mindrot.jbcrypt;
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
import junit.framework.TestCase;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* JUnit unit tests for BCrypt routines
* @author Damien Miller
* @version 0.2
*/
public class TestBCrypt extends TestCase {
String test_vectors[][] = {
public class BCryptTest {
String[][] test_vectors = {
{ "",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
@@ -87,17 +91,10 @@ public class TestBCrypt extends TestCase {
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
};
/**
* Entry point for unit tests
* @param args unused
*/
public static void main(String[] args) {
junit.textui.TestRunner.run(TestBCrypt.class);
}
/**
* Test method for 'BCrypt.hashpw(String, String)'
*/
@Test
public void testHashpw() {
System.out.print("BCrypt.hashpw(): ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -108,16 +105,16 @@ public class TestBCrypt extends TestCase {
assertEquals(hashed, expected);
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.gensalt(int)'
*/
@Test
public void testGensaltInt() {
System.out.print("BCrypt.gensalt(log_rounds):");
for (int i = 4; i <= 12; i++) {
System.out.print(" " + Integer.toString(i) + ":");
System.out.print(" " + i + ":");
for (int j = 0; j < test_vectors.length; j += 4) {
String plain = test_vectors[j][0];
String salt = BCrypt.gensalt(i);
@@ -127,12 +124,12 @@ public class TestBCrypt extends TestCase {
System.out.print(".");
}
}
System.out.println("");
}
/**
* Test method for 'BCrypt.gensalt()'
*/
@Test
public void testGensalt() {
System.out.print("BCrypt.gensalt(): ");
for (int i = 0; i < test_vectors.length; i += 4) {
@@ -143,13 +140,13 @@ public class TestBCrypt extends TestCase {
assertEquals(hashed1, hashed2);
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting success
*/
@Test
public void testCheckpw_success() {
System.out.print("BCrypt.checkpw w/ good passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -158,13 +155,13 @@ public class TestBCrypt extends TestCase {
assertTrue(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
System.out.println("");
}
/**
* Test method for 'BCrypt.checkpw(String, String)'
* expecting failure
*/
@Test
public void testCheckpw_failure() {
System.out.print("BCrypt.checkpw w/ bad passwords: ");
for (int i = 0; i < test_vectors.length; i++) {
@@ -174,12 +171,12 @@ public class TestBCrypt extends TestCase {
assertFalse(BCrypt.checkpw(plain, expected));
System.out.print(".");
}
System.out.println("");
}
/**
* Test for correct hashing of non-US-ASCII passwords
*/
@Test
public void testInternationalChars() {
System.out.print("BCrypt.hashpw w/ international chars: ");
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
@@ -192,7 +189,6 @@ public class TestBCrypt extends TestCase {
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
assertFalse(BCrypt.checkpw(pw1, h2));
System.out.print(".");
System.out.println("");
}
private static class BCryptHashTV {
@@ -242,7 +238,8 @@ public class TestBCrypt extends TestCase {
}),
};
public void testBCryptHashTestVectors() throws Exception {
@Test
public void testBCryptHashTestVectors() {
System.out.print("BCrypt.hash w/ known vectors: ");
for (BCryptHashTV tv : bcrypt_hash_test_vectors) {
byte[] output = new byte[tv.out.length];
@@ -250,7 +247,6 @@ public class TestBCrypt extends TestCase {
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
private static class BCryptPbkdfTV {
@@ -281,7 +277,8 @@ public class TestBCrypt extends TestCase {
(byte) 0x83, (byte) 0x3c, (byte) 0xf0, (byte) 0xdc, (byte) 0xf5, (byte) 0x6d, (byte) 0xb6, (byte) 0x56, (byte) 0x08, (byte) 0xe8, (byte) 0xf0, (byte) 0xdc, (byte) 0x0c, (byte) 0xe8, (byte) 0x82, (byte) 0xbd}),
};
public void testBCryptPbkdfTestVectors() throws Exception {
@Test
public void testBCryptPbkdfTestVectors() {
System.out.print("BCrypt.pbkdf w/ known vectors: ");
for (BCryptPbkdfTV tv : bcrypt_pbkdf_test_vectors) {
byte[] output = new byte[tv.out.length];
@@ -289,6 +286,5 @@ public class TestBCrypt extends TestCase {
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
}

View File

@@ -20,12 +20,8 @@ import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
import net.schmizz.sshj.userauth.method.ChallengeResponseProvider;
import net.schmizz.sshj.userauth.password.Resource;
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.server.auth.UserAuth;
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractive;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -43,28 +39,8 @@ public class AuthKeyboardInteractiveTest {
@Before
public void setKeyboardInteractiveAuthenticator() throws IOException {
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
@Override
public String getName() {
return UserAuthKeyboardInteractiveFactory.NAME;
}
@Override
public UserAuth get() {
return new UserAuthKeyboardInteractive();
}
@Override
public UserAuth create() {
return get();
}
}));
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
return password.equals(username);
}
});
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthKeyboardInteractiveFactory()));
fixture.getServer().setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
fixture.getServer().start();
}
@@ -75,7 +51,7 @@ public class AuthKeyboardInteractiveTest {
sshClient.auth(userAndPassword, new AuthKeyboardInteractive(new ChallengeResponseProvider() {
@Override
public List<String> getSubmethods() {
return new ArrayList<String>();
return new ArrayList<>();
}
@Override

View File

@@ -21,56 +21,29 @@ import net.schmizz.sshj.userauth.UserAuthException;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUpdateProvider;
import net.schmizz.sshj.userauth.password.Resource;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.server.auth.UserAuth;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
import org.apache.sshd.server.auth.password.UserAuthPassword;
import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
import org.apache.sshd.server.session.ServerSession;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.util.Collections;
import java.util.Stack;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
public class AuthPasswordTest {
@Rule
public SshFixture fixture = new SshFixture(false);
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void setPasswordAuthenticator() throws IOException {
fixture.getServer().setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(new NamedFactory<UserAuth>() {
@Override
public String getName() {
return UserAuthPasswordFactory.NAME;
}
@Override
public UserAuth get() {
return new UserAuthPassword() {
@Override
protected Boolean handleClientPasswordChangeRequest(Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword) throws Exception {
return session.getPasswordAuthenticator().authenticate(username, newPassword, session);
}
};
}
@Override
public UserAuth create() {
return get();
}
}));
fixture.getServer().setUserAuthFactories(Collections.singletonList(new UserAuthPasswordFactory()));
fixture.getServer().setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
@@ -80,6 +53,12 @@ public class AuthPasswordTest {
return password.equals(username);
}
}
@Override
public boolean handleClientPasswordChangeRequest(
ServerSession session, String username, String oldPassword, String newPassword) {
return username.equals(newPassword);
}
});
fixture.getServer().start();
}
@@ -87,8 +66,7 @@ public class AuthPasswordTest {
@Test
public void shouldNotHandlePasswordChangeIfNoPasswordUpdateProviderSet() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
expectedException.expect(UserAuthException.class);
sshClient.authPassword("jeroen", "changeme");
assertThrows(UserAuthException.class, () -> sshClient.authPassword("jeroen", "changeme"));
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
}
@@ -112,8 +90,7 @@ public class AuthPasswordTest {
@Test
public void shouldHandlePasswordChangeWithWrongPassword() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
expectedException.expect(UserAuthException.class);
sshClient.authPassword("jeroen", new PasswordFinder() {
assertThrows(UserAuthException.class, () -> sshClient.authPassword("jeroen", new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
return "changeme".toCharArray();
@@ -123,7 +100,7 @@ public class AuthPasswordTest {
public boolean shouldRetry(Resource<?> resource) {
return false;
}
}, new StaticPasswordUpdateProvider("bad"));
}, new StaticPasswordUpdateProvider("bad")));
assertThat("Should not have authenticated", !sshClient.isAuthenticated());
}
@@ -153,7 +130,7 @@ public class AuthPasswordTest {
}
private static class StaticPasswordUpdateProvider implements PasswordUpdateProvider {
private Stack<String> newPasswords = new Stack<String>();
private final Stack<String> newPasswords = new Stack<>();
public StaticPasswordUpdateProvider(String... newPasswords) {
for (int i = newPasswords.length - 1; i >= 0; i--) {

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