Compare commits

..

79 Commits

Author SHA1 Message Date
Shikhar Bhushan
16a363fef6 [maven-release-plugin] prepare release v0.7.0 2012-01-08 09:32:41 +00:00
Shikhar Bhushan
9b0d39a798 Remove the SFTPClient.getFileTansfer() method that has typo altogether as this is a simple change to make for clients. 2012-01-08 09:30:05 +00:00
Shikhar Bhushan
81e36153d7 wrapping 2012-01-08 09:10:07 +00:00
Shikhar Bhushan
3026be282a Refactored the local port forwarding API; give caller control over initializing and cleaning up the server socket used.
Also removed 'server socket factory' stuff from SocketClient.
2012-01-05 22:26:44 +00:00
Shikhar Bhushan
8eedeb25fa Merge pull request #50 from iocanel/master
Remove bouncycastle version range from OSGi metadata.
2012-01-02 06:52:50 -08:00
Ioannis Canellos
de11880648 Removed package version from bouncycastle imports 2012-01-02 15:04:12 +02:00
Shikhar Bhushan
1ff4772f3f update id string 2011-12-20 11:12:35 +00:00
Shikhar Bhushan
22a5ffe735 fix for #47 - should send data down rather than sitting around waiting for an adjustment if there is window space available 2011-12-20 10:41:49 +00:00
Shikhar Bhushan
7a77f85ced docfix 2011-12-20 10:37:32 +00:00
Shikhar Bhushan
0002fe8b40 Made some Buffer subclasses final 2011-12-19 22:52:41 +00:00
Shikhar Bhushan
3028e7f218 Fix logging of window adjustments 2011-12-19 21:56:44 +00:00
Shikhar Bhushan
333e1cb7b8 small cleanup 2011-12-04 19:10:34 +00:00
Shikhar Bhushan
945d430916 Removed deprecated Session.Command's methods - getOutputAsString() and getErrorAsString() 2011-12-04 18:26:05 +00:00
Shikhar Bhushan
73b903784a Next version will be 0.7.0 2011-12-04 18:24:00 +00:00
Shikhar Bhushan
7d53649a85 . 2011-12-04 18:23:15 +00:00
Shikhar Bhushan
e193db9a14 Fix example in SSHClient doc 2011-12-04 18:14:27 +00:00
Shikhar Bhushan
a942edb911 Add SFTPClient.getFileTransfer() and deprecate getFileTansfer() which has typo 2011-12-04 18:11:30 +00:00
Shikhar Bhushan
137a7f5956 (reformat) 2011-12-04 17:48:42 +00:00
Shikhar Bhushan
718ff503df Merge pull request #41 from hierynomus/known-hosts
OpenSSH Known hosts format re-implemented
2011-12-04 09:35:58 -08:00
Shikhar Bhushan
d933b2538e Upgrade maven-bundle-plugin to 2.3.6 to fix #37 [https://issues.apache.org/jira/browse/FELIX-3058] 2011-12-04 17:10:33 +00:00
Shikhar Bhushan
ea6f9ceed2 Correct version 2011-12-04 13:54:19 +00:00
rws
07c61b14e8 Change SocketClient to public so that SSHClient can be mocked for testing. 2011-12-04 21:51:58 +08:00
hierynomus
4b175e6938 Re-implemented OpenSSHKnownHostsVerifier to deal with the real format 2011-11-10 09:51:18 +01:00
Shikhar Bhushan
f7e47cffa0 [maven-release-plugin] prepare for next development iteration 2011-10-02 22:17:46 +01:00
Shikhar Bhushan
42dddc7f7e [maven-release-plugin] prepare release v0.6.1 2011-10-02 22:17:36 +01:00
Shikhar Bhushan
f1b3dbb102 Restore mutual exclusion of sendChannelRequest() and gotResponse() in AbstractChannel (but rather than make methods synchronized do it on the queue, which itself doesn't need to be thread-safe). Regression due to 1a2351c5ee. Fixes #35. 2011-10-02 09:47:49 +01:00
Shikhar Bhushan
f83bf2cd3f [maven-release-plugin] prepare for next development iteration 2011-09-26 12:41:23 +01:00
Shikhar Bhushan
be11cbb848 [maven-release-plugin] prepare release v0.6.0 2011-09-26 12:41:17 +01:00
Shikhar Bhushan
43b0599e1f let's make next release sshj 0.6.0 2011-09-26 12:37:21 +01:00
Shikhar Bhushan
b218186cae Merge pull request #34 from hierynomus/scp-filter-copy
Added upload filter capability to SCPUploadClient
2011-09-26 04:11:24 -07:00
hierynomus
184236c3d5 Added upload filter capability to SCPUploadClient 2011-09-26 11:49:07 +02:00
Shikhar Bhushan
cb1d773659 Merge branch 'master' of github.com:shikhar/sshj 2011-09-14 23:06:05 +01:00
Shikhar Bhushan
378665cb46 update contrib 2011-09-14 23:37:31 +02:00
Shikhar Bhushan
a5272dc413 Merge branch 'master' of github.com:shikhar/sshj 2011-09-14 22:11:29 +01:00
Shikhar Bhushan
60552fd001 Merge pull request #31 from hierynomus/filemode-typemask
Fixed bug in SFTP FileMode for AIX/Unix directory mask
2011-09-14 11:43:59 -07:00
hierynomus
ef082c668a Fixed bug in SFTP FileMode for AIX/Unix directory mask 2011-09-14 11:02:35 +02:00
Shikhar Bhushan
e66386eb1c Local window exhaustion -> ConnectionException 2011-09-07 21:45:44 +01:00
Shikhar Bhushan
0937ec9800 minor 2011-09-07 21:24:49 +01:00
Shikhar Bhushan
4b2f42804e Added version in pom.xml for maven-bundle-plugin 2011-08-29 16:49:49 +01:00
Shikhar Bhushan
01765d24d2 AbstractChannel - no reason for channel request response events to share the same underlying lock object. #27 2011-08-29 16:45:12 +01:00
Shikhar Bhushan
1a2351c5ee AbstractChannel - make chanReqResponseEvents a ConcurrentLinkedQueue, so don't have to have sendChannelRequest() & gotResponse() as synchronized methods. #27 2011-08-29 16:41:27 +01:00
Shikhar Bhushan
1cec011401 chown / chmod / chgrp in StatefulSFTPClient -- these all delegate to setattr which is overriden in StatefulSFTPClient to do cwdification. #28 2011-08-29 16:21:10 +01:00
Shikhar Bhushan
52338c13cb Merge pull request #28 from neilprosser/master
StatefulSFTPClient doesn't cwdify all commands
2011-08-27 04:23:07 -07:00
Neil Prosser
09cf21f61a Some stateful methods that needed cwdifying 2011-08-27 00:30:20 +01:00
Shikhar Bhushan
04c2e7b6b8 Client version string update 2011-07-27 19:52:27 +01:00
Shikhar Bhushan
822f196dd8 update contrib 2011-07-27 19:38:56 +01:00
Shikhar Bhushan
a88a574b10 [maven-release-plugin] prepare for next development iteration 2011-07-27 19:33:32 +01:00
Shikhar Bhushan
5cd6986355 [maven-release-plugin] prepare release v0.5.0 2011-07-27 19:33:26 +01:00
Shikhar Bhushan
b5d206bbcb Merge pull request #22 from iocanel/master
OSGi bundle for sshj
2011-07-25 12:22:43 -07:00
Ioannis Canellos
4eae26c551 OSGi-fied sshj 2011-07-25 06:29:20 -07:00
Shikhar Bhushan
b950f88f52 SSHClient implements Closeable 2011-07-24 20:50:01 +01:00
Shikhar Bhushan
3267860db4 mass auto-format 2011-07-24 20:48:00 +01:00
Shikhar Bhushan
d6eb5a040e Non-final ChannelOutputStream field may be null if we haven't received channel open confirmation or channel open failed. So do null-check. 2011-07-16 10:19:46 +01:00
Shikhar Bhushan
21da5b9f65 update contributor list 2011-07-03 14:50:43 -07:00
Shikhar Bhushan
6b66a952d4 Session#changeWindowDimensions was broken, wrong channel request.
Fix for #21
2011-07-03 10:59:40 +01:00
Shikhar Bhushan
aa4faf3f25 Merge pull request #20 from cloudera/forUpstream
Don't leak PrivateKeyStringResource private keys via toString()
2011-06-23 13:46:37 -07:00
Adar Dembo
4be02450dd Don't leak PrivateKeyStringResource private keys via toString() 2011-06-22 17:52:46 -07:00
Shikhar Bhushan
0cec27c28e Edited README.rst via GitHub 2011-06-20 11:44:56 -07:00
Shikhar Bhushan
4384367a1b point to issue tracker 2011-06-20 11:43:21 -07:00
Shikhar Bhushan
4549648a76 [maven-release-plugin] prepare for next development iteration 2011-06-19 18:32:36 +01:00
Shikhar Bhushan
20e2161022 [maven-release-plugin] prepare release v0.4.1 2011-06-19 18:32:30 +01:00
Shikhar Bhushan
fb0f3afa17 fix javadoc warnings 2011-06-19 18:30:52 +01:00
Shikhar Bhushan
114c2bb424 specify missing maven plugin versions & upgrade others 2011-06-19 18:25:04 +01:00
Shikhar Bhushan
079bde5dbf update dependencies in pom 2011-06-19 18:17:06 +01:00
Shikhar Bhushan
eaee42b017 Fix for GH-18
Need to explicitly tell the BigInteger constructor that K (generated secret) is a positive number.
2011-06-19 17:53:56 +01:00
Shikhar Bhushan
8b61d96808 changed some things around, lesser conversions / copying. still not found the bug. grr. 2011-06-19 17:52:00 +01:00
Shikhar Bhushan
73fcc81e83 Unused methods 2011-06-18 20:14:44 +01:00
Shikhar Bhushan
0f7926d4fa . 2011-06-18 14:45:28 +01:00
Shikhar Bhushan
ca6f15650a Useless indirection 2011-06-18 14:44:58 +01:00
Shikhar Bhushan
eb78dc499d . 2011-06-18 13:53:18 +01:00
Shikhar Bhushan
a852f33a15 way to reproduce GH-18 2011-06-18 13:52:03 +01:00
Shikhar Bhushan
ccabc1a20c vestige of eclipse formatting 2011-06-18 13:48:25 +01:00
Shikhar Bhushan
cb2986d32e Update client version string 2011-06-18 13:48:10 +01:00
Shikhar Bhushan
dc70f08e45 Update contributor list 2011-06-08 20:06:23 +01:00
Shikhar Bhushan
bf68ec18b2 Merge pull request #16 from cloudera/forUpstream
Adding support for public key authentication from strings
2011-06-08 11:45:32 -07:00
Philip Langdale
7e78260ca9 Add unit test for String originated key files. 2011-06-07 15:55:44 -07:00
Philip Langdale
27c60cee60 Add support for public key authentication with keys as strings.
Currently, only keys as file locations are supported. This change
adds support for keys as strings.

Significant changes are:

1) Introduction of a new Resource type for keys as strings.

2) Initialization of a key provider with two strings (private and public keys)
   Leaving the public key null is equivalent to not having a .pub file.

3) Obtaining the reader for the resource is refactored into the resource itself
   to avoid requiring knowledge of the type outside the resource.

The loadKeys and authPublickey convenience methods are not duplicated for
the string based loading as we currently don't need them but they could be
if desired (although method signature collisions will be a problem).
2011-06-07 15:55:39 -07:00
Shikhar Bhushan
551b8b4fcf Add forked-path to maven-release-plugin config to allow stdin password input 2011-06-06 20:55:12 +01:00
Shikhar Bhushan
fd591e70be [maven-release-plugin] prepare for next development iteration 2011-06-06 20:44:39 +01:00
99 changed files with 1345 additions and 781 deletions

View File

@@ -1,3 +1,8 @@
Shikhar Bhushan <shikhar@schmizz.net>
Cyril Ledru <cledru@keynectis.net>
Incendium <incendium@gmail.com>
Incendium <incendium@gmail.com>
Philip Langdale <philipl@cloudera.com>
Adar Dembo <adar@cloudera.com>
Ioannis Canellos <iocanel@gmail.com>
Neil Prosser <neil.prosser@gmail.com>
hierynomus <jeroen@hierynomus.com>

View File

@@ -39,6 +39,11 @@ Dependencies
Java 6+. slf4j_ is required. bouncycastle_ is highly recommended and required for using some of the crypto algorithms. jzlib_ is required for using zlib compression.
Bugs, questions
----------------
`Issue tracker <https://github.com/shikhar/sshj/issues>`_
Contributing
------------

51
pom.xml
View File

@@ -5,8 +5,8 @@
<groupId>net.schmizz</groupId>
<artifactId>sshj</artifactId>
<packaging>jar</packaging>
<version>0.4.0</version>
<packaging>bundle</packaging>
<version>0.7.0</version>
<name>sshj</name>
<description>SSHv2 library for Java</description>
@@ -42,13 +42,13 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.45</version>
<version>1.46</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -60,21 +60,27 @@
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>0.9.24</version>
<version>0.9.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.24</version>
<version>0.9.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.0-rc1</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -96,6 +102,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<excludes>
<exclude>examples/*.java</exclude>
@@ -107,11 +114,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.0</version>
<version>2.1</version>
<configuration>
<mavenExecutorId>forked-path</mavenExecutorId>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
<version>2.2.1</version>
<configuration>
<descriptors>
<descriptor>src/assemble/examples.xml</descriptor>
@@ -130,6 +140,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>attach-sources</id>
@@ -142,6 +153,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.8</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
@@ -154,6 +166,25 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.6</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>
!net.schmizz.*,
javax.crypto*,
com.jcraft.jzlib*;version="[1.0,2)",
org.slf4j*;version="[1.6,2)",
org.bouncycastle*,
*
</Import-Package>
<Export-Package>net.schmizz.*</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
@@ -209,7 +240,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.0</version>
<version>1.3</version>
<configuration>
<passphrase>${gpg.passphrase}</passphrase>
</configuration>

View File

@@ -16,9 +16,11 @@
package examples;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
/**
* This example demonstrates local port forwarding, i.e. when we listen on a particular address and port; and forward
@@ -41,8 +43,16 @@ public class LocalPF {
* _We_ listen on localhost:8080 and forward all connections on to server, which then forwards it to
* google.com:80
*/
ssh.newLocalPortForwarder(new InetSocketAddress("localhost", 8080), "google.com", 80)
.listen();
final LocalPortForwarder.Parameters params
= new LocalPortForwarder.Parameters("0.0.0.0", 8080, "google.com", 80);
final ServerSocket ss = new ServerSocket();
ss.setReuseAddress(true);
ss.bind(new InetSocketAddress(params.getLocalHost(), params.getLocalPort()));
try {
ssh.newLocalPortForwarder(params, ss).listen();
} finally {
ss.close();
}
} finally {
ssh.disconnect();

View File

@@ -30,12 +30,12 @@ public class ErrorDeliveryUtil {
}
public static void alertEvents(Throwable x, Event... events) {
for (Event e: events)
for (Event e : events)
e.deliverError(x);
}
public static void alertEvents(Throwable x, Collection<? extends Event> events) {
for (Event e: events)
for (Event e : events)
e.deliverError(x);
}

View File

@@ -73,8 +73,8 @@ public class Event<T extends Throwable> {
}
/**
* @return whether this event is in a 'set' state. An event is set by a call to {@link set()} or {@link
* deliverError}
* @return whether this event is in a 'set' state. An event is set by a call to {@link #set} or {@link
* #deliverError}
*/
public boolean isSet() {
return promise.isDelivered();

View File

@@ -18,7 +18,8 @@ package net.schmizz.sshj;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
public class AndroidConfig extends DefaultConfig {
public class AndroidConfig
extends DefaultConfig {
@Override
protected void initRandomFactory(boolean ignored) {

View File

@@ -31,6 +31,7 @@ import java.util.List;
* {@link Compression}, {@link MAC}, {@link Signature}, {@link Random}, and {@link FileKeyProvider}.
*/
public interface Config {
/**
* Retrieve the list of named factories for {@code Cipher}.
*

View File

@@ -73,15 +73,20 @@ import java.util.List;
* A {@link Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
* BouncyCastle is in the classpath.
* <p/>
* <ul> <li>{@link ConfigImpl#setKeyExchangeFactories Key exchange}: {@link DHG14}*, {@link DHG1}</li> <li>{@link
* ConfigImpl#setCipherFactories Ciphers} [1]: {@link AES128CTR}, {@link AES192CTR}, {@link AES256CTR}, {@link
* <ul>
* <li>{@link ConfigImpl#setKeyExchangeFactories Key exchange}: {@link DHG14}*, {@link DHG1}</li>
* <li>{@link ConfigImpl#setCipherFactories Ciphers} [1]: {@link AES128CTR}, {@link AES192CTR}, {@link AES256CTR},
* {@link
* AES128CBC}, {@link AES192CBC}, {@link AES256CBC}, {@link AES192CBC}, {@link TripleDESCBC}, {@link BlowfishCBC}</li>
* <li>{@link ConfigImpl#setMACFactories MAC}: {@link HMACSHA1}, {@link HMACSHA196}, {@link HMACMD5}, {@link
* HMACMD596}</li> <li>{@link ConfigImpl#setCompressionFactories Compression}: {@link NoneCompression}</li> <li>{@link
* ConfigImpl#setSignatureFactories Signature}: {@link SignatureRSA}, {@link SignatureDSA}</li> <li>{@link
* ConfigImpl#setRandomFactory PRNG}: {@link BouncyCastleRandom}* or {@link JCERandom}</li> <li>{@link
* ConfigImpl#setFileKeyProviderFactories Key file support}: {@link PKCS8KeyFile}*, {@link OpenSSHKeyFile}*</li>
* <li>{@link ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li> </ul>
* HMACMD596}</li>
* <li>{@link ConfigImpl#setCompressionFactories Compression}: {@link NoneCompression}</li>
* <li>{@link ConfigImpl#setSignatureFactories Signature}: {@link SignatureRSA}, {@link SignatureDSA}</li>
* <li>{@link ConfigImpl#setRandomFactory PRNG}: {@link BouncyCastleRandom}* or {@link JCERandom}</li>
* <li>{@link ConfigImpl#setFileKeyProviderFactories Key file support}: {@link PKCS8KeyFile}*, {@link
* OpenSSHKeyFile}*</li>
* <li>{@link ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li>
* </ul>
* <p/>
* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This
* prevents using ciphers with strength greater than 128.
@@ -91,7 +96,7 @@ public class DefaultConfig
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String VERSION = "SSHJ_0_3";
private static final String VERSION = "SSHJ_0_7";
public DefaultConfig() {
setVersion(VERSION);
@@ -113,7 +118,8 @@ public class DefaultConfig
}
protected void initRandomFactory(boolean bouncyCastleRegistered) {
setRandomFactory(new SingletonRandomFactory(bouncyCastleRegistered ? new BouncyCastleRandom.Factory() : new JCERandom.Factory()));
setRandomFactory(new SingletonRandomFactory(bouncyCastleRegistered
? new BouncyCastleRandom.Factory() : new JCERandom.Factory()));
}
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
@@ -137,7 +143,7 @@ public class DefaultConfig
boolean warn = false;
// Ref. https://issues.apache.org/jira/browse/SSHD-24
// "AES256 and AES192 requires unlimited cryptography extension"
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext();) {
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext(); ) {
final Factory.Named<Cipher> f = i.next();
try {
final Cipher c = f.create();
@@ -161,7 +167,7 @@ public class DefaultConfig
protected void initMACFactories() {
setMACFactories(new HMACSHA1.Factory(), new HMACSHA196.Factory(), new HMACMD5.Factory(),
new HMACMD596.Factory());
new HMACMD596.Factory());
}
protected void initCompressionFactories() {

View File

@@ -16,7 +16,6 @@
package net.schmizz.sshj;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SecurityUtils;
@@ -62,9 +61,10 @@ import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.ServerSocket;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Arrays;
@@ -92,8 +92,8 @@ import java.util.List;
* <em>A simple example:</em>
* <p/>
* <pre>
* client = new SSHClient();
* client.initUserKnownHosts();
* final SSHClient client = new SSHClient();
* client.loadKnownHosts();
* client.connect(&quot;hostname&quot;);
* try {
* client.authPassword(&quot;username&quot;, &quot;password&quot;);
@@ -113,7 +113,7 @@ import java.util.List;
*/
public class SSHClient
extends SocketClient
implements SessionFactory {
implements Closeable, SessionFactory {
/** Default port for SSH */
public static final int DEFAULT_PORT = 22;
@@ -441,7 +441,7 @@ public class SSHClient
/**
* Utility function for createing a {@link KeyProvider} instance from given location on the file system. Creates a
* one-off {@link PasswordFinder} using {@link PasswordUtils#createOneOff(char[])}, and calls {@link
* #loadKeys(String,PasswordFinder)}.
* #loadKeys(String, PasswordFinder)}.
*
* @param location location of the key file
* @param passphrase passphrase as a char-array
@@ -476,8 +476,8 @@ public class SSHClient
throws IOException {
final File loc = new File(location);
final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(loc);
final FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format
.toString());
final FileKeyProvider fkp =
Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString());
if (fkp == null)
throw new SSHException("No provider available for " + format + " key file");
fkp.init(loc, passwordFinder);
@@ -501,6 +501,33 @@ public class SSHClient
return loadKeys(location, passphrase.toCharArray());
}
/**
* Creates a {@link KeyProvider} instance from passed strings. Currently only PKCS8 format private key files are
* supported (OpenSSH uses this format).
* <p/>
*
* @param privateKey the private key as a string
* @param publicKey the public key as a string if it's not included with the private key
* @param passwordFinder the {@link PasswordFinder} that can supply the passphrase for decryption (may be {@code
* null} in case keyfile is not encrypted)
*
* @return the key provider ready for use in authentication
*
* @throws SSHException if there was no suitable key provider available for the file format; typically because
* BouncyCastle is not in the classpath
* @throws IOException if the key file format is not known, etc.
*/
public KeyProvider loadKeys(String privateKey, String publicKey, PasswordFinder passwordFinder)
throws IOException {
final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null);
final FileKeyProvider fkp =
Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString());
if (fkp == null)
throw new SSHException("No provider available for " + format + " key file");
fkp.init(privateKey, publicKey, passwordFinder);
return fkp;
}
/**
* Attempts loading the user's {@code known_hosts} file from the default locations, i.e. {@code ~/.ssh/known_hosts}
* and {@code ~/.ssh/known_hosts2} on most platforms. Adds the resulting {@link OpenSSHKnownHosts} object as a host
@@ -541,23 +568,21 @@ public class SSHClient
}
/**
* Create a {@link LocalPortForwarder} that will listen on {@code address} and forward incoming connections to the
* server; which will further forward them to {@code host:port}.
* Create a {@link LocalPortForwarder} that will listen based on {@code parameters} using the bound
* {@code serverSocket} and forward incoming connections to the server; which will further forward them to
* {@code host:port}.
* <p/>
* The returned forwarder's {@link LocalPortForwarder#listen() listen()} method should be called to actually start
* listening, this method just creates an instance.
*
* @param address defines where the {@link LocalPortForwarder} listens
* @param host hostname to which the server will forward
* @param port the port at {@code hostname} to which the server wil forward
* @param parameters parameters for the forwarding setup
* @param serverSocket bound server socket
*
* @return a {@link LocalPortForwarder}
*
* @throws IOException if there is an error opening a local server socket
*/
public LocalPortForwarder newLocalPortForwarder(SocketAddress address, String host, int port)
throws IOException {
return new LocalPortForwarder(getServerSocketFactory(), conn, address, host, port);
public LocalPortForwarder newLocalPortForwarder(LocalPortForwarder.Parameters parameters,
ServerSocket serverSocket) {
return new LocalPortForwarder(conn, parameters, serverSocket);
}
/**
@@ -661,4 +686,15 @@ public class SSHClient
log.info("Key exchange took {} seconds", (System.currentTimeMillis() - start) / 1000.0);
}
/**
* Same as {@link #disconnect()}.
*
* @throws IOException
*/
@Override
public void close()
throws IOException {
disconnect();
}
}

View File

@@ -35,7 +35,6 @@
*/
package net.schmizz.sshj;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -44,8 +43,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
abstract class SocketClient {
public abstract class SocketClient {
private final int defaultPort;
@@ -54,7 +52,6 @@ abstract class SocketClient {
private OutputStream output;
private SocketFactory socketFactory = SocketFactory.getDefault();
private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
private static final int DEFAULT_CONNECT_TIMEOUT = 0;
private int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
@@ -158,17 +155,6 @@ abstract class SocketClient {
return socketFactory;
}
public void setServerSocketFactory(ServerSocketFactory factory) {
if (factory == null)
serverSocketFactory = ServerSocketFactory.getDefault();
else
serverSocketFactory = factory;
}
public ServerSocketFactory getServerSocketFactory() {
return serverSocketFactory;
}
public int getConnectTimeout() {
return connectTimeout;
}

View File

@@ -32,7 +32,8 @@ public class Base64 {
* @see Base64
* @since 1.3
*/
public static class InputStream extends java.io.FilterInputStream {
public static class InputStream
extends java.io.FilterInputStream {
private final boolean encode; // Encoding or decoding
private int position; // Current position in the buffer
@@ -99,7 +100,8 @@ public class Base64 {
* @since 1.3
*/
@Override
public int read() throws java.io.IOException {
public int read()
throws java.io.IOException {
// Do we need to get data?
if (position < 0)
@@ -197,7 +199,8 @@ public class Base64 {
* @since 1.3
*/
@Override
public int read(byte[] dest, int off, int len) throws java.io.IOException {
public int read(byte[] dest, int off, int len)
throws java.io.IOException {
int i;
int b;
for (i = 0; i < len; i++) {
@@ -222,7 +225,8 @@ public class Base64 {
* @see Base64
* @since 1.3
*/
public static class OutputStream extends java.io.FilterOutputStream {
public static class OutputStream
extends java.io.FilterOutputStream {
private final boolean encode;
private int position;
@@ -289,7 +293,8 @@ public class Base64 {
* @since 1.3
*/
@Override
public void close() throws java.io.IOException {
public void close()
throws java.io.IOException {
// 1. Ensure that pending characters are written
flush();
@@ -308,7 +313,8 @@ public class Base64 {
* @since 2.3
*/
@Override
public void flush() throws java.io.IOException {
public void flush()
throws java.io.IOException {
flushBase64();
super.flush();
}
@@ -318,7 +324,8 @@ public class Base64 {
*
* @throws java.io.IOException if there's an error.
*/
public void flushBase64() throws java.io.IOException {
public void flushBase64()
throws java.io.IOException {
if (position > 0)
if (encode) {
out.write(encode3to4(b4, buffer, position, options));
@@ -346,7 +353,8 @@ public class Base64 {
* @throws java.io.IOException if there's an error flushing
* @since 1.5.1
*/
public void suspendEncoding() throws java.io.IOException {
public void suspendEncoding()
throws java.io.IOException {
flushBase64();
suspendEncoding = true;
} // end suspendEncoding
@@ -361,7 +369,8 @@ public class Base64 {
* @since 1.3
*/
@Override
public void write(byte[] theBytes, int off, int len) throws java.io.IOException {
public void write(byte[] theBytes, int off, int len)
throws java.io.IOException {
// Encoding suspended?
if (suspendEncoding) {
super.out.write(theBytes, off, len);
@@ -383,7 +392,8 @@ public class Base64 {
* @since 1.3
*/
@Override
public void write(int theByte) throws java.io.IOException {
public void write(int theByte)
throws java.io.IOException {
// Encoding suspended?
if (suspendEncoding) {
super.out.write(theByte);
@@ -673,7 +683,8 @@ public class Base64 {
* @throws java.io.IOException If bogus characters exist in source data
* @since 1.3
*/
public static byte[] decode(byte[] source, int off, int len, int options) throws java.io.IOException {
public static byte[] decode(byte[] source, int off, int len, int options)
throws java.io.IOException {
// Lots of error checking and exception throwing
if (source == null)
@@ -725,7 +736,7 @@ public class Base64 {
else
// There's a bad input character in the Base64 stream.
throw new java.io.IOException(String.format("Bad Base64 input character '%c' in array position %d",
source[i], i));
source[i], i));
} // each input character
byte[] out = new byte[outBuffPosn];
@@ -743,7 +754,8 @@ public class Base64 {
* @throws java.io.IOException If there is a problem
* @since 1.4
*/
public static byte[] decode(String s) throws java.io.IOException {
public static byte[] decode(String s)
throws java.io.IOException {
return decode(s, NO_OPTIONS);
}
@@ -759,7 +771,8 @@ public class Base64 {
* @throws NullPointerException if <tt>s</tt> is null
* @since 1.4
*/
public static byte[] decode(String s, int options) throws java.io.IOException {
public static byte[] decode(String s, int options)
throws java.io.IOException {
if (s == null)
throw new NullPointerException("Input string was null.");
@@ -833,7 +846,8 @@ public class Base64 {
* @throws java.io.IOException if there is an error
* @since 2.2
*/
public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException {
public static void decodeFileToFile(String infile, String outfile)
throws java.io.IOException {
byte[] decoded = Base64.decodeFromFile(infile);
java.io.OutputStream out = null;
@@ -864,7 +878,8 @@ public class Base64 {
* @throws java.io.IOException if there is an error
* @since 2.1
*/
public static byte[] decodeFromFile(String filename) throws java.io.IOException {
public static byte[] decodeFromFile(String filename)
throws java.io.IOException {
byte[] decodedData = null;
Base64.InputStream bis = null;
@@ -878,12 +893,12 @@ public class Base64 {
// Check for size of file
if (file.length() > Integer.MAX_VALUE)
throw new java.io.IOException("File is too big for this convenience method (" + file.length()
+ " bytes).");
+ " bytes).");
buffer = new byte[(int) file.length()];
// Open a stream
bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
Base64.DECODE);
Base64.DECODE);
// Read until done
while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
@@ -918,7 +933,8 @@ public class Base64 {
* @throws java.io.IOException if there is an error
* @since 2.1
*/
public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException {
public static void decodeToFile(String dataToDecode, String filename)
throws java.io.IOException {
Base64.OutputStream bos = null;
try {
@@ -950,8 +966,9 @@ public class Base64 {
* @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
* @since 1.5
*/
public static Object decodeToObject(String encodedObject) throws java.io.IOException,
java.lang.ClassNotFoundException {
public static Object decodeToObject(String encodedObject)
throws java.io.IOException,
java.lang.ClassNotFoundException {
// Decode and gunzip if necessary
byte[] objBytes = decode(encodedObject);
@@ -1078,7 +1095,8 @@ public class Base64 {
* @see Base64#DO_BREAK_LINES
* @since 2.0
*/
public static String encodeBytes(byte[] source, int options) throws java.io.IOException {
public static String encodeBytes(byte[] source, int options)
throws java.io.IOException {
return encodeBytes(source, 0, source.length, options);
} // end encodeBytes
@@ -1137,7 +1155,8 @@ public class Base64 {
* @see Base64#DO_BREAK_LINES
* @since 2.0
*/
public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
public static String encodeBytes(byte[] source, int off, int len, int options)
throws java.io.IOException {
byte[] encoded = encodeBytesToBytes(source, off, len, options);
// Return value according to relevant encoding.
@@ -1189,7 +1208,8 @@ public class Base64 {
* @see Base64#DO_BREAK_LINES
* @since 2.3.1
*/
public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options)
throws java.io.IOException {
if (source == null)
throw new NullPointerException("Cannot serialize a null array.");
@@ -1302,7 +1322,8 @@ public class Base64 {
* @throws java.io.IOException if there is an error
* @since 2.2
*/
public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException {
public static void encodeFileToFile(String infile, String outfile)
throws java.io.IOException {
String encoded = Base64.encodeFromFile(infile);
java.io.OutputStream out = null;
@@ -1333,7 +1354,8 @@ public class Base64 {
* @throws java.io.IOException if there is an error
* @since 2.1
*/
public static String encodeFromFile(String filename) throws java.io.IOException {
public static String encodeFromFile(String filename)
throws java.io.IOException {
String encodedData = null;
Base64.InputStream bis = null;
@@ -1348,7 +1370,7 @@ public class Base64 {
// Open a stream
bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
Base64.ENCODE);
Base64.ENCODE);
// Read until done
while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
@@ -1387,7 +1409,8 @@ public class Base64 {
* @throws NullPointerException if serializedObject is null
* @since 1.4
*/
public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException {
public static String encodeObject(java.io.Serializable serializableObject)
throws java.io.IOException {
return encodeObject(serializableObject, NO_OPTIONS);
} // end encodeObject
@@ -1420,7 +1443,8 @@ public class Base64 {
* @see Base64#DO_BREAK_LINES
* @since 2.0
*/
public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException {
public static String encodeObject(java.io.Serializable serializableObject, int options)
throws java.io.IOException {
if (serializableObject == null)
throw new NullPointerException("Cannot serialize a null object.");
@@ -1481,7 +1505,8 @@ public class Base64 {
* @throws NullPointerException if dataToEncode is null
* @since 2.1
*/
public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException {
public static void encodeToFile(byte[] dataToEncode, String filename)
throws java.io.IOException {
if (dataToEncode == null)
throw new NullPointerException("Data to encode was null.");

View File

@@ -51,7 +51,7 @@ public class Buffer<T extends Buffer<T>> {
}
}
public static class PlainBuffer
public static final class PlainBuffer
extends Buffer<PlainBuffer> {
public PlainBuffer() {
@@ -341,41 +341,13 @@ public class Buffer<T extends Buffer<T>> {
*/
public BigInteger readMPInt()
throws BufferException {
return new BigInteger(readMPIntAsBytes());
return new BigInteger(readBytes());
}
/**
* Writes an SSH multiple-precision integer from a {@code BigInteger}
*
* @param bi {@code BigInteger} to write
*
* @return this
*/
public T putMPInt(BigInteger bi) {
return putMPInt(bi.toByteArray());
}
/**
* Writes an SSH multiple-precision integer from a Java byte-array
*
* @param foo byte-array
*
* @return this
*/
public T putMPInt(byte[] foo) {
int i = foo.length;
if ((foo[0] & 0x80) != 0) {
i++;
putUInt32(i);
putByte((byte) 0);
} else
putUInt32(i);
return putRawBytes(foo);
}
public byte[] readMPIntAsBytes()
throws BufferException {
return readBytes();
final byte[] asBytes = bi.toByteArray();
putUInt32(asBytes.length);
return putRawBytes(asBytes);
}
public long readUInt64()

View File

@@ -35,25 +35,11 @@
*/
package net.schmizz.sshj.common;
import java.util.Arrays;
/** Utility functions for byte arrays. */
public class ByteArrayUtils {
final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Check whether two byte arrays are the equal.
*
* @param a1
* @param a2
*
* @return <code>true</code> or <code>false</code>
*/
public static boolean equals(byte[] a1, byte[] a2) {
return (a1.length != a2.length && equals(a1, 0, a2, 0, a1.length));
}
/**
* Check whether some part or whole of two byte arrays is equal, for <code>length</code> bytes starting at some
* offset.
@@ -75,17 +61,6 @@ public class ByteArrayUtils {
return true;
}
/**
* Get a hexadecimal representation of <code>array</code>, with each octet separated by a space.
*
* @param array
*
* @return hex string, each octet delimited by a space
*/
public static String printHex(byte[] array) {
return printHex(array, 0, array.length);
}
/**
* Get a hexadecimal representation of a byte array starting at <code>offset</code> index for <code>len</code>
* bytes, with each octet separated by a space.
@@ -139,8 +114,4 @@ public class ByteArrayUtils {
return sb.toString();
}
public static byte[] copyOf(byte[] array) {
return Arrays.copyOf(array, array.length);
}
}

View File

@@ -22,6 +22,7 @@ public interface ErrorNotifiable {
/** Utility functions. */
class Util {
/** Notify all {@code notifiables} of given {@code error}. */
public static void alertAll(SSHException error, ErrorNotifiable... notifiables) {
for (ErrorNotifiable notifiable : notifiables)

View File

@@ -32,7 +32,6 @@ public enum KeyType {
/** SSH identifier for RSA keys */
RSA("ssh-rsa") {
@Override
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
throws GeneralSecurityException {
@@ -64,7 +63,6 @@ public enum KeyType {
/** SSH identifier for DSA keys */
DSA("ssh-dss") {
@Override
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
throws GeneralSecurityException {
@@ -100,7 +98,6 @@ public enum KeyType {
/** Unrecognized */
UNKNOWN("unknown") {
@Override
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
throws GeneralSecurityException {

View File

@@ -37,7 +37,7 @@ package net.schmizz.sshj.common;
import java.util.Arrays;
public class SSHPacket
public final class SSHPacket
extends Buffer<SSHPacket> {
public SSHPacket() {

View File

@@ -58,6 +58,7 @@ import java.security.Signature;
public class SecurityUtils {
private static class BouncyCastleRegistration {
public void run()
throws Exception {
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {

View File

@@ -89,13 +89,13 @@ public interface Connection {
* @param wantReply whether a reply is requested
* @param specifics {@link SSHPacket} containing fields specific to the request
*
* @return a {@link net.schmizz.concurrent.Promise} for the reply data (in case {@code wantReply} is true) which allows waiting on the
* reply, or {@code null} if a reply is not requested.
* @return a {@link net.schmizz.concurrent.Promise} for the reply data (in case {@code wantReply} is true) which
* allows waiting on the reply, or {@code null} if a reply is not requested.
*
* @throws TransportException if there is an error sending the request
*/
public Promise<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
byte[] specifics)
byte[] specifics)
throws TransportException;
/**

View File

@@ -35,8 +35,8 @@
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.ErrorDeliveryUtil;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.common.DisconnectReason;
@@ -240,7 +240,8 @@ public abstract class AbstractChannel
ErrorDeliveryUtil.alertEvents(error, chanReqResponseEvents);
in.notifyError(error);
out.notifyError(error);
if (out != null)
out.notifyError(error);
finishOff();
}
@@ -312,7 +313,8 @@ public abstract class AbstractChannel
handleRequest(reqType, buf);
}
private void gotWindowAdjustment(SSHPacket buf) throws ConnectionException {
private void gotWindowAdjustment(SSHPacket buf)
throws ConnectionException {
final int howMuch;
try {
howMuch = buf.readUInt32AsInt();
@@ -362,37 +364,41 @@ public abstract class AbstractChannel
stream.receive(buf.array(), buf.rpos(), len);
}
protected synchronized Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
Buffer.PlainBuffer reqSpecific)
protected Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
Buffer.PlainBuffer reqSpecific)
throws TransportException {
log.info("Sending channel request for `{}`", reqType);
trans.write(
newBuffer(Message.CHANNEL_REQUEST)
.putString(reqType)
.putBoolean(wantReply)
.putBuffer(reqSpecific)
);
synchronized (chanReqResponseEvents) {
trans.write(
newBuffer(Message.CHANNEL_REQUEST)
.putString(reqType)
.putBoolean(wantReply)
.putBuffer(reqSpecific)
);
Event<ConnectionException> responseEvent = null;
if (wantReply) {
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType, ConnectionException.chainer, lock);
chanReqResponseEvents.add(responseEvent);
Event<ConnectionException> responseEvent = null;
if (wantReply) {
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType,
ConnectionException.chainer);
chanReqResponseEvents.add(responseEvent);
}
return responseEvent;
}
return responseEvent;
}
private synchronized void gotResponse(boolean success)
private void gotResponse(boolean success)
throws ConnectionException {
final Event<ConnectionException> responseEvent = chanReqResponseEvents.poll();
if (responseEvent != null) {
if (success)
responseEvent.set();
else
responseEvent.deliverError(new ConnectionException("Request failed"));
} else
throw new ConnectionException(
DisconnectReason.PROTOCOL_ERROR,
"Received response to channel request when none was requested");
synchronized (chanReqResponseEvents) {
final Event<ConnectionException> responseEvent = chanReqResponseEvents.poll();
if (responseEvent != null) {
if (success)
responseEvent.set();
else
responseEvent.deliverError(new ConnectionException("Request failed"));
} else
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR,
"Received response to channel request when none was requested");
}
}
private synchronized void gotEOF()
@@ -428,7 +434,7 @@ public abstract class AbstractChannel
@Override
public String toString() {
return "< " + type + " channel: id=" + id + ", recipient=" + recipient + ", localWin=" + lwin + ", remoteWin="
+ rwin + " >";
+ rwin + " >";
}

View File

@@ -32,6 +32,7 @@ public interface Channel
/** Direct channels are those that are initiated by us. */
interface Direct
extends Channel {
/**
* Request opening this channel from remote end.
*

View File

@@ -116,7 +116,7 @@ public final class ChannelInputStream
public int read(byte[] b, int off, int len)
throws IOException {
synchronized (buf) {
for (; ;) {
for (; ; ) {
if (buf.available() > 0)
break;
if (eof)
@@ -163,7 +163,7 @@ public final class ChannelInputStream
if (adjustment > 0) {
log.info("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST to #{} for {} bytes", chan.getRecipient(), adjustment);
trans.write(new SSHPacket(Message.CHANNEL_WINDOW_ADJUST)
.putUInt32(chan.getRecipient()).putUInt32(adjustment));
.putUInt32(chan.getRecipient()).putUInt32(adjustment));
win.expand(adjustment);
}
}

View File

@@ -35,12 +35,14 @@
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.OutputStream;
@@ -56,26 +58,92 @@ public final class ChannelOutputStream
private final Channel chan;
private final Transport trans;
private final Window.Remote win;
private final SSHPacket buffer = new SSHPacket();
private final DataBuffer buffer = new DataBuffer();
private final byte[] b = new byte[1];
private int bufferLength;
private boolean closed;
private SSHException error;
private final class DataBuffer {
private final int headerOffset;
private final int dataOffset;
private final SSHPacket packet = new SSHPacket(Message.CHANNEL_DATA);
private final Buffer.PlainBuffer leftOvers = new Buffer.PlainBuffer();
DataBuffer() {
headerOffset = packet.rpos();
packet.putUInt32(0); // recipient
packet.putUInt32(0); // data length
dataOffset = packet.wpos();
}
int write(byte[] data, int off, int len)
throws TransportException, ConnectionException {
final int bufferSize = packet.wpos() - dataOffset;
if (bufferSize >= win.getMaxPacketSize()) {
flush(bufferSize);
return 0;
} else {
final int n = Math.min(len - off, win.getMaxPacketSize() - bufferSize);
packet.putRawBytes(data, off, n);
return n;
}
}
void flush()
throws TransportException, ConnectionException {
flush(packet.wpos() - dataOffset);
}
void flush(int bufferSize)
throws TransportException, ConnectionException {
while (bufferSize > 0) {
int remoteWindowSize = win.getSize();
if (remoteWindowSize == 0)
remoteWindowSize = win.awaitExpansion(remoteWindowSize);
// We can only write the min. of
// a) how much data we have
// b) the max packet size
// c) what the current window size will allow
final int writeNow = Math.min(bufferSize, Math.min(win.getMaxPacketSize(), remoteWindowSize));
packet.wpos(headerOffset);
packet.putMessageID(Message.CHANNEL_DATA);
packet.putUInt32(chan.getRecipient());
packet.putUInt32(writeNow);
packet.wpos(dataOffset + writeNow);
final int leftOverBytes = bufferSize - writeNow;
if (leftOverBytes > 0) {
leftOvers.putRawBytes(packet.array(), packet.wpos(), leftOverBytes);
}
trans.write(packet);
win.consume(writeNow);
packet.rpos(headerOffset);
packet.wpos(dataOffset);
if (leftOverBytes > 0) {
packet.putBuffer(leftOvers);
leftOvers.clear();
}
bufferSize = leftOverBytes;
}
}
}
public ChannelOutputStream(Channel chan, Transport trans, Window.Remote win) {
this.chan = chan;
this.trans = trans;
this.win = win;
prepBuffer();
}
private void prepBuffer() {
bufferLength = 0;
buffer.rpos(5);
buffer.wpos(5);
buffer.putMessageID(Message.CHANNEL_DATA);
buffer.putUInt32(0); // meant to be recipient
buffer.putUInt32(0); // meant to be data length
}
@Override
@@ -86,19 +154,13 @@ public final class ChannelOutputStream
}
@Override
public synchronized void write(byte[] data, int off, int len)
public synchronized void write(final byte[] data, int off, int len)
throws IOException {
checkClose();
while (len > 0) {
final int x = Math.min(len, win.getMaxPacketSize() - bufferLength);
if (x <= 0) {
flush();
continue;
}
buffer.putRawBytes(data, off, x);
bufferLength += x;
off += x;
len -= x;
final int n = buffer.write(data, off, len);
off += n;
len -= n;
}
}
@@ -107,55 +169,44 @@ public final class ChannelOutputStream
this.error = error;
}
private synchronized void checkClose()
private void checkClose()
throws SSHException {
if (closed)
if (closed) {
if (error != null)
throw error;
else
throw new ConnectionException("Stream closed");
}
}
@Override
public synchronized void close()
throws IOException {
if (!closed)
if (!closed) {
try {
flush();
buffer.flush();
chan.sendEOF();
} finally {
setClosed();
}
}
}
public synchronized void setClosed() {
closed = true;
}
/**
* Send all data currently buffered. If window space is exhausted in the process, this will block
* until it is expanded by the server.
*
* @throws IOException
*/
@Override
public synchronized void flush()
throws IOException {
checkClose();
if (bufferLength <= 0) // No data to send
return;
putRecipientAndLength();
try {
win.waitAndConsume(bufferLength);
trans.write(buffer);
} finally {
prepBuffer();
}
}
private void putRecipientAndLength() {
final int origPos = buffer.wpos();
buffer.wpos(6);
buffer.putUInt32(chan.getRecipient());
buffer.putUInt32(bufferLength);
buffer.wpos(origPos);
buffer.flush();
}
@Override

View File

@@ -43,8 +43,8 @@ public class SocketStreamCopyMonitor
}
public static void monitor(final int frequency, final TimeUnit unit,
final Event<IOException> x, final Event<IOException> y,
final Channel channel, final Socket socket) {
final Event<IOException> x, final Event<IOException> y,
final Channel channel, final Socket socket) {
new SocketStreamCopyMonitor(new Runnable() {
public void run() {
try {

View File

@@ -37,8 +37,8 @@ public abstract class Window {
public void expand(int inc) {
synchronized (lock) {
log.debug("Increasing by {} up to {}", inc, size);
size += inc;
log.debug("Increasing by {} up to {}", inc, size);
lock.notifyAll();
}
}
@@ -48,15 +48,18 @@ public abstract class Window {
}
public int getSize() {
return size;
synchronized (lock) {
return size;
}
}
public void consume(int dec) {
public void consume(int dec)
throws ConnectionException {
synchronized (lock) {
log.debug("Consuming by " + dec + " down to " + size);
size -= dec;
log.debug("Consuming by " + dec + " down to " + size);
if (size < 0)
throw new SSHRuntimeException("Window consumed to below 0");
throw new ConnectionException("Window consumed to below 0");
}
}
@@ -73,18 +76,26 @@ public abstract class Window {
super(initialWinSize, maxPacketSize);
}
public void waitAndConsume(int howMuch)
public int awaitExpansion(int was)
throws ConnectionException {
synchronized (lock) {
while (size < howMuch) {
log.debug("Waiting, need window space for {} bytes", howMuch);
while (size <= was) {
log.debug("Waiting, need size to grow from {} bytes", was);
try {
lock.wait();
} catch (InterruptedException ie) {
throw new ConnectionException(ie);
}
}
consume(howMuch);
return size;
}
}
public void consume(int howMuch) {
try {
super.consume(howMuch);
} catch (ConnectionException e) { // It's a bug if we consume more than remote allowed
throw new SSHRuntimeException(e);
}
}

View File

@@ -19,104 +19,104 @@ import net.schmizz.concurrent.Event;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.SocketStreamCopyMonitor;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ServerSocketFactory;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.TimeUnit;
public class LocalPortForwarder {
private class DirectTCPIPChannel
extends AbstractDirectChannel {
public static class Parameters {
private final Socket sock;
private final String localHost;
private final int localPort;
private final String remoteHost;
private final int remotePort;
private DirectTCPIPChannel(Connection conn, Socket sock) {
super(conn, "direct-tcpip");
this.sock = sock;
public Parameters(String localHost, int localPort, String remoteHost, int remotePort) {
this.localHost = localHost;
this.localPort = localPort;
this.remoteHost = remoteHost;
this.remotePort = remotePort;
}
private void start()
public String getRemoteHost() {
return remoteHost;
}
public int getRemotePort() {
return remotePort;
}
public String getLocalHost() {
return localHost;
}
public int getLocalPort() {
return localPort;
}
}
public static class DirectTCPIPChannel
extends AbstractDirectChannel {
protected final Socket socket;
protected final Parameters parameters;
public DirectTCPIPChannel(Connection conn, Socket socket, Parameters parameters) {
super(conn, "direct-tcpip");
this.socket = socket;
this.parameters = parameters;
}
protected void start()
throws IOException {
sock.setSendBufferSize(getLocalMaxPacketSize());
sock.setReceiveBufferSize(getRemoteMaxPacketSize());
final Event<IOException> soc2chan = new StreamCopier(sock.getInputStream(), getOutputStream())
socket.setSendBufferSize(getLocalMaxPacketSize());
socket.setReceiveBufferSize(getRemoteMaxPacketSize());
final Event<IOException> soc2chan = new StreamCopier(socket.getInputStream(), getOutputStream())
.bufSize(getRemoteMaxPacketSize())
.spawnDaemon("soc2chan");
final Event<IOException> chan2soc = new StreamCopier(getInputStream(), sock.getOutputStream())
final Event<IOException> chan2soc = new StreamCopier(getInputStream(), socket.getOutputStream())
.bufSize(getLocalMaxPacketSize())
.spawnDaemon("chan2soc");
SocketStreamCopyMonitor.monitor(5, TimeUnit.SECONDS, soc2chan, chan2soc, this, sock);
}
SocketStreamCopyMonitor.monitor(5, TimeUnit.SECONDS, soc2chan, chan2soc, this, socket);
}
@Override
protected SSHPacket buildOpenReq() {
return super.buildOpenReq()
.putString(host)
.putUInt32(port)
.putString(ss.getInetAddress().getHostAddress())
.putUInt32(ss.getLocalPort());
.putString(parameters.getRemoteHost())
.putUInt32(parameters.getRemotePort())
.putString(parameters.getLocalHost())
.putUInt32(parameters.getLocalPort());
}
}
private final Logger log = LoggerFactory.getLogger(getClass());
private final Logger log = LoggerFactory.getLogger(LocalPortForwarder.class);
private final Connection conn;
private final ServerSocket ss;
private final String host;
private final int port;
private final Parameters parameters;
private final ServerSocket serverSocket;
/**
* Create a local port forwarder with specified binding ({@code listeningAddr}. It does not, however, start
* listening unless {@link #listen() explicitly told to}. The {@link javax.net.ServerSocketFactory#getDefault()
* default} server socket factory is used.
*
* @param conn {@link Connection} implementation
* @param listeningAddr {@link SocketAddress} this forwarder will listen on, if {@code null} then an ephemeral port
* and valid local address will be picked to bind the server socket
* @param host what host the SSH server will further forward to
* @param port port on {@code toHost}
*
* @throws IOException if there is an error binding on specified {@code listeningAddr}
*/
public LocalPortForwarder(Connection conn, SocketAddress listeningAddr, String host, int port)
throws IOException {
this(ServerSocketFactory.getDefault(), conn, listeningAddr, host, port);
}
/**
* Create a local port forwarder with specified binding ({@code listeningAddr}. It does not, however, start
* listening unless {@link #listen() explicitly told to}.
*
* @param ssf factory to use for creating the server socket
* @param conn {@link Connection} implementation
* @param listeningAddr {@link SocketAddress} this forwarder will listen on, if {@code null} then an ephemeral port
* and valid local address will be picked to bind the server socket
* @param host what host the SSH server will further forward to
* @param port port on {@code toHost}
*
* @throws IOException if there is an error binding on specified {@code listeningAddr}
*/
public LocalPortForwarder(ServerSocketFactory ssf, Connection conn, SocketAddress listeningAddr, String host, int port)
throws IOException {
public LocalPortForwarder(Connection conn, Parameters parameters, ServerSocket serverSocket) {
this.conn = conn;
this.host = host;
this.port = port;
this.ss = ssf.createServerSocket();
ss.setReceiveBufferSize(conn.getMaxPacketSize());
ss.bind(listeningAddr);
this.parameters = parameters;
this.serverSocket = serverSocket;
}
/** @return the address to which this forwarder is bound for listening */
public SocketAddress getListeningAddress() {
return ss.getLocalSocketAddress();
protected DirectTCPIPChannel openChannel(Socket socket)
throws TransportException, ConnectionException {
final DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, socket, parameters);
chan.open();
return chan;
}
/**
@@ -126,14 +126,11 @@ public class LocalPortForwarder {
*/
public void listen()
throws IOException {
log.info("Listening on {}", ss.getLocalSocketAddress());
Socket sock;
log.info("Listening on {}", serverSocket.getLocalSocketAddress());
while (!Thread.currentThread().isInterrupted()) {
sock = ss.accept();
log.info("Got connection from {}", sock.getRemoteSocketAddress());
DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, sock);
chan.open();
chan.start();
final Socket socket = serverSocket.accept();
log.info("Got connection from {}", socket.getRemoteSocketAddress());
openChannel(socket).start();
}
log.info("Interrupted!");
}

View File

@@ -19,7 +19,6 @@ import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@@ -81,12 +80,6 @@ public interface Session
void signal(Signal signal)
throws TransportException;
@Deprecated
String getOutputAsString() throws IOException;
@Deprecated
String getErrorAsString() throws IOException;
}
/** Shell API. */
@@ -132,6 +125,7 @@ public interface Session
/** Subsystem API. */
interface Subsystem
extends Channel {
Integer getExitStatus();
}

View File

@@ -46,7 +46,6 @@ import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.ChannelInputStream;
import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
@@ -104,7 +103,7 @@ public class SessionChannel
public void changeWindowDimensions(int cols, int rows, int width, int height)
throws TransportException {
sendChannelRequest(
"pty-req",
"window-change",
false,
new Buffer.PlainBuffer()
.putUInt32(cols)
@@ -255,18 +254,4 @@ public class SessionChannel
throw new SSHRuntimeException("This session channel is all used up");
}
@Override
@Deprecated
public String getOutputAsString()
throws IOException {
return IOUtils.readFully(getInputStream()).toString();
}
@Override
@Deprecated
public String getErrorAsString()
throws IOException {
return IOUtils.readFully(getErrorStream()).toString();
}
}

View File

@@ -69,9 +69,9 @@ public abstract class AbstractForwardedChannel
// Must ensure channel is attached before confirming, data could start coming in immediately!
conn.attach(this);
trans.write(newBuffer(Message.CHANNEL_OPEN_CONFIRMATION)
.putUInt32(getID())
.putUInt32(getLocalWinSize())
.putUInt32(getLocalMaxPacketSize()));
.putUInt32(getID())
.putUInt32(getLocalWinSize())
.putUInt32(getLocalMaxPacketSize()));
open.set();
}

View File

@@ -39,18 +39,18 @@ public class RemotePortForwarder
* address (or domain name) and port on which connections for forwarding
* are to be accepted. Some strings used for 'address to bind' have
* special-case semantics.
* <p/>
*
* o &quot;&quot; means that connections are to be accepted on all protocol
* families supported by the SSH implementation.
* <p/>
*
* o &quot;0.0.0.0&quot; means to listen on all IPv4 addresses.
* <p/>
*
* o &quot;::&quot; means to listen on all IPv6 addresses.
* <p/>
*
* o &quot;localhost&quot; means to listen on all protocol families supported by
* the SSH implementation on loopback addresses only ([RFC3330] and
* [RFC3513]).
* <p/>
*
* o &quot;127.0.0.1&quot; and &quot;::1&quot; indicate listening on the loopback
* interfaces for IPv4 and IPv6, respectively.
* </pre>
@@ -198,9 +198,9 @@ public class RemotePortForwarder
protected SSHPacket req(String reqName, Forward forward)
throws ConnectionException, TransportException {
final byte[] specifics = new Buffer.PlainBuffer().putString(forward.address).putUInt32(forward.port)
.getCompactData();
.getCompactData();
return conn.sendGlobalRequest(reqName, true, specifics)
.retrieve(conn.getTimeout(), TimeUnit.SECONDS);
.retrieve(conn.getTimeout(), TimeUnit.SECONDS);
}
/** @return the active forwards. */
@@ -227,7 +227,7 @@ public class RemotePortForwarder
callListener(listeners.get(chan.getParentForward()), chan);
else
chan.reject(OpenFailException.Reason.ADMINISTRATIVELY_PROHIBITED, "Forwarding was not requested on `"
+ chan.getParentForward() + "`");
+ chan.getParentForward() + "`");
}
}

View File

@@ -74,7 +74,7 @@ public class FileMode {
}
public int getTypeMask() {
return mask & 0770000;
return mask & 0170000;
}
public int getPermissionsMask() {

View File

@@ -57,9 +57,9 @@ public class PacketReader
readIntoBuffer(lenBuf, 0, lenBuf.length);
return (int) (lenBuf[0] << 24 & 0xff000000L
| lenBuf[1] << 16 & 0x00ff0000L
| lenBuf[2] << 8 & 0x0000ff00L
| lenBuf[3] & 0x000000ffL);
| lenBuf[1] << 16 & 0x00ff0000L
| lenBuf[2] << 8 & 0x0000ff00L
| lenBuf[3] & 0x000000ffL);
}
public SFTPPacket<Response> readPacket()
@@ -97,7 +97,7 @@ public class PacketReader
log.debug("Received {} packet", resp.getType());
if (promise == null)
throw new SFTPException("Received [" + resp.readType() + "] response for request-id " + resp.getRequestID()
+ ", no such request was made");
+ ", no such request was made");
else
promise.deliver(resp);
}

View File

@@ -32,7 +32,7 @@ public class RemoteDirectory
throws IOException {
List<RemoteResourceInfo> rri = new LinkedList<RemoteResourceInfo>();
loop:
for (; ;) {
for (; ; ) {
Response res = requester.doRequest(newRequest(PacketType.READDIR));
switch (res.getType()) {

View File

@@ -39,8 +39,8 @@ public class RemoteFile
public FileAttributes fetchAttributes()
throws IOException {
return requester.doRequest(newRequest(PacketType.FSTAT))
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
}
public long length()
@@ -74,9 +74,9 @@ public class RemoteFile
public void write(long fileOffset, byte[] data, int off, int len)
throws IOException {
requester.doRequest(newRequest(PacketType.WRITE)
.putUInt64(fileOffset)
.putUInt32(len - off)
.putRawBytes(data, off, len)
.putUInt64(fileOffset)
.putUInt32(len - off)
.putRawBytes(data, off, len)
).ensureStatusPacketIsOK();
}
@@ -87,12 +87,12 @@ public class RemoteFile
public int getOutgoingPacketOverhead() {
return 1 + // packet type
4 + // request id
4 + // next length
handle.length() + // next
8 + // file offset
4 + // data length
4; // packet length
4 + // request id
4 + // next length
handle.length() + // next
8 + // file offset
4 + // data length
4; // packet length
}
public class RemoteFileOutputStream

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Promise;
public class Request
public final class Request
extends SFTPPacket<Request> {
private final PacketType type;

View File

@@ -17,7 +17,7 @@ package net.schmizz.sshj.sftp;
import net.schmizz.sshj.common.Buffer;
public class Response
public final class Response
extends SFTPPacket<Response> {
public static enum StatusCode {
@@ -51,7 +51,8 @@ public class Response
private final PacketType type;
private final long reqID;
public Response(Buffer<Response> pk, int protocolVersion) throws SFTPException {
public Response(Buffer<Response> pk, int protocolVersion)
throws SFTPException {
super(pk);
this.protocolVersion = protocolVersion;
this.type = readType();
@@ -74,7 +75,8 @@ public class Response
return type;
}
public StatusCode readStatusCode() throws SFTPException {
public StatusCode readStatusCode()
throws SFTPException {
try {
return StatusCode.fromInt(readUInt32AsInt());
} catch (BufferException be) {

View File

@@ -47,7 +47,7 @@ public class SFTPClient
return engine;
}
public SFTPFileTransfer getFileTansfer() {
public SFTPFileTransfer getFileTransfer() {
return xfer;
}

View File

@@ -56,7 +56,8 @@ public class SFTPEngine
this(ssh, PathHelper.DEFAULT_PATH_SEPARATOR);
}
public SFTPEngine(SessionFactory ssh, String pathSep) throws SSHException {
public SFTPEngine(SessionFactory ssh, String pathSep)
throws SSHException {
sub = ssh.startSession().startSubsystem("sftp");
out = sub.getOutputStream();
reader = new PacketReader(this);

View File

@@ -15,6 +15,9 @@
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.xfer.LocalDestFile;
import net.schmizz.sshj.xfer.LocalSourceFile;
import java.io.IOException;
import java.util.List;
import java.util.Set;
@@ -178,10 +181,22 @@ public class StatefulSFTPClient
super.get(cwdify(source), dest);
}
@Override
public void get(String source, LocalDestFile dest)
throws IOException {
super.get(cwdify(source), dest);
}
@Override
public void put(String source, String dest)
throws IOException {
super.put(source, cwdify(dest));
}
@Override
public void put(LocalSourceFile source, String dest)
throws IOException {
super.put(source, cwdify(dest));
}
}

View File

@@ -90,9 +90,9 @@ public abstract class AbstractSignature
| sig[i++] & 0x000000ff;
i += j;
j = sig[i++] << 24 & 0xff000000
| sig[i++] << 16 & 0x00ff0000
| sig[i++] << 8 & 0x0000ff00
| sig[i++] & 0x000000ff;
| sig[i++] << 16 & 0x00ff0000
| sig[i++] << 8 & 0x0000ff00
| sig[i++] & 0x000000ff;
byte[] newSig = new byte[j];
System.arraycopy(sig, i, newSig, 0, j);
sig = newSig;

View File

@@ -47,7 +47,7 @@ import net.schmizz.sshj.transport.mac.MAC;
* <p/>
* <pre>
* Each packet is in the following format:
* <p/>
*
* uint32 packet_length
* byte padding_length
* byte[n1] payload; n1 = packet_length - padding_length - 1

View File

@@ -88,7 +88,7 @@ final class Decoder
int need;
/* Decoding loop */
for (; ;)
for (; ; )
if (packetLength == -1) // Waiting for beginning of packet
{
@@ -124,7 +124,7 @@ final class Decoder
if (log.isTraceEnabled())
log.trace("Received packet #{}: {}", seq, plain.printHex());
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet //
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet
inputBuffer.clear();
packetLength = -1;

View File

@@ -63,7 +63,7 @@ final class Encoder
private SSHPacket checkHeaderSpace(SSHPacket buffer) {
if (buffer.rpos() < 5) {
log.warn("Performance cost: when sending a packet, ensure that "
+ "5 bytes are available in front of the buffer");
+ "5 bytes are available in front of the buffer");
SSHPacket nb = new SSHPacket(buffer.available() + 5);
nb.rpos(5);
nb.wpos(5);
@@ -96,8 +96,6 @@ final class Encoder
long encode(SSHPacket buffer) {
encodeLock.lock();
try {
buffer = checkHeaderSpace(buffer);
if (log.isTraceEnabled())
log.trace("Encoding packet #{}: {}", seq, buffer.printHex());

View File

@@ -35,13 +35,12 @@
*/
package net.schmizz.sshj.transport;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.ErrorDeliveryUtil;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
@@ -57,6 +56,7 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Arrays;
@@ -234,17 +234,16 @@ final class KeyExchanger
private void gotKexInit(SSHPacket buf)
throws TransportException {
Proposal serverProposal = new Proposal(buf);
buf.rpos(buf.rpos() - 1);
final Proposal serverProposal = new Proposal(buf);
negotiatedAlgs = clientProposal.negotiate(serverProposal);
log.debug("Negotiated algorithms: {}", negotiatedAlgs);
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(), negotiatedAlgs
.getKeyExchangeAlgorithm());
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(),
negotiatedAlgs.getKeyExchangeAlgorithm());
try {
kex.init(transport,
transport.getServerID().getBytes(IOUtils.UTF8),
transport.getClientID().getBytes(IOUtils.UTF8),
buf.getCompactData(),
clientProposal.getPacket().getCompactData());
transport.getServerID(), transport.getClientID(),
serverProposal.getPacket().getCompactData(), clientProposal.getPacket().getCompactData());
} catch (GeneralSecurityException e) {
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, e);
}
@@ -262,7 +261,7 @@ final class KeyExchanger
*
* @return the resized key
*/
private static byte[] resizedKey(byte[] E, int blockSize, Digest hash, byte[] K, byte[] H) {
private static byte[] resizedKey(byte[] E, int blockSize, Digest hash, BigInteger K, byte[] H) {
while (blockSize > E.length) {
Buffer.PlainBuffer buffer = new Buffer.PlainBuffer().putMPInt(K).putRawBytes(H).putRawBytes(E);
hash.update(buffer.array(), 0, buffer.available());
@@ -280,13 +279,15 @@ final class KeyExchanger
private void gotNewKeys() {
final Digest hash = kex.getHash();
final byte[] H = kex.getH();
if (sessionID == null)
// session id is 'H' from the first key exchange and does not change thereafter
sessionID = Arrays.copyOf(kex.getH(), kex.getH().length);
sessionID = H;
final Buffer.PlainBuffer hashInput = new Buffer.PlainBuffer()
.putMPInt(kex.getK())
.putRawBytes(kex.getH())
.putRawBytes(H)
.putByte((byte) 0) // <placeholder>
.putRawBytes(sessionID);
final int pos = hashInput.available() - sessionID.length - 1; // Position of <placeholder>
@@ -360,7 +361,6 @@ final class KeyExchanger
* having sent the packet ourselves (would cause gotKexInit() to fail)
*/
kexInitSent.await(transport.getTimeout(), TimeUnit.SECONDS);
buf.rpos(buf.rpos() - 1);
gotKexInit(buf);
expected = Expected.FOLLOWUP;
break;

View File

@@ -92,15 +92,15 @@ public final class NegotiatedAlgorithms {
@Override
public String toString() {
return ("[ " + //
"kex=" + kex + "; " + //
"sig=" + sig + "; " + //
"c2sCipher=" + c2sCipher + "; " + //
"s2cCipher=" + s2cCipher + "; " + //
"c2sMAC=" + c2sMAC + "; " + //
"s2cMAC=" + s2cMAC + "; " + //
"c2sComp=" + c2sComp + "; " + //
"s2cComp=" + s2cComp + //
return ("[ " +
"kex=" + kex + "; " +
"sig=" + sig + "; " +
"c2sCipher=" + c2sCipher + "; " +
"s2cCipher=" + s2cCipher + "; " +
"c2sMAC=" + c2sMAC + "; " +
"s2cMAC=" + s2cMAC + "; " +
"c2sComp=" + c2sComp + "; " +
"s2cComp=" + s2cComp +
" ]");
}

View File

@@ -86,7 +86,8 @@ class Proposal {
packet.putUInt32(0); // "Reserved" for future by spec
}
public Proposal(SSHPacket packet) throws TransportException {
public Proposal(SSHPacket packet)
throws TransportException {
this.packet = packet;
final int savedPos = packet.rpos();
packet.rpos(packet.rpos() + 17); // Skip message ID & cookie
@@ -144,14 +145,14 @@ class Proposal {
public NegotiatedAlgorithms negotiate(Proposal other)
throws TransportException {
return new NegotiatedAlgorithms(
firstMatch(this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()), //
firstMatch(this.getSignatureAlgorithms(), other.getSignatureAlgorithms()), //
firstMatch(this.getClient2ServerCipherAlgorithms(), other.getClient2ServerCipherAlgorithms()), //
firstMatch(this.getServer2ClientCipherAlgorithms(), other.getServer2ClientCipherAlgorithms()), //
firstMatch(this.getClient2ServerMACAlgorithms(), other.getClient2ServerMACAlgorithms()), //
firstMatch(this.getServer2ClientMACAlgorithms(), other.getServer2ClientMACAlgorithms()), //
firstMatch(this.getClient2ServerCompressionAlgorithms(), other.getClient2ServerCompressionAlgorithms()), //
firstMatch(this.getServer2ClientCompressionAlgorithms(), other.getServer2ClientCompressionAlgorithms()) //
firstMatch(this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
firstMatch(this.getSignatureAlgorithms(), other.getSignatureAlgorithms()),
firstMatch(this.getClient2ServerCipherAlgorithms(), other.getClient2ServerCipherAlgorithms()),
firstMatch(this.getServer2ClientCipherAlgorithms(), other.getServer2ClientCipherAlgorithms()),
firstMatch(this.getClient2ServerMACAlgorithms(), other.getClient2ServerMACAlgorithms()),
firstMatch(this.getServer2ClientMACAlgorithms(), other.getServer2ClientMACAlgorithms()),
firstMatch(this.getClient2ServerCompressionAlgorithms(), other.getClient2ServerCompressionAlgorithms()),
firstMatch(this.getServer2ClientCompressionAlgorithms(), other.getServer2ClientCompressionAlgorithms())
);
}

View File

@@ -178,12 +178,14 @@ public interface Transport
*/
void join()
throws TransportException;
/**
* Joins the thread calling this method to the transport's death.
*
* @throws TransportException if the transport dies of an exception
*/
void join(int timeout, TimeUnit unit) throws TransportException;
void join(int timeout, TimeUnit unit)
throws TransportException;
/** Send a disconnection packet with reason as {@link DisconnectReason#BY_APPLICATION}, and closes this transport. */
void disconnect();
@@ -226,9 +228,7 @@ public interface Transport
*/
void setDisconnectListener(DisconnectListener listener);
/**
* @return the current disconnect listener.
*/
/** @return the current disconnect listener. */
DisconnectListener getDisconnectListener();
}

View File

@@ -35,8 +35,8 @@
*/
package net.schmizz.sshj.transport;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.ErrorDeliveryUtil;
import net.schmizz.concurrent.Event;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.Service;
@@ -62,12 +62,14 @@ public final class TransportImpl
private static final class NullService
extends AbstractService {
NullService(Transport trans) {
super("null-service", trans);
}
}
static final class ConnInfo {
final String host;
final int port;
final InputStream in;
@@ -185,11 +187,11 @@ public final class TransportImpl
String ident;
byte[] data = new byte[256];
for (; ;) {
for (; ; ) {
int savedBufPos = buffer.rpos();
int pos = 0;
boolean needLF = false;
for (; ;) {
for (; ; ) {
if (buffer.available() == 0) {
// Need more data, so undo reading and return null
buffer.rpos(savedBufPos);
@@ -217,7 +219,7 @@ public final class TransportImpl
if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
"Server does not support SSHv2, identified as: " + ident);
"Server does not support SSHv2, identified as: " + ident);
return ident;
}
@@ -438,9 +440,9 @@ public final class TransportImpl
log.debug("Sending SSH_MSG_DISCONNECT: reason=[{}], msg=[{}]", reason, message);
try {
write(new SSHPacket(Message.DISCONNECT)
.putUInt32(reason.toInt())
.putString(message)
.putString(""));
.putUInt32(reason.toInt())
.putString(message)
.putString(""));
} catch (IOException worthless) {
log.debug("Error writing packet: {}", worthless.toString());
}
@@ -501,7 +503,8 @@ public final class TransportImpl
}
}
private void gotDebug(SSHPacket buf) throws TransportException {
private void gotDebug(SSHPacket buf)
throws TransportException {
try {
final boolean display = buf.readBoolean();
final String message = buf.readString();
@@ -529,7 +532,7 @@ public final class TransportImpl
try {
if (!serviceAccept.hasWaiters())
throw new TransportException(DisconnectReason.PROTOCOL_ERROR,
"Got a service accept notification when none was awaited");
"Got a service accept notification when none was awaited");
serviceAccept.set();
} finally {
serviceAccept.unlock();

View File

@@ -42,6 +42,7 @@ public class AES128CBC
/** Named factory for AES128CBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new AES128CBC();

View File

@@ -42,6 +42,7 @@ public class AES128CTR
/** Named factory for AES128CBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new AES128CTR();

View File

@@ -42,6 +42,7 @@ public class AES192CBC
/** Named factory for AES192CBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new AES192CBC();

View File

@@ -42,6 +42,7 @@ public class AES192CTR
/** Named factory for AES192CTR Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new AES192CTR();

View File

@@ -42,6 +42,7 @@ public class AES256CBC
/** Named factory for AES256CBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new AES256CBC();

View File

@@ -42,6 +42,7 @@ public class AES256CTR
/** Named factory for AES256CBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new AES256CTR();

View File

@@ -42,6 +42,7 @@ public class BlowfishCBC
/** Named factory for BlowfishCBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new BlowfishCBC();

View File

@@ -42,6 +42,7 @@ public class NoneCipher
/** Named factory for the no-op Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new NoneCipher();

View File

@@ -42,6 +42,7 @@ public class TripleDESCBC
/** Named factory for TripleDESCBC Cipher */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
@Override
public Cipher create() {
return new TripleDESCBC();

View File

@@ -46,6 +46,7 @@ public class DelayedZlibCompression
/** Named factory for the ZLib Delayed Compression. */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Compression> {
@Override
public Compression create() {
return new DelayedZlibCompression();

View File

@@ -42,6 +42,7 @@ public abstract class NoneCompression
/** Named factory for the no-op <code>Compression</code> */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Compression> {
@Override
public Compression create() {
return null;

View File

@@ -49,6 +49,7 @@ public class ZlibCompression
/** Named factory for the ZLib Compression. */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Compression> {
@Override
public Compression create() {
return new ZlibCompression();
@@ -125,7 +126,7 @@ public class ZlibCompression
return;
default:
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned "
+ status);
+ status);
}
}
}

View File

@@ -36,7 +36,6 @@
package net.schmizz.sshj.transport.kex;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.KeyType;
@@ -53,6 +52,7 @@ import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Arrays;
/**
* Base class for DHG key exchange algorithms. Implementations will only have to configure the required data on the
@@ -65,32 +65,30 @@ public abstract class AbstractDHG
private Transport trans;
private final Digest sha = new SHA1();
private final Digest sha1 = new SHA1();
private final DH dh = new DH();
private byte[] V_S;
private byte[] V_C;
private String V_S;
private String V_C;
private byte[] I_S;
private byte[] I_C;
private byte[] e;
private byte[] K;
private byte[] H;
private PublicKey hostKey;
@Override
public byte[] getH() {
return ByteArrayUtils.copyOf(H);
return Arrays.copyOf(H, H.length);
}
@Override
public byte[] getK() {
return ByteArrayUtils.copyOf(K);
public BigInteger getK() {
return dh.getK();
}
@Override
public Digest getHash() {
return sha;
return sha1;
}
@Override
@@ -99,19 +97,18 @@ public abstract class AbstractDHG
}
@Override
public void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C)
public void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C)
throws GeneralSecurityException, TransportException {
this.trans = trans;
this.V_S = ByteArrayUtils.copyOf(V_S);
this.V_C = ByteArrayUtils.copyOf(V_C);
this.I_S = ByteArrayUtils.copyOf(I_S);
this.I_C = ByteArrayUtils.copyOf(I_C);
sha.init();
this.V_S = V_S;
this.V_C = V_C;
this.I_S = Arrays.copyOf(I_S, I_S.length);
this.I_C = Arrays.copyOf(I_C, I_C.length);
sha1.init();
initDH(dh);
e = dh.getE();
log.info("Sending SSH_MSG_KEXDH_INIT");
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(e));
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(dh.getE()));
}
@Override
@@ -122,19 +119,18 @@ public abstract class AbstractDHG
log.info("Received SSH_MSG_KEXDH_REPLY");
final byte[] K_S;
final byte[] f;
final BigInteger f;
final byte[] sig; // signature sent by server
try {
K_S = packet.readBytes();
f = packet.readMPIntAsBytes();
f = packet.readMPInt();
sig = packet.readBytes();
hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
} catch (Buffer.BufferException be) {
throw new TransportException(be);
}
dh.setF(new BigInteger(f));
K = dh.getK();
dh.computeK(f);
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer()
.putString(V_C)
@@ -142,11 +138,11 @@ public abstract class AbstractDHG
.putString(I_C)
.putString(I_S)
.putString(K_S)
.putMPInt(e)
.putMPInt(dh.getE())
.putMPInt(f)
.putMPInt(K);
sha.update(buf.array(), 0, buf.available());
H = sha.digest();
.putMPInt(dh.getK());
sha1.update(buf.array(), buf.rpos(), buf.available());
H = sha1.digest();
Signature signature = Factory.Named.Util.create(trans.getConfig().getSignatureFactories(),
KeyType.fromKey(hostKey).toString());
@@ -158,6 +154,7 @@ public abstract class AbstractDHG
return true;
}
protected abstract void initDH(DH dh);
protected abstract void initDH(DH dh)
throws GeneralSecurityException;
}

View File

@@ -54,7 +54,6 @@ public class DH {
private BigInteger p;
private BigInteger g;
private BigInteger e; // my public key
private BigInteger f; // your public key
private BigInteger K; // shared secret key
private final KeyPairGenerator generator;
private final KeyAgreement agreement;
@@ -68,39 +67,30 @@ public class DH {
}
}
public void setF(BigInteger f) {
this.f = f;
}
public void setG(BigInteger g) {
this.g = g;
}
public void setP(BigInteger p) {
public void init(BigInteger p, BigInteger g)
throws GeneralSecurityException {
this.p = p;
this.g = g;
generator.initialize(new DHParameterSpec(p, g));
final KeyPair kp = generator.generateKeyPair();
agreement.init(kp.getPrivate());
e = ((javax.crypto.interfaces.DHPublicKey) kp.getPublic()).getY();
}
public byte[] getE()
public void computeK(BigInteger f)
throws GeneralSecurityException {
if (e == null) {
generator.initialize(new DHParameterSpec(p, g));
final KeyPair kp = generator.generateKeyPair();
agreement.init(kp.getPrivate());
e = ((javax.crypto.interfaces.DHPublicKey) kp.getPublic()).getY();
}
return e.toByteArray();
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("DH");
final PublicKey yourPubKey = keyFactory.generatePublic(new DHPublicKeySpec(f, p, g));
agreement.doPhase(yourPubKey, true);
K = new BigInteger(1, agreement.generateSecret());
}
public byte[] getK()
throws GeneralSecurityException {
if (K == null) {
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("DH");
final DHPublicKeySpec keySpec = new DHPublicKeySpec(f, p, g);
final PublicKey yourPubKey = keyFactory.generatePublic(keySpec);
agreement.doPhase(yourPubKey, true);
K = new BigInteger(agreement.generateSecret());
}
return K.toByteArray();
public BigInteger getE() {
return e;
}
public BigInteger getK() {
return K;
}
}

View File

@@ -35,6 +35,8 @@
*/
package net.schmizz.sshj.transport.kex;
import java.security.GeneralSecurityException;
/**
* Diffie-Hellman key exchange with SHA-1 and Oakley Group 2 [RFC2409] (1024-bit MODP Group).
*
@@ -60,9 +62,9 @@ public class DHG1
}
@Override
protected void initDH(DH dh) {
dh.setG(DHGroupData.G);
dh.setP(DHGroupData.P1);
protected void initDH(DH dh)
throws GeneralSecurityException {
dh.init(DHGroupData.P1, DHGroupData.G);
}
}

View File

@@ -35,6 +35,8 @@
*/
package net.schmizz.sshj.transport.kex;
import java.security.GeneralSecurityException;
/**
* Diffie-Hellman key exchange with SHA-1 and Oakley Group 14 [RFC3526] (2048-bit MODP Group).
* <p/>
@@ -61,9 +63,9 @@ public class DHG14
}
@Override
protected void initDH(DH dh) {
dh.setG(DHGroupData.G);
dh.setP(DHGroupData.P14);
protected void initDH(DH dh)
throws GeneralSecurityException {
dh.init(DHGroupData.P14, DHGroupData.G);
}
}

View File

@@ -45,18 +45,18 @@ public final class DHGroupData {
public static final BigInteger P1 =
new BigInteger("1797693134862315907708391567937874531978602960487560117064444236841971802161585193" +
"6894783379586492554150218056548598050364644054819923910005079287700335581663922955" +
"3136239076508735759914822574862575007425302077447712589550957937778424442426617334" +
"727629299387668709205606050270810842907692932019128194467627007");
"6894783379586492554150218056548598050364644054819923910005079287700335581663922955" +
"3136239076508735759914822574862575007425302077447712589550957937778424442426617334" +
"727629299387668709205606050270810842907692932019128194467627007");
public static final BigInteger P14 =
new BigInteger("3231700607131100730033891392642382824881794124114023911284200975140074170663435422" +
"2619689417363569347117901737909704191754605873209195028853758986185622153212175412" +
"5149017745202702357960782362488842461894775876411059286460994117232454266225221932" +
"3054091903768052423551912567971587011700105805587765103886184728025797605490356973" +
"2561526167081339361799541336476559160368317896729073178384589680639671900977202194" +
"1686472258710314113364293195361934716365332097170774482279885885653692086452966360" +
"7725026895550592836275112117409697299806841055435958486658329164213621823107899099" +
"9448652468262416972035911852507045361090559");
"2619689417363569347117901737909704191754605873209195028853758986185622153212175412" +
"5149017745202702357960782362488842461894775876411059286460994117232454266225221932" +
"3054091903768052423551912567971587011700105805587765103886184728025797605490356973" +
"2561526167081339361799541336476559160368317896729073178384589680639671900977202194" +
"1686472258710314113364293195361934716365332097170774482279885885653692086452966360" +
"7725026895550592836275112117409697299806841055435958486658329164213621823107899099" +
"9448652468262416972035911852507045361090559");
}

View File

@@ -41,6 +41,7 @@ import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.digest.Digest;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
@@ -59,14 +60,14 @@ public interface KeyExchange {
* @throws GeneralSecurityException
* @throws TransportException if there is an error sending a packet
*/
void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C)
void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C)
throws GeneralSecurityException, TransportException;
/** @return the computed H parameter */
byte[] getH();
/** @return the computed K parameter */
byte[] getK();
BigInteger getK();
/**
* The message digest used by this key exchange algorithm.

View File

@@ -58,10 +58,9 @@ public class BouncyCastleRandom
}
private final RandomGenerator random;
private final RandomGenerator random = new VMPCRandomGenerator();
public BouncyCastleRandom() {
random = new VMPCRandomGenerator();
byte[] seed = new SecureRandom().generateSeed(8);
random.addSeedMaterial(seed);
}

View File

@@ -58,11 +58,7 @@ public class JCERandom
}
private byte[] tmp = new byte[16];
private SecureRandom random = null;
public JCERandom() {
random = new SecureRandom();
}
private final SecureRandom random = new SecureRandom();
/**
* Fill the given byte-array with random bytes from this PRNG.

View File

@@ -40,6 +40,7 @@ import net.schmizz.sshj.common.Factory;
/** A random factory wrapper that uses a single random instance. The underlying random instance has to be thread safe. */
public class SingletonRandomFactory
implements Random, Factory<Random> {
private final Random random;
public SingletonRandomFactory(Factory<Random> factory) {

View File

@@ -41,14 +41,14 @@ public class ConsoleKnownHostsVerifier
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
final KeyType type = KeyType.fromKey(key);
console.printf("The authenticity of host '%s' can't be established.\n" +
"%s key fingerprint is %s.\n", hostname, type, SecurityUtils.getFingerprint(key));
"%s key fingerprint is %s.\n", hostname, type, SecurityUtils.getFingerprint(key));
String response = console.readLine("Are you sure you want to continue connecting (yes/no)? ");
while (!(response.equalsIgnoreCase(YES) || response.equalsIgnoreCase(NO))) {
response = console.readLine("Please explicitly enter yes/no: ");
}
if (response.equalsIgnoreCase(YES)) {
try {
entries().add(new SimpleEntry(hostname, key));
entries().add(new SimpleEntry(null, hostname, KeyType.fromKey(key), key));
write();
console.printf("Warning: Permanently added '%s' (%s) to the list of known hosts.\n", hostname, type);
} catch (IOException e) {
@@ -60,21 +60,21 @@ public class ConsoleKnownHostsVerifier
}
@Override
protected boolean hostKeyChangedAction(Entry entry, String hostname, PublicKey key) {
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
final KeyType type = KeyType.fromKey(key);
final String fp = SecurityUtils.getFingerprint(key);
final String path = getFile().getAbsolutePath();
console.printf(
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n" +
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
"It is also possible that the host key has just been changed.\n" +
"The fingerprint for the %s key sent by the remote host is\n" +
"%s.\n" +
"Please contact your system administrator or" +
"add correct host key in %s to get rid of this message.\n",
"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n" +
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
"It is also possible that the host key has just been changed.\n" +
"The fingerprint for the %s key sent by the remote host is\n" +
"%s.\n" +
"Please contact your system administrator or" +
"add correct host key in %s to get rid of this message.\n",
type, fp, path);
return false;
}

View File

@@ -20,6 +20,7 @@ import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.MAC;
import org.slf4j.Logger;
@@ -31,7 +32,10 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -44,187 +48,11 @@ import java.util.List;
public class OpenSSHKnownHosts
implements HostKeyVerifier {
public static abstract class Entry {
private KeyType type;
private PublicKey key;
private String sKey;
protected void init(PublicKey key)
throws SSHException {
this.key = key;
this.type = KeyType.fromKey(key);
if (type == KeyType.UNKNOWN)
throw new SSHException("Unknown key type for key: " + key);
}
protected void init(String typeString, String keyString)
throws SSHException {
this.sKey = keyString;
this.type = KeyType.fromString(typeString);
if (type == KeyType.UNKNOWN)
throw new SSHException("Unknown key type: " + typeString);
}
public KeyType getType() {
return type;
}
public PublicKey getKey()
throws IOException {
if (key == null) {
key = new Buffer.PlainBuffer(Base64.decode(sKey)).readPublicKey();
}
return key;
}
protected String getKeyString() {
if (sKey == null) {
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(key);
sKey = Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
}
return sKey;
}
public String getLine() {
final StringBuilder line = new StringBuilder();
line.append(getHostPart());
line.append(" ").append(type.toString());
line.append(" ").append(getKeyString());
return line.toString();
}
@Override
public String toString() {
return "KnownHostsEntry{host=" + getHostPart() + "; type=" + type + "}";
}
protected abstract String getHostPart();
public abstract boolean appliesTo(String host)
throws IOException;
}
public static class SimpleEntry
extends Entry {
private final List<String> hosts;
public SimpleEntry(String host, PublicKey key)
throws SSHException {
this(Arrays.asList(host), key);
}
public SimpleEntry(List<String> hosts, PublicKey key)
throws SSHException {
this.hosts = hosts;
init(key);
}
public SimpleEntry(String line)
throws SSHException {
final String[] parts = line.split(" ");
if (parts.length != 3)
throw new SSHException("Line parts not 3: " + line);
hosts = Arrays.asList(parts[0].split(","));
init(parts[1], parts[2]);
}
@Override
public boolean appliesTo(String host) {
for (String h : hosts)
if (host.equals(h))
return true;
return false;
}
@Override
protected String getHostPart() {
final StringBuilder sb = new StringBuilder();
for (String host : hosts) {
if (sb.length() > 0) // a host already in there
sb.append(",");
sb.append(host);
}
return sb.toString();
}
}
public static class HashedEntry
extends Entry {
private final MAC sha1 = new HMACSHA1();
private String salt;
private byte[] saltyBytes;
private final String hashedHost;
public HashedEntry(String host, PublicKey key)
throws IOException {
{
saltyBytes = new byte[sha1.getBlockSize()];
new java.util.Random().nextBytes(saltyBytes);
}
this.hashedHost = hashHost(host);
init(key);
}
public HashedEntry(String line)
throws IOException {
final String[] parts = line.split(" ");
if (parts.length != 3)
throw new SSHException("Line parts not 3: " + line);
hashedHost = parts[0];
{
final String[] hostParts = hashedHost.split("\\|");
if (hostParts.length != 4)
throw new SSHException("Unrecognized format for hashed hostname");
salt = hostParts[2];
}
init(parts[1], parts[2]);
}
@Override
public boolean appliesTo(String host)
throws IOException {
return hashedHost.equals(hashHost(host));
}
private String hashHost(String host)
throws IOException {
sha1.init(getSaltyBytes());
return "|1|" + getSalt() + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
}
private byte[] getSaltyBytes()
throws IOException {
if (saltyBytes == null) {
saltyBytes = Base64.decode(salt);
}
return saltyBytes;
}
private String getSalt() {
if (salt == null) {
salt = Base64.encodeBytes(saltyBytes);
}
return salt;
}
@Override
protected String getHostPart() {
return hashedHost;
}
}
private static final Logger LOG = LoggerFactory.getLogger(OpenSSHKnownHosts.class);
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final File khFile;
protected final List<Entry> entries = new ArrayList<Entry>();
protected final List<HostEntry> entries = new ArrayList<HostEntry>();
public OpenSSHKnownHosts(File khFile)
throws IOException {
@@ -236,7 +64,10 @@ public class OpenSSHKnownHosts
String line;
while ((line = br.readLine()) != null)
try {
entries.add(isHashed(line) ? new HashedEntry(line) : new SimpleEntry(line));
HostEntry entry = EntryFactory.parseEntry(line);
if (entry != null) {
entries.add(entry);
}
} catch (SSHException ignore) {
log.debug("Bad line ({}): {} ", ignore.toString(), line);
}
@@ -253,19 +84,22 @@ public class OpenSSHKnownHosts
@Override
public boolean verify(final String hostname, final int port, final PublicKey key) {
final KeyType type = KeyType.fromKey(key);
if (type == KeyType.UNKNOWN)
return false;
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
for (Entry e : entries)
for (HostEntry e : entries) {
try {
if (e.getType() == type && e.appliesTo(adjustedHostname))
return key.equals(e.getKey()) || hostKeyChangedAction(e, adjustedHostname, key);
if (e.appliesTo(type, adjustedHostname))
return e.verify(key) || hostKeyChangedAction(e, adjustedHostname, key);
} catch (IOException ioe) {
log.error("Error with {}: {}", e, ioe);
return false;
}
}
return hostKeyUnverifiableAction(adjustedHostname, key);
}
@@ -273,12 +107,12 @@ public class OpenSSHKnownHosts
return false;
}
protected boolean hostKeyChangedAction(Entry entry, String hostname, PublicKey key) {
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
log.warn("Host key for `{}` has changed!", hostname);
return false;
}
public List<Entry> entries() {
public List<HostEntry> entries() {
return entries;
}
@@ -288,7 +122,7 @@ public class OpenSSHKnownHosts
throws IOException {
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(khFile));
try {
for (Entry entry : entries)
for (HostEntry entry : entries)
bos.write((entry.getLine() + LS).getBytes(IOUtils.UTF8));
} finally {
bos.close();
@@ -300,8 +134,280 @@ public class OpenSSHKnownHosts
return sshDir.exists() ? sshDir : null;
}
public static boolean isHashed(String line) {
return line.startsWith("|1|");
/**
* Each line in these files contains the following fields: markers
* (optional), hostnames, bits, exponent, modulus, comment. The fields are
* separated by spaces.
* <p/>
* The marker is optional, but if it is present then it must be one of
* ``@cert-authority'', to indicate that the line contains a certification
* authority (CA) key, or ``@revoked'', to indicate that the key contained
* on the line is revoked and must not ever be accepted. Only one marker
* should be used on a key line.
* <p/>
* Hostnames is a comma-separated list of patterns (`*' and `?' act as
* wildcards); each pattern in turn is matched against the canonical host
* name (when authenticating a client) or against the user-supplied name
* (when authenticating a server). A pattern may also be preceded by `!' to
* indicate negation: if the host name matches a negated pattern, it is not
* accepted (by that line) even if it matched another pattern on the line.
* A hostname or address may optionally be enclosed within `[' and `]'
* brackets then followed by `:' and a non-standard port number.
* <p/>
* Alternately, hostnames may be stored in a hashed form which hides host
* names and addresses should the file's contents be disclosed. Hashed
* hostnames start with a `|' character. Only one hashed hostname may
* appear on a single line and none of the above negation or wildcard
* operators may be applied.
* <p/>
* Bits, exponent, and modulus are taken directly from the RSA host key;
* they can be obtained, for example, from /etc/ssh/ssh_host_key.pub. The
* optional comment field continues to the end of the line, and is not used.
* <p/>
* Lines starting with `#' and empty lines are ignored as comments.
*/
public static class EntryFactory {
public static HostEntry parseEntry(String line)
throws IOException {
if (isComment(line)) {
return new CommentEntry(line);
}
final String[] split = line.split(" ");
int i = 0;
final Marker marker = Marker.fromString(split[i]);
if (marker != null) {
i++;
}
final String hostnames = split[i++];
final String sType = split[i++];
KeyType type = KeyType.fromString(sType);
PublicKey key;
if (type != KeyType.UNKNOWN) {
final String sKey = split[i++];
key = getKey(sKey);
} else if (isBits(sType)) {
type = KeyType.RSA;
// int bits = Integer.valueOf(sType);
final BigInteger e = new BigInteger(split[i++]);
final BigInteger n = new BigInteger(split[i++]);
try {
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("RSA");
key = keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
} catch (Exception ex) {
LOG.error("Error reading entry `{}`, could not create key", line, ex);
return null;
}
} else {
LOG.error("Error reading entry `{}`, could not determine type", line);
return null;
}
if (isHashed(hostnames)) {
return new HashedEntry(marker, hostnames, type, key);
} else {
return new SimpleEntry(marker, hostnames, type, key);
}
}
private static PublicKey getKey(String sKey)
throws IOException {
return new Buffer.PlainBuffer(Base64.decode(sKey)).readPublicKey();
}
private static boolean isBits(String type) {
try {
Integer.parseInt(type);
return true;
} catch (NumberFormatException e) {
return false;
}
}
private static boolean isComment(String line) {
return line.isEmpty() || line.startsWith("#");
}
public static boolean isHashed(String line) {
return line.startsWith("|1|");
}
}
public interface HostEntry {
boolean appliesTo(KeyType type, String host)
throws IOException;
boolean verify(PublicKey key)
throws IOException;
String getLine();
}
public static class CommentEntry
implements HostEntry {
private final String comment;
public CommentEntry(String comment) {
this.comment = comment;
}
@Override
public boolean appliesTo(KeyType type, String host) {
return false;
}
@Override
public boolean verify(PublicKey key) {
return false;
}
@Override
public String getLine() {
return comment;
}
}
public static abstract class AbstractEntry
implements HostEntry {
protected final OpenSSHKnownHosts.Marker marker;
protected final KeyType type;
protected final PublicKey key;
public AbstractEntry(Marker marker, KeyType type, PublicKey key) {
this.marker = marker;
this.type = type;
this.key = key;
}
@Override
public boolean verify(PublicKey key)
throws IOException {
return key.equals(this.key) && marker != Marker.REVOKED;
}
public String getLine() {
final StringBuilder line = new StringBuilder();
if (marker != null) line.append(marker.getMarkerString()).append(" ");
line.append(getHostPart());
line.append(" ").append(type.toString());
line.append(" ").append(getKeyString());
return line.toString();
}
private String getKeyString() {
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(key);
return Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
}
protected abstract String getHostPart();
}
public static class SimpleEntry
extends AbstractEntry {
private final List<String> hosts;
private final String hostnames;
public SimpleEntry(Marker marker, String hostnames, KeyType type, PublicKey key) {
super(marker, type, key);
this.hostnames = hostnames;
hosts = Arrays.asList(hostnames.split(","));
}
@Override
protected String getHostPart() {
return hostnames;
}
@Override
public boolean appliesTo(KeyType type, String host)
throws IOException {
return type == this.type && hostnames.contains(host);
}
}
public static class HashedEntry
extends AbstractEntry {
private final MAC sha1 = new HMACSHA1();
private final String hashedHost;
private final String salt;
private byte[] saltyBytes;
public HashedEntry(Marker marker, String hash, KeyType type, PublicKey key)
throws SSHException {
super(marker, type, key);
this.hashedHost = hash;
{
final String[] hostParts = hashedHost.split("\\|");
if (hostParts.length != 4)
throw new SSHException("Unrecognized format for hashed hostname");
salt = hostParts[2];
}
}
@Override
public boolean appliesTo(KeyType type, String host)
throws IOException {
return this.type == type && hashedHost.equals(hashHost(host));
}
private String hashHost(String host)
throws IOException {
sha1.init(getSaltyBytes());
return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
}
private byte[] getSaltyBytes()
throws IOException {
if (saltyBytes == null) {
saltyBytes = Base64.decode(salt);
}
return saltyBytes;
}
@Override
public String getLine() {
return null;
}
@Override
protected String getHostPart() {
return hashedHost;
}
}
public enum Marker {
CA_CERT("@cert-authority"),
REVOKED("@revoked");
private final String sMarker;
Marker(String sMarker) {
this.sMarker = sMarker;
}
public String getMarkerString() {
return sMarker;
}
public static Marker fromString(String str) {
for (Marker m: values())
if (m.sMarker.equals(str))
return m;
return null;
}
}
}

View File

@@ -49,7 +49,7 @@ public interface UserAuth {
* Returns the authentication banner (if any). In some cases this is available even before the first authentication
* request has been made.
*
* @return the banner, or {@code null} if none was received
* @return the banner, or an empty string if none was received
*/
String getBanner();

View File

@@ -139,11 +139,13 @@ public class UserAuthImpl
case USERAUTH_BANNER: {
banner = buf.readString();
} break;
}
break;
case USERAUTH_SUCCESS: {
authenticated.set();
} break;
}
break;
case USERAUTH_FAILURE: {
allowedMethods.clear();
@@ -154,7 +156,8 @@ public class UserAuthImpl
} else {
authenticated.deliverError(new UserAuthException(currentMethod.getName() + " auth failed"));
}
} break;
}
break;
default: {
log.debug("Asking `{}` method to handle {} packet", currentMethod.getName(), msg);

View File

@@ -33,4 +33,7 @@ public interface FileKeyProvider
void init(File location, PasswordFinder pwdf);
void init(String privateKey, String publicKey);
void init(String privateKey, String publicKey, PasswordFinder pwdf);
}

View File

@@ -21,6 +21,8 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class KeyProviderUtil {
@@ -37,13 +39,50 @@ public class KeyProviderUtil {
*/
public static FileKeyProvider.Format detectKeyFileFormat(File location)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(location));
return detectKeyFileFormat(new FileReader(location),
new File(location + ".pub").exists());
}
/**
* Attempts to detect how a key file is encoded.
* <p/>
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
*
* @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
*/
public static FileKeyProvider.Format detectKeyFileFormat(String privateKey,
boolean separatePubKey)
throws IOException {
return detectKeyFileFormat(new StringReader(privateKey), separatePubKey);
}
/**
* Attempts to detect how a key file is encoded.
* <p/>
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
*
* @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
*/
private static FileKeyProvider.Format detectKeyFileFormat(Reader privateKey,
boolean separatePubKey)
throws IOException {
BufferedReader br = new BufferedReader(privateKey);
String firstLine = br.readLine();
IOUtils.closeQuietly(br);
if (firstLine == null)
throw new IOException("Empty file");
if (firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----"))
if (new File(location + ".pub").exists())
if (separatePubKey)
// Can delay asking for password since have unencrypted pubkey
return FileKeyProvider.Format.OpenSSH;
else
@@ -54,5 +93,4 @@ public class KeyProviderUtil {
*/
return FileKeyProvider.Format.Unknown;
}
}

View File

@@ -23,8 +23,11 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.security.PublicKey;
/**
* Represents an OpenSSH identity that consists of a PKCS8-encoded private key file and an unencrypted public key file
* of the same name with the {@code ".pub"} extension. This allows to delay requesting of the passphrase until the
@@ -62,18 +65,7 @@ public class OpenSSHKeyFile
final File f = new File(location + ".pub");
if (f.exists())
try {
final BufferedReader br = new BufferedReader(new FileReader(f));
try {
final String keydata = br.readLine();
if (keydata != null) {
String[] parts = keydata.split(" ");
assert parts.length >= 2;
type = KeyType.fromString(parts[0]);
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
}
} finally {
br.close();
}
initPubKey(new FileReader(f));
} catch (IOException e) {
// let super provide both public & private key
log.warn("Error reading public key file: {}", e.toString());
@@ -81,4 +73,36 @@ public class OpenSSHKeyFile
super.init(location);
}
@Override
public void init(String privateKey, String publicKey) {
if (publicKey != null) {
initPubKey(new StringReader(publicKey));
}
super.init(privateKey, null);
}
/**
* Read and store the separate public key provided alongside the private key
*
* @param publicKey Public key accessible through a {@code Reader}
*/
private void initPubKey(Reader publicKey) {
try {
final BufferedReader br = new BufferedReader(publicKey);
try {
final String keydata = br.readLine();
if (keydata != null) {
String[] parts = keydata.split(" ");
assert parts.length >= 2;
type = KeyType.fromString(parts[0]);
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
}
} finally {
br.close();
}
} catch (IOException e) {
// let super provide both public & private key
log.warn("Error reading public key: {}", e.toString());
}
}
}

View File

@@ -20,15 +20,15 @@ import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
import net.schmizz.sshj.userauth.password.Resource;
import org.bouncycastle.openssl.EncryptionException;
import org.bouncycastle.openssl.PEMReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -39,6 +39,7 @@ public class PKCS8KeyFile
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
@Override
public FileKeyProvider create() {
return new PKCS8KeyFile();
@@ -52,7 +53,8 @@ public class PKCS8KeyFile
protected final Logger log = LoggerFactory.getLogger(getClass());
protected PasswordFinder pwdf;
protected PrivateKeyFileResource resource;
@SuppressWarnings("unchecked")
protected Resource resource;
protected KeyPair kp;
protected KeyType type;
@@ -89,6 +91,19 @@ public class PKCS8KeyFile
this.pwdf = pwdf;
}
@Override
public void init(String privateKey, String publicKey) {
assert privateKey != null;
assert publicKey == null;
resource = new PrivateKeyStringResource(privateKey);
}
@Override
public void init(String privateKey, String publicKey, PasswordFinder pwdf) {
init(privateKey, publicKey);
this.pwdf = pwdf;
}
protected org.bouncycastle.openssl.PasswordFinder makeBouncyPasswordFinder() {
if (pwdf == null)
return null;
@@ -108,10 +123,10 @@ public class PKCS8KeyFile
PEMReader r = null;
Object o = null;
try {
for (; ;) {
for (; ; ) {
// while the PasswordFinder tells us we should retry
try {
r = new PEMReader(new InputStreamReader(new FileInputStream(resource.getDetail())), pFinder);
r = new PEMReader(resource.getReader(), pFinder);
o = r.readObject();
} catch (EncryptionException e) {
if (pwdf.shouldRetry(resource))

View File

@@ -51,6 +51,7 @@ public class AuthKeyboardInteractive
}
private static class CharArrWrap {
private final char[] arr;
private CharArrWrap(char[] arr) {

View File

@@ -48,7 +48,7 @@ public abstract class KeyedAuthMethod
// public key as 2 strings: [ key type | key blob ]
reqBuf.putString(KeyType.fromKey(key).toString())
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
return reqBuf;
}
@@ -67,9 +67,10 @@ public abstract class KeyedAuthMethod
throw new UserAuthException("Could not create signature instance for " + kt + " key");
sigger.init(null, key);
sigger.update(new Buffer.PlainBuffer().putString(params.getTransport().getSessionID()) //
.putBuffer(reqBuf) // & rest of the data for sig
.getCompactData());
sigger.update(new Buffer.PlainBuffer()
.putString(params.getTransport().getSessionID())
.putBuffer(reqBuf) // & rest of the data for sig
.getCompactData());
reqBuf.putSignature(kt, sigger.sign());
return reqBuf;
}

View File

@@ -15,6 +15,10 @@
*/
package net.schmizz.sshj.userauth.password;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class AccountResource
extends Resource<String> {
@@ -22,4 +26,9 @@ public class AccountResource
super(user + "@" + host);
}
@Override
public Reader getReader()
throws IOException {
return new StringReader(getDetail());
}
}

View File

@@ -16,6 +16,10 @@
package net.schmizz.sshj.userauth.password;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
public class PrivateKeyFileResource
extends Resource<File> {
@@ -24,4 +28,9 @@ public class PrivateKeyFileResource
super(privateKeyFile);
}
@Override
public Reader getReader()
throws IOException {
return new InputStreamReader(new FileInputStream(getDetail()));
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.userauth.password;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class PrivateKeyStringResource
extends Resource<String> {
public PrivateKeyStringResource(String string) {
super(string);
}
@Override
public Reader getReader()
throws IOException {
return new StringReader(getDetail());
}
@Override
public String toString() {
// If not overridden, the superclass's will return the private key as
// part of the string.
return "[" + getClass().getSimpleName() + "]";
}
}

View File

@@ -15,6 +15,9 @@
*/
package net.schmizz.sshj.userauth.password;
import java.io.IOException;
import java.io.Reader;
/** A password-protected resource */
public abstract class Resource<H> {
@@ -28,6 +31,9 @@ public abstract class Resource<H> {
return detail;
}
public abstract Reader getReader()
throws IOException;
@Override
public boolean equals(Object o) {
if (this == o)

View File

@@ -26,9 +26,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FileSystemFile
implements LocalSourceFile, LocalDestFile {

View File

@@ -26,7 +26,7 @@ public interface FileTransfer {
throws IOException;
void upload(LocalSourceFile localFile, String remotePath)
throws IOException;
throws IOException;
void download(String remotePath, LocalDestFile localFile)
throws IOException;

View File

@@ -45,7 +45,6 @@ public interface LocalDestFile {
/**
* Set the permissions for the underlying file.
*
* @param f the file
* @param perms permissions e.g. 0644
*
* @throws IOException
@@ -66,7 +65,6 @@ public interface LocalDestFile {
/**
* Set the last modified time for the underlying file.
*
* @param f the file
* @param t time in seconds since Unix epoch
*
* @throws IOException

View File

@@ -34,7 +34,8 @@ public interface LocalSourceFile {
*
* @throws IOException
*/
int getPermissions() throws IOException;
int getPermissions()
throws IOException;
boolean isFile();

View File

@@ -19,6 +19,7 @@ import net.schmizz.sshj.common.SSHException;
public class SCPException
extends SSHException {
public SCPException(String message) {
super(message);
}

View File

@@ -15,8 +15,6 @@
*/
package net.schmizz.sshj.xfer.scp;
import java.io.IOException;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import net.schmizz.sshj.xfer.AbstractFileTransfer;
import net.schmizz.sshj.xfer.FileSystemFile;
@@ -24,6 +22,8 @@ import net.schmizz.sshj.xfer.FileTransfer;
import net.schmizz.sshj.xfer.LocalDestFile;
import net.schmizz.sshj.xfer.LocalSourceFile;
import java.io.IOException;
public class SCPFileTransfer
extends AbstractFileTransfer
implements FileTransfer {
@@ -43,7 +43,7 @@ public class SCPFileTransfer
}
private SCPEngine newSCPEngine() {
return new SCPEngine(sessionFactory, getTransferListener());
return new SCPEngine(sessionFactory, getTransferListener());
}
@Override
@@ -65,9 +65,9 @@ public class SCPFileTransfer
}
@Override
public void upload(LocalSourceFile localFile, String remotePath)
throws IOException {
newSCPUploadClient().copy(localFile, remotePath);
}
public void upload(LocalSourceFile localFile, String remotePath)
throws IOException {
newSCPUploadClient().copy(localFile, remotePath);
}
}

View File

@@ -16,6 +16,7 @@
package net.schmizz.sshj.xfer.scp;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.xfer.LocalFileFilter;
import net.schmizz.sshj.xfer.LocalSourceFile;
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
@@ -28,8 +29,9 @@ import java.util.List;
public final class SCPUploadClient {
private final SCPEngine engine;
private LocalFileFilter uploadFilter;
SCPUploadClient(SCPEngine engine) {
SCPUploadClient(SCPEngine engine) {
this.engine = engine;
}
@@ -45,7 +47,11 @@ public final class SCPUploadClient {
return engine.getExitStatus();
}
private synchronized void startCopy(LocalSourceFile sourceFile, String targetPath)
public void setUploadFilter(LocalFileFilter uploadFilter) {
this.uploadFilter = uploadFilter;
}
private synchronized void startCopy(LocalSourceFile sourceFile, String targetPath)
throws IOException {
List<Arg> args = new LinkedList<Arg>();
args.add(Arg.SINK);
@@ -75,7 +81,7 @@ public final class SCPUploadClient {
throws IOException {
preserveTimeIfPossible(f);
engine.sendMessage("D0" + getPermString(f) + " 0 " + f.getName());
for (LocalSourceFile child : f.getChildren(null))
for (LocalSourceFile child : f.getChildren(uploadFilter))
process(child);
engine.sendMessage("E");
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.util.BasicFixture;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class LoadsOfConnects {
protected final Logger log = LoggerFactory.getLogger(getClass());
private final BasicFixture fixture = new BasicFixture();
@Test
public void loadsOfConnects()
throws IOException, InterruptedException {
for (int i = 0; i < 1000; i++) {
System.out.println("Try " + i);
fixture.init(false);
fixture.done();
}
}
}

View File

@@ -50,7 +50,8 @@ public class SmokeTest {
}
@Test
public void authenticated() throws UserAuthException, TransportException {
public void authenticated()
throws UserAuthException, TransportException {
fixture.dummyAuth();
assertTrue(fixture.getClient().isAuthenticated());
}

View File

@@ -31,6 +31,7 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Scanner;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -113,6 +114,19 @@ public class OpenSSHKeyFileTest {
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
}
@Test
public void fromString()
throws IOException, GeneralSecurityException {
FileKeyProvider dsa = new OpenSSHKeyFile();
String privateKey = readFile("src/test/resources/id_dsa");
String publicKey = readFile("src/test/resources/id_dsa.pub");
dsa.init(privateKey, publicKey,
PasswordUtils.createOneOff(correctPassphrase));
assertEquals(dsa.getType(), KeyType.DSA);
assertEquals(KeyUtil.newDSAPublicKey(y, p, q, g), dsa.getPublic());
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
}
@Before
public void setup()
throws UnsupportedEncodingException, GeneralSecurityException {
@@ -120,4 +134,18 @@ public class OpenSSHKeyFileTest {
throw new AssertionError("bouncy castle needed");
}
private String readFile(String pathname)
throws IOException {
StringBuilder fileContents = new StringBuilder();
Scanner scanner = new Scanner(new File(pathname));
String lineSeparator = System.getProperty("line.separator");
try {
while (scanner.hasNextLine()) {
fileContents.append(scanner.nextLine() + lineSeparator);
}
return fileContents.toString();
} finally {
scanner.close();
}
}
}

View File

@@ -0,0 +1,23 @@
package net.schmizz.sshj.sftp;
import org.junit.Test;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class FileModeTest {
@Test
public void shouldDetectDirectoryWithLinuxMask() {
FileMode fileMode = new FileMode(040755);
assertThat(fileMode.toString(), equalTo("[mask=40755]"));
assertThat(fileMode.getType(), equalTo(FileMode.Type.DIRECTORY));
}
@Test
public void shouldDetectDirectoryWithAixUnixMask() {
FileMode fileMode = new FileMode(0240755);
assertThat(fileMode.toString(), equalTo("[mask=240755]"));
assertThat(fileMode.getType(), equalTo(FileMode.Type.DIRECTORY));
}
}

View File

@@ -17,15 +17,23 @@ package net.schmizz.sshj.transport.verification;
import net.schmizz.sshj.util.KeyUtil;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.internal.matchers.IsCollectionContaining.hasItem;
public class OpenSSHKnownHostsTest {
@@ -33,23 +41,53 @@ public class OpenSSHKnownHostsTest {
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
// }
private OpenSSHKnownHosts kh;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Before
public void setUp()
throws IOException, GeneralSecurityException {
kh = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts"));
// kh = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts"));
}
@Test
public void testLocalhostEntry()
throws UnknownHostException, GeneralSecurityException {
public File writeKnownHosts(String line) throws IOException {
File known_hosts = temp.newFile("known_hosts");
FileWriter fileWriter = new FileWriter(known_hosts);
BufferedWriter writer = new BufferedWriter(fileWriter);
writer.write(line);
writer.write("\r\n");
writer.flush();
writer.close();
return known_hosts;
}
}
@Test
public void shouldAddCommentForEmptyLine() throws IOException {
File file = writeKnownHosts("");
OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(file);
assertThat(openSSHKnownHosts.entries().size(), equalTo(1));
assertThat(openSSHKnownHosts.entries().get(0), instanceOf(OpenSSHKnownHosts.CommentEntry.class));
}
@Test
public void shouldAddCommentForCommentLine() throws IOException {
File file = writeKnownHosts("# this is a comment");
OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(file);
assertThat(openSSHKnownHosts.entries().size(), equalTo(1));
assertThat(openSSHKnownHosts.entries().get(0), instanceOf(OpenSSHKnownHosts.CommentEntry.class));
}
//
// @Test
// public void testLocalhostEntry()
// throws UnknownHostException, GeneralSecurityException {
//
// }
//
@Test
public void testSchmizzEntry()
throws UnknownHostException, GeneralSecurityException {
throws IOException, GeneralSecurityException {
OpenSSHKnownHosts kh = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts"));
final PublicKey key = KeyUtil
.newRSAPublicKey(
"e8ff4797075a861db9d2319960a836b2746ada3da514955d2921f2c6a6c9895cbd557f604e43772b6303e3cab2ad82d83b21acdef4edb72524f9c2bef893335115acacfe2989bcbb2e978e4fedc8abc090363e205d975c1fdc35e55ba4daa4b5d5ab7a22c40f547a4a0fd1c683dfff10551c708ff8c34ea4e175cb9bf2313865308fa23601e5a610e2f76838be7ded3b4d3a2c49d2d40fa20db51d1cc8ab20d330bb0dadb88b1a12853f0ecb7c7632947b098dcf435a54566bcf92befd55e03ee2a57d17524cd3d59d6e800c66059067e5eb6edb81946b3286950748240ec9afa4389f9b62bc92f94ec0fba9e64d6dc2f455f816016a4c5f3d507382ed5d3365",

View File

@@ -28,6 +28,7 @@ import static org.junit.Assert.assertEquals;
/** Tests {@link Buffer} functionality */
public class BufferTest {
private Buffer.PlainBuffer posBuf;
private Buffer.PlainBuffer handyBuf;

View File

@@ -29,34 +29,38 @@ import java.security.spec.RSAPublicKeySpec;
public class KeyUtil {
/** Creates a DSA private key. */
public static PrivateKey newDSAPrivateKey(String x, String p, String q, String g) throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("DSA").generatePrivate(new DSAPrivateKeySpec //
(new BigInteger(x, 16), //
new BigInteger(p, 16), //
new BigInteger(q, 16), //
new BigInteger(g, 16)));
public static PrivateKey newDSAPrivateKey(String x, String p, String q, String g)
throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("DSA").generatePrivate(new DSAPrivateKeySpec(new BigInteger(x, 16),
new BigInteger(p, 16),
new BigInteger(q, 16),
new BigInteger(g, 16))
);
}
/** Creates a DSA public key. */
public static PublicKey newDSAPublicKey(String y, String p, String q, String g) throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("DSA").generatePublic(new DSAPublicKeySpec //
(new BigInteger(y, 16), //
new BigInteger(p, 16), //
new BigInteger(q, 16), //
new BigInteger(g, 16)));
public static PublicKey newDSAPublicKey(String y, String p, String q, String g)
throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("DSA").generatePublic(new DSAPublicKeySpec(new BigInteger(y, 16),
new BigInteger(p, 16),
new BigInteger(q, 16),
new BigInteger(g, 16))
);
}
/** Creates an RSA private key. */
public static PrivateKey newRSAPrivateKey(String modulus, String exponent) throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("RSA").generatePrivate(new RSAPrivateKeySpec //
(new BigInteger(modulus, 16), //
new BigInteger(exponent, 16)));
public static PrivateKey newRSAPrivateKey(String modulus, String exponent)
throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("RSA").generatePrivate(new RSAPrivateKeySpec(new BigInteger(modulus, 16),
new BigInteger(exponent, 16))
);
}
/** Creates an RSA public key. */
public static PublicKey newRSAPublicKey(String modulus, String exponent) throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("RSA").generatePublic(new RSAPublicKeySpec //
(new BigInteger(modulus, 16), new BigInteger(exponent, 16)));
public static PublicKey newRSAPublicKey(String modulus, String exponent)
throws GeneralSecurityException {
return SecurityUtils.getKeyFactory("RSA").generatePublic(new RSAPublicKeySpec(new BigInteger(modulus, 16),
new BigInteger(exponent, 16)));
}
}

View File

@@ -0,0 +1,54 @@
package net.schmizz.sshj.xfer.scp;
import net.schmizz.sshj.xfer.FileSystemFile;
import net.schmizz.sshj.xfer.LocalFileFilter;
import net.schmizz.sshj.xfer.LocalSourceFile;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.verification.VerificationMode;
import java.io.File;
import java.io.IOException;
import static org.mockito.Matchers.endsWith;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class SCPUploadClientTest {
private SCPEngine engine;
private SCPUploadClient scpUploadClient;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Before
public void init() {
engine = mock(SCPEngine.class);
scpUploadClient = new SCPUploadClient(engine);
}
@Test
public void shouldOnlySendFilterAcceptedFilesFromDirectory() throws IOException {
scpUploadClient.setUploadFilter(new LocalFileFilter() {
@Override
public boolean accept(LocalSourceFile file) {
return !file.getName().contains("not-");
}
});
File dir = temp.newFolder("filtered-scp-upload");
new File(dir, "not-sent.txt").createNewFile();
new File(dir, "sent.txt").createNewFile();
int copy = scpUploadClient.copy(new FileSystemFile(dir), "/tmp");
verify(engine).startedDir("filtered-scp-upload");
verify(engine).startedFile(eq("sent.txt"), isA(Long.class));
verify(engine, times(1)).startedFile(isA(String.class), isA(Long.class));
}
}

View File

@@ -1,3 +1,4 @@
schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ==
Above we have a plain line, Below we have a hashed line, This is a garbage line.
# Above we have a plain line, Below we have a hashed line, Last is a v1 line, This is a garbage line.
|1|dy7xSefq6NmJms6AzANG3w45W28=|SSCTlHs4pZbc2uaRoPvjyEAHE1g= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAu64GJcCkdtckPGt8uKTyhG1ShT1Np1kh10eE49imQ4Nh9Y/IrSPzDtYUAazQ88ABc2NffuOKkdn2qtUwZ1ulfcdNfN3oTim3BiVHqa041pKG0L+onQe8Bo+CaG5KBLy/C24eNGM9EcfQvDQOnq1eD3lnR/l8fFckldzjfxZgar0yT9Bb3pwp50oN+1wSEINJEHOgMIW8kZBQmyNr/B+b7yX+Y1s1vuYIP/i4WimCVmkdi9G87Ga8w7GxKalRD2QOG6Xms2YWRQDN6M/MOn4tda3EKolbWkctEWcQf/PcVJffTH4Wv5f0RjVyrQv4ha4FZcNAv6RkRd9WkiCsiTKioQ==
test.com,1.1.1.1 2048 35 22017496617994656680820635966392838863613340434802393112245951008866692373218840197754553998457793202561151141246686162285550121243768846314646395880632789308110750881198697743542374668273149584280424505890648953477691795864456749782348425425954366277600319096366690719901119774784695056100331902394094537054256611668966698242432417382422091372756244612839068092471592121759862971414741954991375710930168229171638843329213652899594987626853020377726482288618521941129157643483558764875338089684351824791983007780922947554898825663693324944982594850256042689880090306493029526546183035567296830604572253312294059766327