Compare commits

...

26 Commits

Author SHA1 Message Date
Jeroen van Erp
1d8eaa7ce2 Release version: 0.31.0 2021-02-08 22:21:35 +01:00
Jeroen van Erp
6eea9a993c VSCode files 2021-02-08 22:21:23 +01:00
Jeroen van Erp
67d2cf72d6 Prepare release notes for 0.31.0 2021-02-08 22:17:42 +01:00
Jeroen van Erp
b8d58389cf Merge branch 'informaticum-master' 2021-01-13 14:30:45 +01:00
Jeroen van Erp
c5f48f9888 Merge branch 'master' of github.com:informaticum/sshj into informaticum-master 2021-01-13 14:30:29 +01:00
Jeroen van Erp
c05c3307b3 Update dependencies
- BouncyCastle 1.68
- asn-one 0.5.0
- slf4j-api 1.7.30
2021-01-13 10:41:01 +01:00
Vladimir Lagunov
9bc9262842 Support ED25519 and ECDSA keys in the PuTTY format (#660)
* Support ED25519 PuTTY keys.

Fix #659

* PuTTYKeyFile: Use net.schmizz.sshj.common.Buffer instead of own KeyReader.

A tiny refactoring made in order to allow usage of other utility methods which require Buffer.

* Support ECDSA PuTTY keys.

* Some code cleanup

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
2021-01-08 22:44:19 +01:00
Fabian Bonk
6d7dd741de Bump bouncycastle version (#655) 2020-12-21 13:41:11 +01:00
stefan
7f8328f23f Backdate license to prevent build's "License violations" (#653) 2020-12-12 20:10:32 +01:00
stefan
be18cc6e6a Add license prelude (#653) 2020-12-12 20:03:42 +01:00
stefan
ee68e0a8e6 Adopt project's import order (#653)
- copied by import order of 'SocketStreamCopyMonitorTest'
2020-12-12 20:03:12 +01:00
stefan
9266b6c04a Testing #toString of Parameters (#653) 2020-12-12 20:00:43 +01:00
stefan
9e9797c326 Adding #toString to Parameters (#653) 2020-12-12 19:58:31 +01:00
stefan
ee7a65531f Testing #equals of Parameters (#653) 2020-12-12 19:58:19 +01:00
stefan
8337cce382 Adding #equals to Parameters (#653) 2020-12-12 19:58:05 +01:00
stefan
bc5a119169 Testing #hashCode of Parameters (#653) 2020-12-12 19:57:51 +01:00
stefan
cda04809e1 Adding #hashCode to Parameters (#653) 2020-12-12 19:56:56 +01:00
Henning Poettker
07d624b1df reduced log level for lenient handling of missing CR at end of identification (#647) 2020-11-24 15:03:11 +01:00
Jeroen van Erp
60aa230929 Update release notes for upcoming 0.31.0 2020-11-17 14:47:18 +01:00
Fabian Henneke
2edaf07e71 Improve Android compatibility (#636)
* Loop through security providers to check for BC

Instead of only counting BouncyCastle as being registered if it
is set as the explicit security provider used by SSHJ, count it as
registered if it is available as a provider.

This commit improves Android compatibility, which requires not
specifying an explicit provider.

* Generify BC-specific curve specifiers

The ECNamendCurveGenParameterSpec is a BC-specific workaround for
missing curve tables in Java 1.4 and earlier. For the sake of Android
compatibility, where Conscrypt can't deal with this custom spec class,
replace it with the standard ECGenParameterSpec and update the curve
names to the standard identifiers.
2020-10-20 09:57:51 +02:00
Fabian Henneke
d124607225 Fix PR links in README (#632) 2020-09-10 09:26:08 +02:00
Jeroen van Erp
8c899eb867 Fix warnings 2020-09-09 11:02:28 +02:00
Jeroen van Erp
939a170ee8 Update README 2020-09-09 10:37:56 +02:00
Raymond Lai
143069e3e0 Implement AES-GCM cipher support (#630)
* Implement AES-GCM cipher support

Fixes #217.

A port of AES-GCM cipher support from Apache MINA-SSHD, based on https://github.com/apache/mina-sshd/pull/132.

Included tests for decoding SSH packets sent from Apache MINA-SSHD and OpenSSH (Version 7.9p1 as used by Debian 10).

Manual tests also done on OpenSSH server 7.9p1 running Debian 10 with its available ciphers, including 3des-cbc, aes128-cbc, aes192-cbc, aes256-cbc, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com and aes256-gcm@openssh.com.

* Changes per PR feedback

- Fixed variable/statement whitespaces and add back missing braces per coding standard requirement
- Moved Buffer.putLong() and Buffer.getLong() into GcmCipher.CounterGCMParameterSpec since it's the only user
- Moved BaseCipher.authSize into GcmCipher since it is the only cipher that would return a non-zero. BaseCipher will keep return 0 instead
- Made BaseCipher.cipher protected instead of making it publicly accessible
- Combined the three decoding modes in Decoder.decode() into one single method, to reduce code duplication
- Added integration test for the ciphers, along with the newly implemented AES-GCM ciphers
2020-09-09 09:51:17 +02:00
Jeroen van Erp
4458332cbf Update release notes for 0.30.0 2020-08-25 15:56:15 +02:00
Pavel Dionisev
a0d7b7fd41 Avoid key lleakage. (#627)
In some cases, current code will leak parts or even the whole ssh key if it's slightly malformed.
One example of that malformation will be a key, where all newlines are replaced by other character, thus turning a multiline key to a single big string.
Then that whole line will be leaked to exception message.
2020-08-25 15:23:46 +02:00
77 changed files with 1275 additions and 245 deletions

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@ sshj.jar
# MacOS X
.DS_Store
# VSCode
.metals/

6
.vscode/settings.json vendored Normal file
View File

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

View File

@@ -1,7 +1,7 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.30.0
:sshj_version: 0.31.0
:source-highlighter: pygments
image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"]
@@ -64,7 +64,7 @@ In the `examples` directory, there is a separate Maven project that shows how th
Implementations / adapters for the following algorithms are included:
ciphers::
`aes{128,192,256}-{cbc,ctr}`, `blowfish-{cbc,ctr}`, `3des-{cbc,ctr}`, `twofish{128,192,256}-{cbc,ctr}`, `twofish-cbc`, `serpent{128,192,256}-{cbc,ctr}`, `idea-{cbc,ctr}`, `cast128-{cbc,ctr}`, `arcfour`, `arcfour{128,256}`
`aes{128,192,256}-{cbc,ctr}`, `aes{128,256}-gcm@openssh.com`, `blowfish-{cbc,ctr}`, `3des-{cbc,ctr}`, `twofish{128,192,256}-{cbc,ctr}`, `twofish-cbc`, `serpent{128,192,256}-{cbc,ctr}`, `idea-{cbc,ctr}`, `cast128-{cbc,ctr}`, `arcfour`, `arcfour{128,256}`
SSHJ also supports the following extended (non official) ciphers: `camellia{128,192,256}-{cbc,ctr}`, `camellia{128,192,256}-{cbc,ctr}@openssh.org`
key exchange::
@@ -105,13 +105,34 @@ Issue tracker: https://github.com/hierynomus/sshj/issues
Fork away!
== Release history
SSHJ 0.30.0 (2020-??-??)::
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
* Merged https://github.com/hierynomus/sshj/pull/655[#655]: Bump BouncyCastle due to CVE
* Merged https://github.com/hierynomus/sshj/pull/653[#653]: Make Parameters class useable as HashMap key
* Merged https://github.com/hierynomus/sshj/pull/647[#647]: Reduce log level for identification parser
* Merged https://github.com/hierynomus/sshj/pull/630[#630]: Add support for `aes128-gcm@openssh.com` and `aes256-gcm@openssh.com` ciphers
* Merged https://github.com/hierynomus/sshj/pull/636[#636]: Improved Android compatibility
* Merged https://github.com/hierynomus/sshj/pull/627[#627]: Prevent key leakage
SSHJ 0.30.0 (2020-08-17)::
* **BREAKING CHANGE**: Removed `setSignatureFactories` and `getSignatureFactories` from the Config and switched them for `getKeyAlgorithms` and `setKeyAlgorithms`
* Fixed https://github.com/hierynomus/sshj/pulls/588[#588]: Add support for `ssh-rsa2-256` and `ssh-rsa2-512` signatures
* Merged https://github.com/hierynomus/sshj/pulls/579[#579]: Fix NPE in OpenSSHKnownHosts
* Merged https://github.com/hierynomus/sshj/pulls/587[#587]: Add passwordfinder retry for OpenSSHKeyV1KeyFile
* Merged https://github.com/hierynomus/sshj/pulls/586[#586]: Make KeyType compatible with Android Store
* Merged https://github.com/hierynomus/sshj/pulls/593[#593]: Change `UserAuth.getAllowedMethods()` to Collection return type
* Fixed https://github.com/hierynomus/sshj/pull/588[#588]: Add support for `ssh-rsa2-256` and `ssh-rsa2-512` signatures
* Merged https://github.com/hierynomus/sshj/pull/579[#579]: Fix NPE in OpenSSHKnownHosts
* Merged https://github.com/hierynomus/sshj/pull/587[#587]: Add passwordfinder retry for OpenSSHKeyV1KeyFile
* Merged https://github.com/hierynomus/sshj/pull/586[#586]: Make KeyType compatible with Android Store
* Merged https://github.com/hierynomus/sshj/pull/593[#593]: Change `UserAuth.getAllowedMethods()` to Collection return type
* Merged https://github.com/hierynomus/sshj/pull/595[#595]: Allow reading arbitrary length keys
* Merged https://github.com/hierynomus/sshj/pull/591[#591]: Allow to query SFTP extensions
* Merged https://github.com/hierynomus/sshj/pull/603[#603]: Add method to create Stateful SFTP client
* Merged https://github.com/hierynomus/sshj/pull/605[#605]: Use Daemon threads to avoid blocking JVM shutdown
* Merged https://github.com/hierynomus/sshj/pull/606[#606]: Always use the JCERandom RNG by default
* Merged https://github.com/hierynomus/sshj/pull/609[#609]: Clear passphrase after use to prevent security issues
* Merged https://github.com/hierynomus/sshj/pull/618[#618]: Fix localport of DirectConnection for use with OpenSSH > 8.0
* Merged https://github.com/hierynomus/sshj/pull/619[#619]: Upgraded BouncyCastle to 1.66
* Merged https://github.com/hierynomus/sshj/pull/622[#622]: Send 'ext-info-c' with KEX algorithms
* Merged https://github.com/hierynomus/sshj/pull/623[#623]: Fix transport encoding of `nistp521` signatures
* Merged https://github.com/hierynomus/sshj/pull/607[#607]: Fix mathing pubkeys to key algorithms
* Merged https://github.com/hierynomus/sshj/pull/602[#602]: Fix RSA certificate key determination
SSHJ 0.27.0 (2019-01-24)::
* Fixed https://github.com/hierynomus/sshj/issues/415[#415]: Fixed wrongly prefixed '/' to path in SFTPClient.mkdirs
* Added support for ETM (Encrypt-then-Mac) MAC algorithms.
@@ -134,31 +155,31 @@ SSHJ 0.24.0 (2018-04-04)::
* Fixed https://github.com/hierynomus/sshj/issues/187[#187]: Fixed length bug in Buffer.putString
* Fixed https://github.com/hierynomus/sshj/issues/405[#405]: Continue host verification if first hostkey does not match.
SSHJ 0.23.0 (2017-10-13)::
* Merged https://github.com/hierynomus/sshj/pulls/372[#372]: Upgrade to 'net.i2p.crypto:eddsa:0.2.0'
* Merged https://github.com/hierynomus/sshj/pull/372[#372]: Upgrade to 'net.i2p.crypto:eddsa:0.2.0'
* Fixed https://github.com/hierynomus/sshj/issues/355[#355] and https://github.com/hierynomus/sshj/issues/354[#354]: Correctly decode signature bytes
* Fixed https://github.com/hierynomus/sshj/issues/365[#365]: Added support for new-style OpenSSH fingerprints of server keys
* Fixed https://github.com/hierynomus/sshj/issues/356[#356]: Fixed key type detection for ECDSA public keys
* Made SSHJ Java9 compatible
SSHJ 0.22.0 (2017-08-24)::
* Fixed https://github.com/hierynomus/sshj/pulls/341[#341]: Fixed path walking during recursive copy
* Merged https://github.com/hierynomus/sshj/pulls/338[#338]: Added ConsolePasswordFinder to read password from stdin
* Merged https://github.com/hierynomus/sshj/pulls/336[#336]: Added support for ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 signatures
* Fixed https://github.com/hierynomus/sshj/pull/341[#341]: Fixed path walking during recursive copy
* Merged https://github.com/hierynomus/sshj/pull/338[#338]: Added ConsolePasswordFinder to read password from stdin
* Merged https://github.com/hierynomus/sshj/pull/336[#336]: Added support for ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 signatures
* Fixed https://github.com/hierynomus/sshj/issues/331[#331]: Added support for wildcards in known_hosts file
SSHJ 0.21.1 (2017-04-25)::
* Merged https://github.com/hierynomus/sshj/pulls/322[#322]: Fix regression from 40f956b (invalid length parameter on outputstream)
* Merged https://github.com/hierynomus/sshj/pull/322[#322]: Fix regression from 40f956b (invalid length parameter on outputstream)
SSHJ 0.21.0 (2017-04-14)::
* Merged https://github.com/hierynomus/sshj/pulls/319[#319]: Added support for `ssh-rsa-cert-v01@openssh.com` and `ssh-dsa-cert-v01@openssh.com` certificate key files
* Merged https://github.com/hierynomus/sshj/pull/319[#319]: Added support for `ssh-rsa-cert-v01@openssh.com` and `ssh-dsa-cert-v01@openssh.com` certificate key files
* Upgraded Gradle to 3.4.1
* Merged https://github.com/hierynomus/sshj/pulls/305[#305]: Added support for custom string encoding
* Merged https://github.com/hierynomus/sshj/pull/305[#305]: Added support for custom string encoding
* Fixed https://github.com/hierynomus/sshj/issues/312[#312]: Upgraded BouncyCastle to 1.56
SSHJ 0.20.0 (2017-02-09)::
* Merged https://github.com/hierynomus/sshj/pulls/294[#294]: Reference ED25519 by constant instead of name
* Merged https://github.com/hierynomus/sshj/pulls/293[#293], https://github.com/hierynomus/sshj/pulls/295[#295] and https://github.com/hierynomus/sshj/pulls/301[#301]: Fixed OSGi packaging
* Merged https://github.com/hierynomus/sshj/pull/294[#294]: Reference ED25519 by constant instead of name
* Merged https://github.com/hierynomus/sshj/pull/293[#293], https://github.com/hierynomus/sshj/pull/295[#295] and https://github.com/hierynomus/sshj/pull/301[#301]: Fixed OSGi packaging
* Added new Diffie Hellman groups 15-18 for stronger KeyExchange algorithms
SSHJ 0.19.1 (2016-12-30)::
* Enabled PKCS5 Key files in DefaultConfig
* Merged https://github.com/hierynomus/sshj/pulls/291[#291]: Fixed sshj.properties loading and chained exception messages
* Merged https://github.com/hierynomus/sshj/pulls/284[#284]: Correctly catch interrupt in keepalive thread
* Merged https://github.com/hierynomus/sshj/pull/291[#291]: Fixed sshj.properties loading and chained exception messages
* Merged https://github.com/hierynomus/sshj/pull/284[#284]: Correctly catch interrupt in keepalive thread
* Fixed https://github.com/hierynomus/sshj/issues/292[#292]: Pass the configured RandomFactory to Diffie Hellman KEX
* Fixed https://github.com/hierynomus/sshj/issues/256[#256]: SSHJ now builds if no git repository present
* LocalPortForwarder now correctly interrupts its own thread on close()
@@ -168,11 +189,11 @@ SSHJ 0.19.0 (2016-11-25)::
SSHJ 0.18.0 (2016-09-30)::
* Fixed Android compatibility
* Upgrade to Gradle 3.0
* Merged https://github.com/hierynomus/sshj/pulls/271[#271]: Load known_hosts without requiring BouncyCastle
* Merged https://github.com/hierynomus/sshj/pulls/269[#269]: Brought back Java6 support by popular demand
* Merged https://github.com/hierynomus/sshj/pulls/267[#267]: Added support for per connection logging (Fixes https://github.com/hierynomus/sshj/issues/264[#264])
* Merged https://github.com/hierynomus/sshj/pulls/262[#262], https://github.com/hierynomus/sshj/pulls/265[#265] and https://github.com/hierynomus/sshj/pulls/266[#266]: Added PKCS5 key file support
* Fixed toString of sftp FileAttributes (Fixes https://github.com/hierynomus/sshj/pulls/258[#258])
* Merged https://github.com/hierynomus/sshj/pull/271[#271]: Load known_hosts without requiring BouncyCastle
* Merged https://github.com/hierynomus/sshj/pull/269[#269]: Brought back Java6 support by popular demand
* Merged https://github.com/hierynomus/sshj/pull/267[#267]: Added support for per connection logging (Fixes https://github.com/hierynomus/sshj/issues/264[#264])
* Merged https://github.com/hierynomus/sshj/pull/262[#262], https://github.com/hierynomus/sshj/pull/265[#265] and https://github.com/hierynomus/sshj/pull/266[#266]: Added PKCS5 key file support
* Fixed toString of sftp FileAttributes (Fixes https://github.com/hierynomus/sshj/pull/258[#258])
* Fixed https://github.com/hierynomus/sshj/issues/255[#255]: No longer depending on 'privately marked' classes in `net.i2p.crypto.eddsa.math` package, fixes OSGI dependencies
SSHJ 0.17.2 (2016-07-07)::
* Treating SSH Server identification line ending in '\n' instead of '\r\n' leniently.
@@ -182,7 +203,7 @@ SSHJ 0.17.0 (2016-07-05)::
* *Introduced breaking change in SFTP copy behaviour*: Previously an SFTP copy operation would behave differently if both source and target were folders with different names.
In this case instead of copying the contents of the source into the target directory, the directory itself was copied as a sub directory of the target directory.
This behaviour has been removed in favour of the default behaviour which is to copy the contents of the source into the target. Bringing the behaviour in line with how SCP works.
* Fixed https://github.com/hierynomus/sshj/issues/252[#252] (via: https://github.com/hierynomus/sshj/pulls/253[#253]): Same name subdirs are no longer merged by accident
* Fixed https://github.com/hierynomus/sshj/issues/252[#252] (via: https://github.com/hierynomus/sshj/pull/253[#253]): Same name subdirs are no longer merged by accident
SSHJ 0.16.0 (2016-04-11)::
* Fixed https://github.com/hierynomus/sshj/issues/239[#239]: Remote port forwards did not work if you used the empty string as address, or a catch-all address.
* Fixed https://github.com/hierynomus/sshj/issues/242[#242]: Added OSGI headers to sources jar manifest

View File

@@ -17,7 +17,7 @@ configurations {
pom
}
def bouncycastleVersion = "1.66"
def bouncycastleVersion = "1.67"
dependencies {
compile "org.slf4j:slf4j-api:1.7.7"

View File

@@ -40,15 +40,15 @@ repositories {
configurations.compile.transitive = false
def bouncycastleVersion = "1.66"
def bouncycastleVersion = "1.68"
def sshdVersion = "2.1.0"
dependencies {
implementation "org.slf4j:slf4j-api:1.7.7"
implementation "org.slf4j:slf4j-api:1.7.30"
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.4.0"
implementation "com.hierynomus:asn-one:0.5.0"
implementation "net.i2p.crypto:eddsa:0.3.0"

View File

@@ -131,4 +131,6 @@ Subsystem sftp /usr/lib/ssh/sftp-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/users_rsa_ca.pub
TrustedUserCAKeys /etc/ssh/users_rsa_ca.pub
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

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.cipher
import com.hierynomus.sshj.IntegrationBaseSpec
import net.schmizz.sshj.DefaultConfig
import spock.lang.Unroll
class CipherSpec extends IntegrationBaseSpec {
@Unroll
def "should correctly connect with #cipher Cipher"() {
given:
def cfg = new DefaultConfig()
cfg.setCipherFactories(cipherFactory)
def client = getConnectedClient(cfg)
when:
client.authPublickey(USERNAME, KEYFILE)
then:
client.authenticated
cleanup:
client.disconnect()
where:
cipherFactory << [BlockCiphers.TripleDESCBC(),
BlockCiphers.BlowfishCBC(),
BlockCiphers.AES128CBC(),
BlockCiphers.AES128CTR(),
BlockCiphers.AES192CBC(),
BlockCiphers.AES192CTR(),
BlockCiphers.AES256CBC(),
BlockCiphers.AES256CTR(),
GcmCiphers.AES128GCM(),
GcmCiphers.AES256GCM()]
cipher = cipherFactory.name
}
}

View File

@@ -23,6 +23,7 @@ import java.io.IOException;
* Thrown when a key file could not be decrypted correctly, e.g. if its checkInts differed in the case of an OpenSSH
* key file.
*/
@SuppressWarnings("serial")
public class KeyDecryptionFailedException extends IOException {
public static final String MESSAGE = "Decryption of the key failed. A supplied passphrase may be incorrect.";

View File

@@ -27,6 +27,7 @@ import java.util.Arrays;
* Our own extension of the EdDSAPublicKey that comes from ECC-25519, as that class does not implement equality.
* The code uses the equality of the keys as an indicator whether they're the same during host key verification.
*/
@SuppressWarnings("serial")
public class Ed25519PublicKey extends EdDSAPublicKey {
public Ed25519PublicKey(EdDSAPublicKeySpec spec) {

View File

@@ -79,11 +79,9 @@ public class IdentificationStringParser {
}
if (bytes[bytes.length - 2] != '\r') {
String ident = new String(bytes, 0, bytes.length - 1);
log.warn("Server identification has bad line ending, was expecting a '\\r\\n' however got: '{}' (hex: {})", (char) (bytes[bytes.length - 2] & 0xFF), Integer.toHexString(bytes[bytes.length - 2] & 0xFF));
log.warn("Will treat the identification of this server '{}' leniently", ident);
log.info("Server identification has bad line ending, was expecting a '\\r\\n' however got: '{}' (hex: {})", (char) (bytes[bytes.length - 2] & 0xFF), Integer.toHexString(bytes[bytes.length - 2] & 0xFF));
log.info("Will treat the identification of this server '{}' leniently", ident);
return ident;
// log.error("Data received up til here was: {}", new String(bytes));
// throw new TransportException("Incorrect identification: bad line ending: " + ByteArrayUtils.toHex(bytes, 0, bytes.length));
}
// Strip off the \r\n

View File

@@ -0,0 +1,164 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.cipher;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.cipher.BaseCipher;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
public class GcmCipher extends BaseCipher {
protected int authSize;
protected Mode mode;
protected boolean initialized;
protected CounterGCMParameterSpec parameters;
protected SecretKey secretKey;
public GcmCipher(int ivsize, int authSize, int bsize, String algorithm, String transformation) {
super(ivsize, bsize, algorithm, transformation);
this.authSize = authSize;
}
@Override
public int getAuthenticationTagSize() {
return authSize;
}
protected Cipher getInitializedCipherInstance() throws GeneralSecurityException {
if (!initialized) {
cipher.init(mode == Mode.Encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey, parameters);
initialized = true;
}
return cipher;
}
@Override
protected void initCipher(Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
this.mode = mode;
this.secretKey = getKeySpec(key);
this.parameters = new CounterGCMParameterSpec(getAuthenticationTagSize() * Byte.SIZE, iv);
cipher.init(getMode(mode), secretKey, parameters);
initialized = true;
}
@Override
public void updateAAD(byte[] data, int offset, int length) {
try {
Cipher cipher = getInitializedCipherInstance();
cipher.updateAAD(data, offset, length);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("Error updating data through cipher", e);
}
}
@Override
public void update(byte[] input, int inputOffset, int inputLen) {
if (mode == Mode.Decrypt) {
inputLen += getAuthenticationTagSize();
}
try {
Cipher cipher = getInitializedCipherInstance();
cipher.doFinal(input, inputOffset, inputLen, input, inputOffset);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("Error updating data through cipher", e);
}
/*
* As the RFC stated, the IV used in AES-GCM cipher for SSH is a 4-byte constant plus a 8-byte invocation
* counter. After cipher.doFinal() is called, IV invocation counter is increased by 1, and changes to the IV
* requires reinitializing the cipher, so initialized have to be false here, for the next invocation.
*
* Refer to RFC5647, Section 7.1
*/
parameters.incrementCounter();
initialized = false;
}
/**
* Algorithm parameters for AES/GCM that assumes the IV uses an 8-byte counter field as its most significant bytes.
*/
protected static class CounterGCMParameterSpec extends GCMParameterSpec {
protected final byte[] iv;
protected CounterGCMParameterSpec(int tLen, byte[] src) {
super(tLen, src);
if (src.length != 12) {
throw new IllegalArgumentException("GCM nonce must be 12 bytes, but given len=" + src.length);
}
iv = src.clone();
}
protected void incrementCounter() {
int off = iv.length - 8;
long counter = getLong(iv, off, 8);
putLong(addExact(counter, 1L), iv, off, 8);
}
@Override
public byte[] getIV() {
// JCE implementation of GCM will complain if the reference doesn't change between inits
return iv.clone();
}
static long addExact(long var0, long var2) {
long var4 = var0 + var2;
if (((var0 ^ var4) & (var2 ^ var4)) < 0L) {
throw new ArithmeticException("long overflow");
} else {
return var4;
}
}
static long getLong(byte[] buf, int off, int len) {
if (len < 8) {
throw new IllegalArgumentException("Not enough data for a long: required=8, available=" + len);
}
long l = (long) buf[off] << 56;
l |= ((long) buf[off + 1] & 0xff) << 48;
l |= ((long) buf[off + 2] & 0xff) << 40;
l |= ((long) buf[off + 3] & 0xff) << 32;
l |= ((long) buf[off + 4] & 0xff) << 24;
l |= ((long) buf[off + 5] & 0xff) << 16;
l |= ((long) buf[off + 6] & 0xff) << 8;
l |= (long) buf[off + 7] & 0xff;
return l;
}
static int putLong(long value, byte[] buf, int off, int len) {
if (len < 8) {
throw new IllegalArgumentException("Not enough data for a long: required=8, available=" + len);
}
buf[off] = (byte) (value >> 56);
buf[off + 1] = (byte) (value >> 48);
buf[off + 2] = (byte) (value >> 40);
buf[off + 3] = (byte) (value >> 32);
buf[off + 4] = (byte) (value >> 24);
buf[off + 5] = (byte) (value >> 16);
buf[off + 6] = (byte) (value >> 8);
buf[off + 7] = (byte) value;
return 8;
}
}
}

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.transport.cipher;
import net.schmizz.sshj.transport.cipher.Cipher;
public class GcmCiphers {
public static final String GALOIS_COUNTER_MODE = "GCM";
public static Factory AES128GCM() {
return new Factory(12, 16, 128, "aes128-gcm@openssh.com", "AES", GALOIS_COUNTER_MODE);
}
public static Factory AES256GCM() {
return new Factory(12, 16, 256, "aes256-gcm@openssh.com", "AES", GALOIS_COUNTER_MODE);
}
/** Named factory for BlockCipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
private int keysize;
private int authSize;
private String cipher;
private String mode;
private String name;
private int ivsize;
/**
* @param ivsize
* @param keysize The keysize used in bits.
* @param name
* @param cipher
* @param mode
*/
public Factory(int ivsize, int authSize, int keysize, String name, String cipher, String mode) {
this.name = name;
this.keysize = keysize;
this.cipher = cipher;
this.mode = mode;
this.ivsize = ivsize;
this.authSize = authSize;
}
@Override
public Cipher create() {
return new GcmCipher(ivsize, authSize, keysize / 8, cipher, cipher + "/" + mode + "/NoPadding");
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
}
}

View File

@@ -18,6 +18,7 @@ package net.schmizz.sshj;
import com.hierynomus.sshj.key.KeyAlgorithm;
import com.hierynomus.sshj.key.KeyAlgorithms;
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
import com.hierynomus.sshj.transport.cipher.StreamCiphers;
import com.hierynomus.sshj.transport.kex.DHGroups;
import com.hierynomus.sshj.transport.kex.ExtInfoClientFactory;
@@ -171,6 +172,8 @@ public class DefaultConfig
BlockCiphers.AES192CTR(),
BlockCiphers.AES256CBC(),
BlockCiphers.AES256CTR(),
GcmCiphers.AES128GCM(),
GcmCiphers.AES256GCM(),
BlockCiphers.BlowfishCBC(),
BlockCiphers.BlowfishCTR(),
BlockCiphers.Cast128CBC(),

View File

@@ -1,5 +1,6 @@
package net.schmizz.sshj.common;
/**
* <p>Encodes and decodes to and from Base64 notation.</p>
* <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
@@ -1359,7 +1360,7 @@ public class Base64
@Override
public Class<?> resolveClass(java.io.ObjectStreamClass streamClass)
throws java.io.IOException, ClassNotFoundException {
Class c = Class.forName(streamClass.getName(), false, loader);
Class<?> c = Class.forName(streamClass.getName(), false, loader);
if( c == null ){
return super.resolveClass(streamClass);
} else {

View File

@@ -21,6 +21,7 @@ import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Arrays;
@SuppressWarnings("serial")
public class Buffer<T extends Buffer<T>> {
public static class BufferException
@@ -55,7 +56,7 @@ public class Buffer<T extends Buffer<T>> {
public static final int DEFAULT_SIZE = 256;
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
public static final int MAX_SIZE = (1 << 30);
public static final int MAX_SIZE = (1 << 30);
/** Maximum size of a uint64 */
public static final BigInteger MAX_UINT64_VALUE = BigInteger.ONE
@@ -66,7 +67,7 @@ public class Buffer<T extends Buffer<T>> {
int j = 1;
while (j < i) {
j <<= 1;
if (j <= 0) throw new IllegalArgumentException("Cannot get next power of 2; "+i+" is too large");
if (j <= 0) throw new IllegalArgumentException("Cannot get next power of 2; "+i+" is too large");
}
return j;
}

View File

@@ -23,6 +23,7 @@ import java.io.IOException;
* Most exceptions in the {@code net.schmizz.sshj} package are instances of this class. An {@link SSHException} is
* itself an {@link IOException} and can be caught like that if this level of granularity is not desired.
*/
@SuppressWarnings("serial")
public class SSHException
extends IOException {

View File

@@ -16,6 +16,7 @@
package net.schmizz.sshj.common;
/** Represents unrecoverable exceptions in the {@code net.schmizz.sshj} package. */
@SuppressWarnings("serial")
public class SSHRuntimeException
extends RuntimeException {

View File

@@ -36,6 +36,8 @@ import javax.crypto.NoSuchPaddingException;
import static java.lang.String.format;
import java.lang.reflect.InvocationTargetException;
/**
* Static utility method relating to security facilities.
*/
@@ -53,8 +55,8 @@ public class SecurityUtils {
public static final String SPONGY_CASTLE = "SC";
/*
* Security provider identifier. null = default JCE
*/
* Security provider identifier. null = default JCE
*/
private static String securityProvider = null;
// relate to BC registration (or SpongyCastle on Android)
@@ -65,13 +67,17 @@ public class SecurityUtils {
Provider provider = null;
try {
Class<?> name = Class.forName(providerClassName);
provider = (Provider) name.newInstance();
provider = (Provider) name.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
LOG.info("Security Provider class '{}' not found", providerClassName);
} catch (InstantiationException e) {
LOG.info("Security Provider class '{}' could not be created", providerClassName);
} catch (IllegalAccessException e) {
LOG.info("Security Provider class '{}' could not be accessed", providerClassName);
} catch (InvocationTargetException e) {
LOG.info("Security Provider class '{}' could not be created", providerClassName);
} catch (NoSuchMethodException e) {
LOG.info("Security Provider class '{}' does not have a no-args constructor", providerClassName);
}
if (provider == null) {
@@ -243,7 +249,14 @@ public class SecurityUtils {
*/
public static synchronized boolean isBouncyCastleRegistered() {
register();
return BOUNCY_CASTLE.equals(securityProvider) || SPONGY_CASTLE.equals(securityProvider);
Provider[] providers = Security.getProviders();
for (Provider provider : providers) {
String name = provider.getName();
if (BOUNCY_CASTLE.equals(name) || SPONGY_CASTLE.equals(name)) {
return true;
}
}
return false;
}
public static synchronized void setRegisterBouncyCastle(boolean registerBouncyCastle) {

View File

@@ -15,6 +15,8 @@
*/
package net.schmizz.sshj.connection.channel.direct;
import java.util.Objects;
public class Parameters {
private final String localHost;
@@ -45,4 +47,24 @@ public class Parameters {
return localPort;
}
@Override
public int hashCode() {
return Objects.hash(localHost, localPort, remoteHost, remotePort);
}
@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (!(obj instanceof Parameters)) { return false; }
Parameters other = (Parameters) obj;
return Objects.equals(localHost, other.localHost) && localPort == other.localPort &&
Objects.equals(remoteHost, other.remoteHost) && remotePort == other.remotePort;
}
@Override
public String toString() {
return "Parameters [localHost=" + localHost + ", localPort=" + localPort + ", "+
"remoteHost=" + remoteHost + ", remotePort=" + remotePort + "]";
}
}

View File

@@ -21,6 +21,7 @@ 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;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
@@ -91,16 +92,18 @@ public class SignatureECDSA extends AbstractSignature {
public byte[] encode(byte[] sig) {
ByteArrayInputStream bais = new ByteArrayInputStream(sig);
com.hierynomus.asn1.ASN1InputStream asn1InputStream = new com.hierynomus.asn1.ASN1InputStream(new DERDecoder(), bais);
try {
ASN1Sequence sequence = asn1InputStream.readObject();
ASN1Integer r = (ASN1Integer) sequence.get(0);
ASN1Integer s = (ASN1Integer) sequence.get(1);
Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
buf.putMPInt(r.getValue());
buf.putMPInt(s.getValue());
ASN1Sequence sequence = asn1InputStream.readObject();
ASN1Integer r = (ASN1Integer) sequence.get(0);
ASN1Integer s = (ASN1Integer) sequence.get(1);
Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
buf.putMPInt(r.getValue());
buf.putMPInt(s.getValue());
return buf.getCompactData();
return buf.getCompactData();
} finally {
IOUtils.closeQuietly(asn1InputStream, bais);
}
}
@Override
@@ -123,16 +126,19 @@ public class SignatureECDSA extends AbstractSignature {
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);
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
try {
asn1OutputStream.writeObject(new ASN1Sequence(vector));
asn1OutputStream.flush();
} finally {
IOUtils.closeQuietly(asn1OutputStream);
}
return baos.toByteArray();
}
}

View File

@@ -45,6 +45,7 @@ abstract class Converter {
protected long seq = -1;
protected boolean authed;
protected boolean etm;
protected boolean authMode;
long getSequenceNumber() {
return seq;
@@ -57,7 +58,11 @@ abstract class Converter {
if (compression != null)
compression.init(getCompressionType());
this.cipherSize = cipher.getIVSize();
this.etm = mac.isEtm();
this.etm = this.mac != null && mac.isEtm();
if(cipher.getAuthenticationTagSize() > 0) {
this.cipherSize = cipher.getAuthenticationTagSize();
this.authMode = true;
}
}
void setAuthenticated() {

View File

@@ -70,87 +70,41 @@ final class Decoder
*
* @return number of bytes needed before further decoding possible
*/
private int decode()
throws SSHException {
if (etm) {
return decodeEtm();
} else {
return decodeMte();
}
}
/**
* Decode an Encrypt-Then-Mac packet.
*/
private int decodeEtm() throws SSHException {
int bytesNeeded;
while (true) {
if (packetLength == -1) {
assert inputBuffer.rpos() == 0 : "buffer cleared";
bytesNeeded = 4 - inputBuffer.available();
if (bytesNeeded <= 0) {
// In Encrypt-Then-Mac, the packetlength is sent unencrypted.
packetLength = inputBuffer.readUInt32AsInt();
checkPacketLength(packetLength);
} else {
// Needs more data
break;
}
} else {
assert inputBuffer.rpos() == 4 : "packet length read";
bytesNeeded = packetLength + mac.getBlockSize() - inputBuffer.available();
if (bytesNeeded <= 0) {
seq = seq + 1 & 0xffffffffL;
checkMAC(inputBuffer.array());
decryptBuffer(4, packetLength);
inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte());
final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer;
if (log.isTraceEnabled()) {
log.trace("Received packet #{}: {}", seq, plain.printHex());
}
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet
inputBuffer.clear();
packetLength = -1;
} else {
// Needs more data
break;
}
}
}
return bytesNeeded;
}
/**
* Decode a Mac-Then-Encrypt packet
* @return
* @throws SSHException
*/
private int decodeMte() throws SSHException {
private int decode() throws SSHException {
int need;
/* Decoding loop */
for (; ; )
for(;;) {
if (packetLength == -1) { // Waiting for beginning of packet
assert inputBuffer.rpos() == 0 : "buffer cleared";
need = cipherSize - inputBuffer.available();
if (need <= 0) {
packetLength = decryptLength();
if (authMode) {
packetLength = decryptLengthAAD();
} else if (etm) {
packetLength = inputBuffer.readUInt32AsInt();
checkPacketLength(packetLength);
} else {
packetLength = decryptLength();
}
} else {
// Need more data
break;
}
} else {
assert inputBuffer.rpos() == 4 : "packet length read";
need = packetLength + (mac != null ? mac.getBlockSize() : 0) - inputBuffer.available();
need = (authMode) ? packetLength + cipherSize - inputBuffer.available() : packetLength + (mac != null ? mac.getBlockSize() : 0) - inputBuffer.available();
if (need <= 0) {
decryptBuffer(cipherSize, packetLength + 4 - cipherSize); // Decrypt the rest of the payload
seq = seq + 1 & 0xffffffffL;
if (mac != null) {
if (authMode) {
cipher.update(inputBuffer.array(), 4, packetLength);
} else if (etm) {
checkMAC(inputBuffer.array());
decryptBuffer(4, packetLength);
} else {
decryptBuffer(cipherSize, packetLength + 4 - cipherSize); // Decrypt the rest of the payload
if (mac != null) {
checkMAC(inputBuffer.array());
}
}
// Exclude the padding & MAC
inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte());
final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer;
if (log.isTraceEnabled()) {
@@ -160,16 +114,20 @@ final class Decoder
inputBuffer.clear();
packetLength = -1;
} else {
// Need more data
// Needs more data
break;
}
}
}
return need;
}
private void checkMAC(final byte[] data)
throws TransportException {
if (mac == null) {
return;
}
mac.update(seq); // seq num
mac.update(data, 0, packetLength + 4); // packetLength+4 = entire packet w/o mac
mac.doFinal(macResult, 0); // compute
@@ -186,6 +144,20 @@ final class Decoder
return uncompressBuffer;
}
private int decryptLengthAAD() throws TransportException {
cipher.updateAAD(inputBuffer.array(), 0, 4);
final int len;
try {
len = inputBuffer.readUInt32AsInt();
} catch (Buffer.BufferException be) {
throw new TransportException(be);
}
checkPacketLength(len);
return len;
}
private int decryptLength()
throws TransportException {
decryptBuffer(0, cipherSize);
@@ -237,7 +209,9 @@ final class Decoder
@Override
void setAlgorithms(Cipher cipher, MAC mac, Compression compression) {
super.setAlgorithms(cipher, mac, compression);
macResult = new byte[mac.getBlockSize()];
if (mac != null) {
macResult = new byte[mac.getBlockSize()];
}
}
@Override

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.cipher.Cipher;
@@ -83,7 +84,7 @@ final class Encoder
// Compute padding length
int padLen = cipherSize - (lengthWithoutPadding % cipherSize);
if (padLen < 4) {
if (padLen < 4 || (authMode && padLen < cipherSize)) {
padLen += cipherSize;
}
@@ -94,6 +95,14 @@ final class Encoder
padLen += cipherSize;
packetLen = 1 + payloadSize + padLen;
}
/*
* In AES-GCM ciphers, they require packets must be a multiple of 16 bytes (which is also block size of AES)
* as mentioned in RFC5647 Section 7.2. So we are calculating the extra padding as necessary here
*/
if (authMode && packetLen % cipherSize != 0) {
padLen += cipherSize - (packetLen % cipherSize);
packetLen = 1 + payloadSize + padLen;
}
final int endOfPadding = startOfPacket + 4 + packetLen;
@@ -101,6 +110,7 @@ final class Encoder
buffer.wpos(startOfPacket);
buffer.putUInt32(packetLen);
buffer.putByte((byte) padLen);
// Now wpos will mark end of padding
buffer.wpos(endOfPadding);
@@ -109,14 +119,17 @@ final class Encoder
seq = seq + 1 & 0xffffffffL;
if (etm) {
if (authMode) {
int wpos = buffer.wpos();
buffer.wpos(wpos + cipherSize);
aeadOutgoingBuffer(buffer, startOfPacket, packetLen);
} else if (etm) {
cipher.update(buffer.array(), startOfPacket + 4, packetLen);
putMAC(buffer, startOfPacket, endOfPadding);
} else {
if (mac != null) {
putMAC(buffer, startOfPacket, endOfPadding);
}
cipher.update(buffer.array(), startOfPacket, 4 + packetLen);
}
buffer.rpos(startOfPacket); // Make ready-to-read
@@ -127,6 +140,14 @@ final class Encoder
}
}
protected void aeadOutgoingBuffer(Buffer buf, int offset, int len) {
if (cipher == null || cipher.getAuthenticationTagSize() == 0) {
throw new IllegalArgumentException("AEAD mode requires an AEAD cipher");
}
byte[] data = buf.array();
cipher.updateWithAAD(data, offset, 4, len);
}
@Override
void setAlgorithms(Cipher cipher, MAC mac, Compression compression) {
encodeLock.lock();

View File

@@ -15,7 +15,6 @@
*/
package net.schmizz.sshj.transport;
import com.hierynomus.sshj.key.KeyAlgorithm;
import net.schmizz.concurrent.ErrorDeliveryUtil;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.common.*;
@@ -323,13 +322,25 @@ final class KeyExchanger
resizedKey(encryptionKey_S2C, cipher_S2C.getBlockSize(), hash, kex.getK(), kex.getH()),
initialIV_S2C);
final MAC mac_C2S = Factory.Named.Util.create(transport.getConfig().getMACFactories(), negotiatedAlgs
.getClient2ServerMACAlgorithm());
mac_C2S.init(resizedKey(integrityKey_C2S, mac_C2S.getBlockSize(), hash, kex.getK(), kex.getH()));
/*
* For AES-GCM ciphers, MAC will also be AES-GCM, so it is handled by the cipher itself.
* In that case, both s2c and c2s MACs are ignored.
*
* Refer to RFC5647 Section 5.1
*/
MAC mac_C2S = null;
if(cipher_C2S.getAuthenticationTagSize() == 0) {
mac_C2S = Factory.Named.Util.create(transport.getConfig().getMACFactories(), negotiatedAlgs
.getClient2ServerMACAlgorithm());
mac_C2S.init(resizedKey(integrityKey_C2S, mac_C2S.getBlockSize(), hash, kex.getK(), kex.getH()));
}
final MAC mac_S2C = Factory.Named.Util.create(transport.getConfig().getMACFactories(),
negotiatedAlgs.getServer2ClientMACAlgorithm());
mac_S2C.init(resizedKey(integrityKey_S2C, mac_S2C.getBlockSize(), hash, kex.getK(), kex.getH()));
MAC mac_S2C = null;
if(cipher_S2C.getAuthenticationTagSize() == 0) {
mac_S2C = Factory.Named.Util.create(transport.getConfig().getMACFactories(),
negotiatedAlgs.getServer2ClientMACAlgorithm());
mac_S2C.init(resizedKey(integrityKey_S2C, mac_S2C.getBlockSize(), hash, kex.getK(), kex.getH()));
}
final Compression compression_S2C =
Factory.Named.Util.create(transport.getConfig().getCompressionFactories(),

View File

@@ -42,7 +42,7 @@ public abstract class BaseCipher
private final String algorithm;
private final String transformation;
private javax.crypto.Cipher cipher;
protected javax.crypto.Cipher cipher;
public BaseCipher(int ivsize, int bsize, String algorithm, String transformation) {
this.ivsize = ivsize;
@@ -61,6 +61,11 @@ public abstract class BaseCipher
return ivsize;
}
@Override
public int getAuthenticationTagSize() {
return 0;
}
@Override
public void init(Mode mode, byte[] key, byte[] iv) {
key = BaseCipher.resize(key, bsize);
@@ -75,6 +80,7 @@ public abstract class BaseCipher
}
protected abstract void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException;
protected SecretKeySpec getKeySpec(byte[] key) {
return new SecretKeySpec(key, algorithm);
}
@@ -92,4 +98,19 @@ public abstract class BaseCipher
}
}
@Override
public void updateAAD(byte[] data, int offset, int length) {
throw new UnsupportedOperationException(getClass() + " does not support AAD operations");
}
@Override
public void updateAAD(byte[] data) {
updateAAD(data, 0, data.length);
}
@Override
public void updateWithAAD(byte[] input, int offset, int aadLen, int inputLen) {
updateAAD(input, offset, aadLen);
update(input, offset + aadLen, inputLen);
}
}

View File

@@ -29,6 +29,9 @@ public interface Cipher {
/** @return the size of the initialization vector */
int getIVSize();
/** @return Size of the authentication tag (AT) in bytes or 0 if this cipher does not support authentication */
int getAuthenticationTagSize();
/**
* Initialize the cipher for encryption or decryption with the given private key and initialization vector
*
@@ -47,4 +50,32 @@ public interface Cipher {
*/
void update(byte[] input, int inputOffset, int inputLen);
/**
* Adds the provided input data as additional authenticated data during encryption or decryption.
*
* @param data The additional data to authenticate
* @param offset The offset of the additional data in the buffer
* @param length The number of bytes in the buffer to use for authentication
*/
void updateAAD(byte[] data, int offset, int length);
/**
* Adds the provided input data as additional authenticated data during encryption or decryption.
*
* @param data The data to authenticate
*/
void updateAAD(byte[] data);
/**
* Performs in-place authenticated encryption or decryption with additional data (AEAD). Authentication tags are
* implicitly appended after the output ciphertext or implicitly verified after the input ciphertext. Header data
* indicated by the {@code aadLen} parameter are authenticated but not encrypted/decrypted, while payload data
* indicated by the {@code inputLen} parameter are authenticated and encrypted/decrypted.
*
* @param input The input/output bytes
* @param offset The offset of the data in the input buffer
* @param aadLen The number of bytes to use as additional authenticated data - starting at offset
* @param inputLen The number of bytes to update - starting at offset + aadLen
*/
void updateWithAAD(byte[] input, int offset, int aadLen, int inputLen);
}

View File

@@ -44,6 +44,11 @@ public class NoneCipher
return 8;
}
@Override
public int getAuthenticationTagSize() {
return 0;
}
@Override
public void init(Mode mode, byte[] bytes, byte[] bytes1) {
// Nothing to do
@@ -54,4 +59,18 @@ public class NoneCipher
// Nothing to do
}
@Override
public void updateAAD(byte[] data, int offset, int length) {
}
@Override
public void updateAAD(byte[] data) {
}
@Override
public void updateWithAAD(byte[] input, int offset, int aadLen, int inputLen) {
}
}

View File

@@ -29,8 +29,7 @@ import java.security.GeneralSecurityException;
* Base class for DHG key exchange algorithms. Implementations will only have to configure the required data on the
* {@link DH} class in the
*/
public abstract class AbstractDHG extends AbstractDH
implements KeyExchange {
public abstract class AbstractDHG extends AbstractDH {
private final Logger log = LoggerFactory.getLogger(getClass());

View File

@@ -57,11 +57,12 @@ public abstract class AbstractDHGex extends AbstractDH {
return parseGexGroup(buffer);
case KEX_DH_GEX_REPLY:
return parseGexReply(buffer);
default:
throw new TransportException("Unexpected message " + msg);
}
} catch (Buffer.BufferException be) {
throw new TransportException(be);
}
throw new TransportException("Unexpected message " + msg);
}
private boolean parseGexReply(SSHPacket buffer) throws Buffer.BufferException, GeneralSecurityException, TransportException {

View File

@@ -19,9 +19,9 @@ import net.schmizz.sshj.transport.digest.Digest;
import net.schmizz.sshj.transport.digest.SHA256;
import net.schmizz.sshj.transport.digest.SHA384;
import net.schmizz.sshj.transport.digest.SHA512;
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
import java.security.GeneralSecurityException;
import java.security.spec.ECGenParameterSpec;
public class ECDHNistP extends AbstractDHG {
@@ -33,7 +33,7 @@ public class ECDHNistP extends AbstractDHG {
@Override
public KeyExchange create() {
return new ECDHNistP("P-521", new SHA512());
return new ECDHNistP("secp521r1", new SHA512());
}
@Override
@@ -48,7 +48,7 @@ public class ECDHNistP extends AbstractDHG {
@Override
public KeyExchange create() {
return new ECDHNistP("P-384", new SHA384());
return new ECDHNistP("secp384r1", new SHA384());
}
@Override
@@ -63,7 +63,7 @@ public class ECDHNistP extends AbstractDHG {
@Override
public KeyExchange create() {
return new ECDHNistP("P-256", new SHA256());
return new ECDHNistP("secp256r1", new SHA256());
}
@Override
@@ -79,7 +79,7 @@ public class ECDHNistP extends AbstractDHG {
@Override
protected void initDH(DHBase dh) throws GeneralSecurityException {
dh.init(new ECNamedCurveGenParameterSpec(curve), trans.getConfig().getRandomFactory());
dh.init(new ECGenParameterSpec(curve), trans.getConfig().getRandomFactory());
}
}

View File

@@ -99,7 +99,7 @@ public class PKCS5KeyFile extends BaseFileKeyProvider {
} else if ("DSS".equals(s)) {
type = KeyType.DSA;
} else {
throw new FormatException("Unrecognized PKCS5 key type: " + s);
throw new FormatException("Unrecognized PKCS5 key type");
}
} else {
throw new FormatException("Bad header; possibly PKCS8 format?");
@@ -109,12 +109,12 @@ public class PKCS5KeyFile extends BaseFileKeyProvider {
} else if (type != null) {
if (line.startsWith("Proc-Type: ")) {
if (!"4,ENCRYPTED".equals(line.substring(11))) {
throw new FormatException("Unrecognized Proc-Type: " + 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: " + line.substring(10));
throw new FormatException("Unrecognized DEK-Info");
} else {
String algorithm = line.substring(10, ptr);
if ("DES-EDE3-CBC".equals(algorithm)) {

View File

@@ -16,9 +16,20 @@
package net.schmizz.sshj.userauth.keyprovider;
import com.hierynomus.sshj.common.KeyAlgorithm;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.KeyType;
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.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Cipher;
@@ -101,17 +112,17 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
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 KeyReader publicKeyReader = new KeyReader(publicKey);
publicKeyReader.skip(); // skip this
// public key exponent
BigInteger e = publicKeyReader.readInt();
BigInteger e = publicKeyReader.readMPInt();
// modulus
BigInteger n = publicKeyReader.readInt();
BigInteger n = publicKeyReader.readMPInt();
final KeyReader privateKeyReader = new KeyReader(privateKey);
// private key exponent
BigInteger d = privateKeyReader.readInt();
BigInteger d = privateKeyReader.readMPInt();
final KeyFactory factory;
try {
@@ -129,16 +140,13 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
}
}
if (KeyType.DSA.equals(this.getType())) {
final KeyReader publicKeyReader = new KeyReader(publicKey);
publicKeyReader.skip(); // skip this
BigInteger p = publicKeyReader.readInt();
BigInteger q = publicKeyReader.readInt();
BigInteger g = publicKeyReader.readInt();
BigInteger y = publicKeyReader.readInt();
BigInteger p = publicKeyReader.readMPInt();
BigInteger q = publicKeyReader.readMPInt();
BigInteger g = publicKeyReader.readMPInt();
BigInteger y = publicKeyReader.readMPInt();
final KeyReader privateKeyReader = new KeyReader(privateKey);
// Private exponent from the private key
BigInteger x = privateKeyReader.readInt();
BigInteger x = privateKeyReader.readMPInt();
final KeyFactory factory;
try {
@@ -154,9 +162,42 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
} catch (InvalidKeySpecException e) {
throw new IOException(e.getMessage(), e);
}
} else {
throw new IOException(String.format("Unknown key type %s", this.getType()));
}
if (KeyType.ED25519.equals(this.getType())) {
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()) {
case ECDSA256:
ecdsaCurve = "P-256";
break;
case ECDSA384:
ecdsaCurve = "P-384";
break;
case ECDSA521:
ecdsaCurve = "P-521";
break;
default:
ecdsaCurve = null;
break;
}
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());
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
try {
PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
return new KeyPair(getType().readPubKeyFromBuffer(publicKeyReader), privateKey);
} catch (GeneralSecurityException e) {
throw new IOException(e.getMessage(), e);
}
}
throw new IOException(String.format("Unknown key type %s", this.getType()));
}
protected void parseKeyPair() throws IOException {
@@ -297,40 +338,4 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
throw new IOException(e.getMessage(), e);
}
}
/**
* Parses the putty key bit vector, which is an encoded sequence
* of {@link java.math.BigInteger}s.
*/
private final static class KeyReader {
private final DataInput di;
public KeyReader(byte[] key) {
this.di = new DataInputStream(new ByteArrayInputStream(key));
}
/**
* Skips an integer without reading it.
*/
public void skip() throws IOException {
final int read = di.readInt();
if (read != di.skipBytes(read)) {
throw new IOException(String.format("Failed to skip %d bytes", read));
}
}
private byte[] read() throws IOException {
int len = di.readInt();
byte[] r = new byte[len];
di.readFully(r);
return r;
}
/**
* Reads the next integer.
*/
public BigInteger readInt() throws IOException {
return new BigInteger(read());
}
}
}

View File

@@ -16,17 +16,14 @@
package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.transport.DisconnectListener;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.theories.suppliers.TestedOn;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import java.security.Security;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
/**
* Unit test to decrypt SSH traffic with OpenSSH and Apache Mina SSHD (master) using AES-GCM ciphers, for verifying
* cipher behaviour.
*/
@RunWith(Theories.class)
public class GcmCipherDecryptSshPacketTest {
@DataPoints
public static final String sets[][] = new String[][]{{"mina-sshd", "3"}, {"openssh", "4"}};
@BeforeClass
public static void setupBeforeClass() {
Security.addProvider(new BouncyCastleProvider());
}
@Theory
public void testDecryptPacket(String[] args) throws Exception {
ClassLoader classLoader = getClass().getClassLoader();
byte[] iv = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/s2c.iv.bin")).toByteArray();
byte[] key = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/s2c.key.bin")).toByteArray();
Cipher cipher = GcmCiphers.AES128GCM().create();
cipher.init(Cipher.Mode.Decrypt, key, iv);
for(int i=1; i<=Integer.parseInt(args[1]); i++) {
byte[] data = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/client.receive."+i+".bin")).toByteArray();
SSHPacket inputBuffer = new SSHPacket(data);
cipher.updateAAD(inputBuffer.array(), 0, 4);
int size = inputBuffer.readUInt32AsInt();
cipher.update(inputBuffer.array(), 4, size);
byte[] expected = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/"+args[0]+"/client.decrypted."+i+".bin")).toByteArray();
assertArrayEquals(Arrays.copyOfRange(expected, 0, size+4),
Arrays.copyOfRange(inputBuffer.array(), 0, size+4));
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import javax.crypto.AEADBadTagException;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.*;
@RunWith(Theories.class)
public class GcmCipherTest {
public static final @DataPoints
GcmCiphers.Factory[] cipherFactories = { GcmCiphers.AES128GCM(), GcmCiphers.AES256GCM() };
@Theory
public void testEncryptDecrypt(GcmCiphers.Factory factory) throws Exception {
Cipher enc = factory.create();
byte[] key = new byte[enc.getBlockSize()];
byte[] iv = new byte[enc.getIVSize()];
enc.init(Cipher.Mode.Encrypt, key, iv);
byte[] aad = getClass().getName().getBytes(StandardCharsets.UTF_8);
enc.updateAAD(aad);
String plaintext = "[Secret authenticated message using AES-GCM";
byte[] ptBytes = plaintext.getBytes(StandardCharsets.UTF_8);
byte[] output = new byte[ptBytes.length + enc.getAuthenticationTagSize()];
System.arraycopy(ptBytes, 0, output, 0, ptBytes.length);
enc.update(output, 0, ptBytes.length);
Cipher dec = factory.create();
dec.init(Cipher.Mode.Decrypt, key, iv);
dec.updateAAD(aad);
byte[] input = output.clone();
dec.update(input, 0, ptBytes.length);
assertEquals(getClass().getName(), new String(aad, StandardCharsets.UTF_8));
assertEquals(plaintext, new String(input, 0, ptBytes.length, StandardCharsets.UTF_8));
byte[] corrupted = output.clone();
corrupted[corrupted.length - 1] += 1;
Cipher failingDec = factory.create();
failingDec.init(Cipher.Mode.Decrypt, key, iv);
try {
failingDec.updateAAD(aad.clone());
failingDec.update(corrupted, 0, ptBytes.length);
fail("Modified authentication tag should not validate");
} catch (SSHRuntimeException e) {
assertNotNull(e);
assertEquals(AEADBadTagException.class, e.getCause().getClass());
}
}
}

View File

@@ -19,7 +19,6 @@ import com.hierynomus.sshj.test.BaseAlgorithmTest;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.transport.kex.Curve25519SHA256;
import net.schmizz.sshj.transport.kex.DHGexSHA1;
import net.schmizz.sshj.transport.kex.DHGexSHA256;
import net.schmizz.sshj.transport.kex.ECDHNistP;

View File

@@ -20,8 +20,6 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.junit.Assert.fail;
public class LoadsOfConnects {
@@ -46,4 +44,4 @@ public class LoadsOfConnects {
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
public class ParametersTest {
@Test
public void testConstructedFields() {
final Parameters p = new Parameters("127.0.0.1", 8080, "github.com", 80);
assertEquals("127.0.0.1", p.getLocalHost());
assertEquals(8080, p.getLocalPort());
assertEquals("github.com", p.getRemoteHost());
assertEquals(80, p.getRemotePort());
}
@Test
public void testFieldsEquality() {
final Parameters first = new Parameters("127.0.0.1", 8080, "github.com", 80);
final Parameters second = new Parameters("127.0.0.1", 8080, "github.com", 80);
assertEquals(first.getLocalHost(), second.getLocalHost());
assertEquals(first.getLocalPort(), second.getLocalPort());
assertEquals(first.getRemoteHost(), second.getRemoteHost());
assertEquals(first.getRemotePort(), second.getRemotePort());
}
@Test
public void testInstanceIdentity() {
final Parameters first = new Parameters("127.0.0.1", 8080, "github.com", 80);
final Parameters second = new Parameters("127.0.0.1", 8080, "github.com", 80);
assertTrue(first == first);
assertTrue(second == second);
assertFalse(first == second);
}
@Test
public void testInstanceEquality() {
final Parameters first = new Parameters("127.0.0.1", 8080, "github.com", 80);
final Parameters second = new Parameters("127.0.0.1", 8080, "github.com", 80);
assertFalse(first == second);
assertTrue(first.equals(second));
assertTrue(second.equals(first));
assertEquals(first, second);
}
@Test
public void testHashCode() {
final Parameters first = new Parameters("127.0.0.1", 8080, "github.com", 80);
final Parameters second = new Parameters("127.0.0.1", 8080, "github.com", 80);
assertEquals(first.hashCode(), first.hashCode());
assertEquals(first.hashCode(), second.hashCode());
assertEquals(second.hashCode(), second.hashCode());
final Parameters third = new Parameters("127.0.0.1", 443, "github.com", 80);
assertEquals(third.hashCode(), third.hashCode());
assertNotEquals(first.hashCode(), third.hashCode());
}
@Test
public void testHashMapApplicability() {
final Parameters first = new Parameters("127.0.0.1", 8080, "github.com", 80);
final Map<Parameters, String> map = new HashMap<>();
assertFalse(map.containsKey(first));
final String none = map.put(first, "is now in the map");
assertNull(none);
assertTrue(map.containsKey(first));
assertEquals("is now in the map", map.get(first));
final Parameters second = new Parameters("127.0.0.1", 8080, "github.com", 80);
assertTrue(map.containsKey(second));
assertEquals("is now in the map", map.get(second));
final String current = map.putIfAbsent(second, "is again in the map");
assertEquals("is now in the map", current);
assertEquals("is now in the map", map.get(first));
assertEquals("is now in the map", map.get(second));
final String previous = map.put(second, "is again in the map");
assertEquals("is now in the map", previous);
assertEquals("is again in the map", map.get(first));
assertEquals("is again in the map", map.get(second));
final Parameters third = new Parameters("127.0.0.1", 443, "github.com", 80);
assertFalse(map.containsKey(third));
assertNull(map.get(third));
}
@Test
public void testToString() {
final Parameters first = new Parameters("127.0.0.1", 8080, "github.com", 443);
assertNotNull(first.toString());
assertFalse(first.toString().isBlank());
assertTrue(first.toString().contains("127.0.0.1"));
assertTrue(first.toString().contains("8080"));
assertTrue(first.toString().contains("github.com"));
assertTrue(first.toString().contains("443"));
}
}

View File

@@ -15,14 +15,18 @@
*/
package net.schmizz.sshj.keyprovider;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.Resource;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -246,6 +250,97 @@ public class PuTTYKeyFileTest {
assertNotNull(key.getPublic());
}
@Test
public void testEd25519() throws Exception {
// Generated with
// puttygen src/test/resources/keytypes/test_ed25519 -O private \
// -o src/test/resources/keytypes/test_ed25519_puttygen.ppk
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen.ppk"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_ed25519"));
assertEquals(key.getPrivate(), referenceKey.getPrivate());
assertEquals(key.getPublic(), referenceKey.getPublic());
}
@Test
public void testEd25519Encrypted() throws Exception {
// Generated with
// puttygen src/test/resources/keytypes/test_ed25519 -O private \
// -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \
// --new-passphrase <(echo 123456)
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"), new PasswordFinder() {
@Override
public char[] reqPassword(Resource<?> resource) {
return "123456".toCharArray();
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return false;
}
});
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_ed25519"));
assertEquals(key.getPrivate(), referenceKey.getPrivate());
assertEquals(key.getPublic(), referenceKey.getPublic());
}
@Test
public void testEcDsa256() throws Exception {
// Generated with
// puttygen src/test/resources/keytypes/test_ecdsa_nistp256 -O private \
// -o src/test/resources/keytypes/test_ecdsa_nistp256_puttygen.ppk
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256_puttygen.ppk"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
PKCS8KeyFile referenceKey = new PKCS8KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
assertEquals(key.getPrivate(), referenceKey.getPrivate());
assertEquals(key.getPublic(), referenceKey.getPublic());
}
@Test
public void testEcDsa384() throws Exception {
// Generated with
// puttygen src/test/resources/keytypes/test_ecdsa_nistp384_2 -O private \
// -o src/test/resources/keytypes/test_ecdsa_nistp384_2_puttygen.ppk
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384_2_puttygen.ppk"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384_2"));
assertEquals(key.getPrivate(), referenceKey.getPrivate());
assertEquals(key.getPublic(), referenceKey.getPublic());
}
@Test
public void testEcDsa521() throws Exception {
// Generated with
// puttygen src/test/resources/keytypes/test_ecdsa_nistp521_2 -O private \
// -o src/test/resources/keytypes/test_ecdsa_nistp521_2_puttygen.ppk
PuTTYKeyFile key = new PuTTYKeyFile();
key.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521_2_puttygen.ppk"));
assertNotNull(key.getPrivate());
assertNotNull(key.getPublic());
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521_2"));
assertEquals(key.getPrivate(), referenceKey.getPrivate());
assertEquals(key.getPublic(), referenceKey.getPublic());
}
@Test
public void testCorrectPassphraseRsa() throws Exception {
PuTTYKeyFile key = new PuTTYKeyFile();

View File

@@ -57,7 +57,11 @@ public class SFTPClientTest {
@Test
public void doesNotTryToCreateDirectoryTwiceWhenPathHasTrailingSeparator() throws Exception {
SFTPClient client = new SFTPClient(sftpEngine);
client.mkdirs("/folder/directory/");
verify(sftpEngine, times(1)).makeDir("/folder/directory");
try {
client.mkdirs("/folder/directory/");
verify(sftpEngine, times(1)).makeDir("/folder/directory");
} finally {
client.close();
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.transport;
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.cipher.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Before;
import org.junit.Test;
import java.security.SecureRandom;
import java.security.Security;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
public class DecoderDecryptGcmCipherSshPacketTest {
private int PACKET_LENGTH;
private byte[] key;
private byte[] iv;
private byte[] data;
private byte[] decrypted;
private Decoder decoder;
@Before
public void setUp() throws Exception {
Security.addProvider(new BouncyCastleProvider());
ClassLoader classLoader = DecoderDecryptGcmCipherSshPacketTest.class.getClassLoader();
iv = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/mina-sshd/s2c.iv.bin" )).toByteArray();
key = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/mina-sshd/s2c.key.bin" )).toByteArray();
data = IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/mina-sshd/client.receive.1.bin" )).toByteArray();
SSHPacket packet = new SSHPacket(IOUtils.readFully(classLoader.getResourceAsStream("ssh-packets/gcm/mina-sshd/client.decrypted.1.bin" )).toByteArray());
PACKET_LENGTH = packet.readUInt32AsInt();
decrypted = new byte[PACKET_LENGTH];
System.arraycopy(packet.array(), 0, decrypted, 0, PACKET_LENGTH);
Config config = mock(Config.class);
Transport transport = mock(Transport.class);
when(transport.getConfig()).thenReturn(config);
when(config.getLoggerFactory()).thenReturn(LoggerFactory.DEFAULT);
doAnswer(invocation -> {
SSHPacket p = invocation.getArgument(1);
byte[] verify = new byte[PACKET_LENGTH];
System.arraycopy(p.array(), 0, verify, 0, PACKET_LENGTH);
assertArrayEquals(decrypted, verify);
return null;
}).when(transport).handle(any(), any());
decoder = new Decoder(transport);
Cipher cipher = GcmCiphers.AES128GCM().create();
cipher.init(Cipher.Mode.Decrypt, key, iv);
decoder.setAlgorithms(cipher, null, null);
}
@Test
public void testDecodeInOneGo() throws SSHException {
decoder.received(data, data.length);
}
@Test
public void testDecodeInConstantChunks() throws SSHException {
int chunkSize = 16;
int remain = PACKET_LENGTH;
int pos = 0;
while(remain >= 0) {
byte[] chunk = new byte[chunkSize];
System.arraycopy(data, pos, chunk, 0, chunkSize);
decoder.received(chunk, chunk.length);
pos += chunkSize;
remain -= chunkSize;
}
}
@Test
public void testDecodeInRandomChunks() throws SSHException {
SecureRandom sr = new SecureRandom();
int remain = PACKET_LENGTH;
int pos = 0;
while(remain >= 0) {
int chunkSize = sr.nextInt(10);
if (chunkSize - remain < 0)
chunkSize = remain;
byte[] chunk = new byte[chunkSize];
System.arraycopy(data, pos, chunk, 0, chunkSize);
decoder.received(chunk, chunk.length);
pos += chunkSize;
remain -= chunkSize;
}
}
}

View File

@@ -21,6 +21,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@@ -33,7 +35,7 @@ public class BaseMacTest {
@Test
public void testResizeTooBigKeys() {
BaseMAC hmac = new HMACSHA1();
BaseMAC hmac = Macs.HMACSHA1().create();
hmac.init((KEY + "foo").getBytes(CHARSET));
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
@@ -82,8 +84,8 @@ public class BaseMacTest {
}
private BaseMAC initHmac() {
BaseMAC hmac = new HMACSHA1();
BaseMAC hmac = Macs.HMACSHA1().create();
hmac.init(KEY.getBytes(CHARSET));
return hmac;
}
}
}

View File

@@ -20,6 +20,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -30,29 +32,29 @@ public class HMACMD596Test {
@Test
public void testUpdateWithDoFinal() {
HMACMD596 hmac = initHmac();
BaseMAC hmac = initHmac();
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
}
@Test
public void testDoFinalWithInput() {
HMACMD596 hmac = initHmac();
BaseMAC hmac = initHmac();
assertThat(Hex.toHexString(hmac.doFinal(PLAIN_TEXT)),
is(EXPECTED_HMAC));
}
@Test
public void testUpdateWithDoFinalWithResultBuffer() {
HMACMD596 hmac = initHmac();
BaseMAC hmac = initHmac();
byte[] resultBuf = new byte[12];
hmac.update(PLAIN_TEXT);
hmac.doFinal(resultBuf, 0);
assertThat(Hex.toHexString(resultBuf), is(EXPECTED_HMAC));
}
private HMACMD596 initHmac() {
HMACMD596 hmac = new HMACMD596();
private BaseMAC initHmac() {
BaseMAC hmac = Macs.HMACMD596().create();
hmac.init("ohBahfei6pee5dai".getBytes(CHARSET));
return hmac;
}

View File

@@ -20,6 +20,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -30,28 +32,28 @@ public class HMACMD5Test {
@Test
public void testUpdateWithDoFinal() {
HMACMD5 hmac = initHmac();
BaseMAC hmac = initHmac();
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
}
@Test
public void testDoFinalWithInput() {
HMACMD5 hmac = initHmac();
BaseMAC hmac = initHmac();
assertThat(Hex.toHexString(hmac.doFinal(PLAIN_TEXT)), is(EXPECTED_HMAC));
}
@Test
public void testUpdateWithDoFinalWithResultBuffer() {
HMACMD5 hmac = initHmac();
BaseMAC hmac = initHmac();
byte[] resultBuf = new byte[16];
hmac.update(PLAIN_TEXT);
hmac.doFinal(resultBuf, 0);
assertThat(Hex.toHexString(resultBuf), is(EXPECTED_HMAC));
}
private HMACMD5 initHmac() {
HMACMD5 hmac = new HMACMD5();
private BaseMAC initHmac() {
BaseMAC hmac = Macs.HMACMD5().create();
hmac.init("ohBahfei6pee5dai".getBytes(CHARSET));
return hmac;
}

View File

@@ -20,6 +20,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -30,28 +32,28 @@ public class HMACSHA196Test {
@Test
public void testUpdateWithDoFinal() {
HMACSHA196 hmac = initHmac();
BaseMAC hmac = initHmac();
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
}
@Test
public void testDoFinalWithInput() {
HMACSHA196 hmac = initHmac();
BaseMAC hmac = initHmac();
assertThat(Hex.toHexString(hmac.doFinal(PLAIN_TEXT)), is(EXPECTED_HMAC));
}
@Test
public void testUpdateWithDoFinalWithResultBuffer() {
HMACSHA196 hmac = initHmac();
BaseMAC hmac = initHmac();
byte[] resultBuf = new byte[12];
hmac.update(PLAIN_TEXT);
hmac.doFinal(resultBuf, 0);
assertThat(Hex.toHexString(resultBuf), is(EXPECTED_HMAC));
}
private HMACSHA196 initHmac() {
HMACSHA196 hmac = new HMACSHA196();
private BaseMAC initHmac() {
BaseMAC hmac = Macs.HMACSHA196().create();
hmac.init("et1Quo5ooCie6theel8i".getBytes(CHARSET));
return hmac;
}

View File

@@ -20,6 +20,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -30,29 +32,29 @@ public class HMACSHA1Test {
@Test
public void testUpdateWithDoFinal() {
HMACSHA1 hmac = initHmac();
BaseMAC hmac = initHmac();
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
}
@Test
public void testDoFinalWithInput() {
HMACSHA1 hmac = initHmac();
BaseMAC hmac = initHmac();
assertThat(Hex.toHexString(hmac.doFinal(PLAIN_TEXT)), is(EXPECTED_HMAC));
}
@Test
public void testUpdateWithDoFinalWithResultBuffer() {
HMACSHA1 hmac = initHmac();
BaseMAC hmac = initHmac();
byte[] resultBuf = new byte[20];
hmac.update(PLAIN_TEXT);
hmac.doFinal(resultBuf, 0);
assertThat(Hex.toHexString(resultBuf), is(EXPECTED_HMAC));
}
private HMACSHA1 initHmac() {
HMACSHA1 hmac = new HMACSHA1();
private BaseMAC initHmac() {
BaseMAC hmac = Macs.HMACSHA1().create();
hmac.init("et1Quo5ooCie6theel8i".getBytes(CHARSET));
return hmac;
}
}
}

View File

@@ -20,6 +20,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -30,29 +32,29 @@ public class HMACSHA2256Test {
@Test
public void testUpdateWithDoFinal() {
HMACSHA2256 hmac = initHmac();
BaseMAC hmac = initHmac();
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
}
@Test
public void testDoFinalWithInput() {
HMACSHA2256 hmac = initHmac();
BaseMAC hmac = initHmac();
assertThat(Hex.toHexString(hmac.doFinal(PLAIN_TEXT)), is(EXPECTED_HMAC));
}
@Test
public void testUpdateWithDoFinalWithResultBuffer() {
HMACSHA2256 hmac = initHmac();
BaseMAC hmac = initHmac();
byte[] resultBuf = new byte[32];
hmac.update(PLAIN_TEXT);
hmac.doFinal(resultBuf, 0);
assertThat(Hex.toHexString(resultBuf), is(EXPECTED_HMAC));
}
private HMACSHA2256 initHmac() {
HMACSHA2256 hmac = new HMACSHA2256();
private BaseMAC initHmac() {
BaseMAC hmac = Macs.HMACSHA2256().create();
hmac.init("koopiegh4reengah1que9Wiew7ohahPh".getBytes(CHARSET));
return hmac;
}
}
}

View File

@@ -20,6 +20,8 @@ import org.junit.Test;
import java.nio.charset.Charset;
import com.hierynomus.sshj.transport.mac.Macs;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -30,28 +32,28 @@ public class HMACSHA2512Test {
@Test
public void testUpdateWithDoFinal() {
HMACSHA2512 hmac = initHmac();
BaseMAC hmac = initHmac();
hmac.update(PLAIN_TEXT);
assertThat(Hex.toHexString(hmac.doFinal()), is(EXPECTED_HMAC));
}
@Test
public void testDoFinalWithInput() {
HMACSHA2512 hmac = initHmac();
BaseMAC hmac = initHmac();
assertThat(Hex.toHexString(hmac.doFinal(PLAIN_TEXT)), is(EXPECTED_HMAC));
}
@Test
public void testUpdateWithDoFinalWithResultBuffer() {
HMACSHA2512 hmac = initHmac();
BaseMAC hmac = initHmac();
byte[] resultBuf = new byte[64];
hmac.update(PLAIN_TEXT);
hmac.doFinal(resultBuf, 0);
assertThat(Hex.toHexString(resultBuf), is(EXPECTED_HMAC));
}
private HMACSHA2512 initHmac() {
HMACSHA2512 hmac = new HMACSHA2512();
private BaseMAC initHmac() {
BaseMAC hmac = Macs.HMACSHA2512().create();
hmac.init("paishiengu1jaeTie5OoTu2eib7Kohqueicie7ahLohfoothahpeivi5weik1EiB".getBytes(CHARSET));
return hmac;
}

View File

@@ -28,8 +28,8 @@ import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
public class SCPFileTransferTest {
@@ -55,7 +55,7 @@ public class SCPFileTransferTest {
sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("test", "test");
}
@After
public void cleanup() {
if (targetFile.exists()) {

View File

@@ -0,0 +1,10 @@
PuTTY-User-Key-File-2: ecdsa-sha2-nistp256
Encryption: none
Comment: imported-openssh-key
Public-Lines: 3
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOEQcvowiV3i
gdRO7rKPrZrao1hCQrnC4tgsxqSJdQCbABI+vHrdbJRfWZNuSk48aAtARJzJVmkn
/r63EPJgkh8=
Private-Lines: 1
AAAAIQCVDJbEpV6gmZgo5TeJFe4cz/qfabtH8CfK+JtapXufEg==
Private-MAC: 48f3a17cf5f65f4f225e7a21f007d8270d7c8c8f

View File

@@ -0,0 +1,10 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTItEGNGyMGn9tCIM4oC3fpU7jVxDQP
RRkB/Qv8lfM4mmSuYLPcakV6av0ATlM6mKD/TObWQNOJAYzp3MsUn1EMgVLe/sd9TY/hP6
8Vn+zumMqjmtdX70Ty5ftEoH9zBlgAAADYhfSye4X0snsAAAATZWNkc2Etc2hhMi1uaXN0
cDM4NAAAAAhuaXN0cDM4NAAAAGEEyLRBjRsjBp/bQiDOKAt36VO41cQ0D0UZAf0L/JXzOJ
pkrmCz3GpFemr9AE5TOpig/0zm1kDTiQGM6dzLFJ9RDIFS3v7HfU2P4T+vFZ/s7pjKo5rX
V+9E8uX7RKB/cwZYAAAAMGvH38HMnj6cELCBVQnAQYHlA/Vz1+RVZHj08cey/P3PALx7MR
pV135UZNZAtWQm+wAAAAlyb290QHNzaGoBAgMEBQYH
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMi0QY0bIwaf20IgzigLd+lTuNXENA9FGQH9C/yV8ziaZK5gs9xqRXpq/QBOUzqYoP9M5tZA04kBjOncyxSfUQyBUt7+x31Nj+E/rxWf7O6YyqOa11fvRPLl+0Sgf3MGWA== root@sshj

View File

@@ -0,0 +1,11 @@
PuTTY-User-Key-File-2: ecdsa-sha2-nistp384
Encryption: none
Comment: root@sshj
Public-Lines: 3
AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMi0QY0bIwaf
20IgzigLd+lTuNXENA9FGQH9C/yV8ziaZK5gs9xqRXpq/QBOUzqYoP9M5tZA04kB
jOncyxSfUQyBUt7+x31Nj+E/rxWf7O6YyqOa11fvRPLl+0Sgf3MGWA==
Private-Lines: 2
AAAAMGvH38HMnj6cELCBVQnAQYHlA/Vz1+RVZHj08cey/P3PALx7MRpV135UZNZA
tWQm+w==
Private-MAC: aa4d48441934e15491af0a30f75a02f4e324e652

View File

@@ -0,0 +1,12 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA3ilD2XkhjkSuEj8KcIXWjhjKSOfQ
QEZBFZyoPT4QV8oRiGT1NRVcN86Paymq8M8WgANFVEAZp7eDqTnsKJ6LEpoAM93DJa1ERO
RWwSeDTDy5GIxMDYgg+CKZVhAMJmS/iavsSXyKUf1ibYo9b5S8y8rpzvmiRg/dQGkfloJR
BLu7czAAAAEI8uaocPLmqHAAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
AAAIUEAN4pQ9l5IY5ErhI/CnCF1o4Yykjn0EBGQRWcqD0+EFfKEYhk9TUVXDfOj2spqvDP
FoADRVRAGae3g6k57CieixKaADPdwyWtRETkVsEng0w8uRiMTA2IIPgimVYQDCZkv4mr7E
l8ilH9Ym2KPW+UvMvK6c75okYP3UBpH5aCUQS7u3MwAAAAQSlrwjeSrVTc6OyiA3OTfac4
+3nKcf/PRSjIhOLsGUIs2pVCxGYP8/ZfbVfkv7nHMn5Cc0fDZEs2cSWi2QhVKBSfAAAACX
Jvb3RAc3NoagEC
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADeKUPZeSGORK4SPwpwhdaOGMpI59BARkEVnKg9PhBXyhGIZPU1FVw3zo9rKarwzxaAA0VUQBmnt4OpOewonosSmgAz3cMlrURE5FbBJ4NMPLkYjEwNiCD4IplWEAwmZL+Jq+xJfIpR/WJtij1vlLzLyunO+aJGD91AaR+WglEEu7tzMA== root@sshj

View File

@@ -0,0 +1,12 @@
PuTTY-User-Key-File-2: ecdsa-sha2-nistp521
Encryption: none
Comment: root@sshj
Public-Lines: 4
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADeKUPZeSGO
RK4SPwpwhdaOGMpI59BARkEVnKg9PhBXyhGIZPU1FVw3zo9rKarwzxaAA0VUQBmn
t4OpOewonosSmgAz3cMlrURE5FbBJ4NMPLkYjEwNiCD4IplWEAwmZL+Jq+xJfIpR
/WJtij1vlLzLyunO+aJGD91AaR+WglEEu7tzMA==
Private-Lines: 2
AAAAQSlrwjeSrVTc6OyiA3OTfac4+3nKcf/PRSjIhOLsGUIs2pVCxGYP8/ZfbVfk
v7nHMn5Cc0fDZEs2cSWi2QhVKBSf
Private-MAC: 052d1a2fe2c5837aec9dbe0bf10f2ccc376eda43

View File

@@ -0,0 +1,9 @@
PuTTY-User-Key-File-2: ssh-ed25519
Encryption: none
Comment: root@sshj
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPo
WICJ
Private-Lines: 1
AAAAIKaxyRDJxad8ZArpe1ClowY4NsCQxA50k0rpclKKkHt0
Private-MAC: 388f807649f181243015cad9650633ec28b25208

View File

@@ -0,0 +1,9 @@
PuTTY-User-Key-File-2: ssh-ed25519
Encryption: aes256-cbc
Comment: root@sshj
Public-Lines: 2
AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPo
WICJ
Private-Lines: 1
XFJyRzRt5NjuCVhDEyb50sI+gRn8FB65hh0U8uhGvP3VBl4haChinQasOTBYa4pj
Private-MAC: 80f50e1a7075567980742644460edffeb67ca829

View File

@@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD>}<7D><>GL<47><01>8a<<3C>ųǁ<C5B3>0ݺ8s<>1<EFBFBD><31>

View File

@@ -0,0 +1 @@
<EFBFBD><EFBFBD>ѝ<EFBFBD>e<EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>7<01><>>n<><50>/<1C>A<EFBFBD><41><EFBFBD><15>_<EFBFBD><5F><EFBFBD>Ş{f<><66>.<2E>~f<>5<EFBFBD><35><EFBFBD><EFBFBD><EFBFBD>ZhM9<4D><1A>;

View File

@@ -0,0 +1 @@
K<EFBFBD><EFBFBD>䪳@B/<2F>Y<EFBFBD><59><EFBFBD>*<2A><>Psu<73><75><EFBFBD><EFBFBD>"<22><>]0t<30>E <0B><><EFBFBD>`<60><>er<65><6C><DABA><EFBFBD>M<EFBFBD><4D><EFBFBD>~OEδ\<5C>'`<60>

Binary file not shown.

10
sshj.code-workspace Normal file
View File

@@ -0,0 +1,10 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"java.configuration.updateBuildConfiguration": "automatic",
}
}