Compare commits

...

16 Commits

Author SHA1 Message Date
Jeroen van Erp
7d326e5ae4 Updated example build 2016-12-30 09:26:43 +01:00
Jeroen van Erp
f038b5ce2b Updated Release notes 2016-12-30 09:26:00 +01:00
Jeroen van Erp
20879a4aa5 LocalPortForwarder interrupts its thread on close() 2016-12-29 16:06:57 +01:00
Jeroen van Erp
516abb0282 Disable version inference when no git history present (Fixes #256) 2016-12-28 10:09:48 +01:00
Jeroen van Erp
0ad51709c2 Use the configured Random factory in DH KEX (Fixes #292) 2016-12-28 10:00:24 +01:00
Jeroen van Erp
c9c68f019e StreamCipher can use the constructor without random. 2016-12-28 09:52:58 +01:00
Jeroen van Erp
fc75f9796c Added logging to trace time creation of Randoms 2016-12-28 09:52:20 +01:00
Jeroen van Erp
61af500c3e Added 'classes' directory to ignore 2016-12-28 09:51:03 +01:00
Jeroen van Erp
56553ea086 Prepared release notes 2016-12-23 11:58:49 +01:00
Jeroen van Erp
86e6631b1e Enabled PKCS5 in DefaultConfig 2016-12-23 11:54:43 +01:00
Jeroen van Erp
b6f437a932 Merge pull request #291 from joval/master
Two minor changes
2016-12-21 21:51:23 +01:00
David Solin
9e3b9f7c24 Obtain the sshj.properties resource from the ClassLoader that loaded the DefaultConfig class, not from the context classloader. 2016-12-21 10:04:56 -06:00
David Solin
766ab916ee Use cause message when initializing an SSHRuntimeException from a Throwable. 2016-12-21 07:48:02 -06:00
Jeroen van Erp
cdca43a848 Merge pull request #284 from gatesking/patch-1
Catch interrupt signal in keepalive thread
2016-11-30 09:42:29 +01:00
gatesking
3ce7c2ebfb Catch interrupt signal in keepalive thread
Interrupt signal may be catched when keepalive thread is sleeping. As a result, it will still go into conn.getTransport().die(e).
2016-11-30 09:30:57 +08:00
Jeroen van Erp
ca4e0bf2d7 Update README.adoc 2016-11-25 13:27:20 +01:00
25 changed files with 223 additions and 33 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@
# Output dirs
target/
classes/
build/
docs/
.gradle/

View File

@@ -79,7 +79,7 @@ compression::
`zlib` and `zlib@openssh.com` (delayed zlib)
private key files::
`pkcs8` encoded (what openssh uses)
`pkcs5`, `pkcs8`, `openssh-key-v1`
If you need something that is not included, it shouldn't be too hard to add (do contribute it!)
@@ -99,6 +99,13 @@ Google Group: http://groups.google.com/group/sshj-users
Fork away!
== Release history
SSHJ 0.19.1 (2016-12-30)::
* Enabled PKCS5 Key files in DefaultConfig
* Merged https://github.com/hierynomus/sshj/pulls/291[#291]: Fixed sshj.properties loading and chained exception messages
* Merged https://github.com/hierynomus/sshj/pulls/284[#284]: Correctly catch interrupt in keepalive thread
* Fixed https://github.com/hierynomus/sshj/issues/292[#292]: Pass the configured RandomFactory to Diffie Hellmann KEX
* Fixed https://github.com/hierynomus/sshj/issues/256[#256]: SSHJ now builds if no git repository present
* LocalPortForwarder now correctly interrupts its own thread on close()
SSHJ 0.19.0 (2016-11-25)::
* Fixed https://github.com/hierynomus/sshj/issues/276[#276]: Add support for ed-25519 and new OpenSSH key format
* Fixed https://github.com/hierynomus/sshj/issues/280[#280]: Read version from a generated sshj.properties file to correctly output version during negotiation

View File

@@ -53,8 +53,12 @@ license {
excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java'])
}
release {
grgit = org.ajoberstar.grgit.Grgit.open(project.projectDir)
if (project.file('.git').isDirectory()) {
release {
grgit = org.ajoberstar.grgit.Grgit.open(project.projectDir)
}
} else {
version = "0.0.0-no.git"
}
// This disables the pedantic doclint feature of JDK8

View File

@@ -24,7 +24,7 @@
<groupId>com.hierynomus</groupId>
<artifactId>sshj-examples</artifactId>
<packaging>jar</packaging>
<version>0.19.0</version>
<version>0.19.1</version>
<name>sshj-examples</name>
<description>Examples for SSHv2 library for Java</description>
@@ -55,7 +55,7 @@
<dependency>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
<version>0.18.0</version>
<version>0.19.0</version>
</dependency>
</dependencies>

View File

@@ -29,6 +29,6 @@ public class StreamCipher extends BaseCipher {
@Override
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
cipher.init(getMode(mode), getKeySpec(key), new SecureRandom());
cipher.init(getMode(mode), getKeySpec(key));
}
}

View File

@@ -65,6 +65,8 @@ public abstract class KeepAlive extends Thread {
}
Thread.sleep(hi * 1000);
}
} catch (InterruptedException e) {
// Interrupt signal may be catched when sleeping.
} catch (Exception e) {
// If we weren't interrupted, kill the transport, then this exception was unexpected.
// Else we're in shutdown-mode already, so don't forcibly kill the transport.

View File

@@ -34,6 +34,8 @@ import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import org.slf4j.Logger;
@@ -85,7 +87,7 @@ public class DefaultConfig
private String readVersionFromProperties() {
try {
Properties properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("sshj.properties"));
properties.load(DefaultConfig.class.getClassLoader().getResourceAsStream("sshj.properties"));
String property = properties.getProperty("sshj.version");
return "SSHJ_" + property.replace('-', '_'); // '-' is a disallowed character, see RFC-4253#section-4.2
} catch (IOException e) {
@@ -122,7 +124,12 @@ public class DefaultConfig
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
if (bouncyCastleRegistered) {
setFileKeyProviderFactories(new OpenSSHKeyV1KeyFile.Factory(), new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory());
setFileKeyProviderFactories(
new OpenSSHKeyV1KeyFile.Factory(),
new PKCS8KeyFile.Factory(),
new PKCS5KeyFile.Factory(),
new OpenSSHKeyFile.Factory(),
new PuTTYKeyFile.Factory());
}
}

View File

@@ -34,7 +34,7 @@ public class SSHRuntimeException
}
public SSHRuntimeException(Throwable cause) {
this(null, cause);
this(cause.getMessage(), cause);
}
}

View File

@@ -107,6 +107,7 @@ public class LocalPortForwarder {
private final Connection conn;
private final Parameters parameters;
private final ServerSocket serverSocket;
private Thread runningThread;
public LocalPortForwarder(Connection conn, Parameters parameters, ServerSocket serverSocket, LoggerFactory loggerFactory) {
this.conn = conn;
@@ -132,10 +133,20 @@ public class LocalPortForwarder {
*
* @throws IOException
*/
public void listen()
throws IOException {
public void listen() throws IOException {
listen(Thread.currentThread());
}
/**
* Start listening for incoming connections and forward to remote host as a channel and ensure that the thread is registered.
* This is useful if for instance {@link #close() is called from another thread}
*
* @throws IOException
*/
public void listen(Thread runningThread) throws IOException {
this.runningThread = runningThread;
log.info("Listening on {}", serverSocket.getLocalSocketAddress());
while (!Thread.currentThread().isInterrupted()) {
while (!runningThread.isInterrupted()) {
try {
final Socket socket = serverSocket.accept();
log.debug("Got connection from {}", socket.getRemoteSocketAddress());
@@ -162,6 +173,7 @@ public class LocalPortForwarder {
if (!serverSocket.isClosed()) {
log.info("Closing listener on {}", serverSocket.getLocalSocketAddress());
serverSocket.close();
runningThread.interrupt();
}
}

View File

@@ -103,7 +103,7 @@ public abstract class AbstractDHGex extends AbstractDH {
throw new GeneralSecurityException("Server generated gex p is out of range (" + bitLength + " bits)");
}
log.debug("Received server p bitlength {}", bitLength);
dh.init(new DHParameterSpec(p, g));
dh.init(new DHParameterSpec(p, g), trans.getConfig().getRandomFactory());
log.debug("Sending {}", Message.KEX_DH_GEX_INIT);
trans.write(new SSHPacket(Message.KEX_DH_GEX_INIT).putBytes(dh.getE()));
return false;

View File

@@ -15,19 +15,19 @@
*/
package net.schmizz.sshj.transport.kex;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.jce.spec.ECParameterSpec;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.transport.random.Random;
public class Curve25519DH extends DHBase {
private byte[] secretKey;
public Curve25519DH() {
@@ -42,10 +42,10 @@ public class Curve25519DH extends DHBase {
}
@Override
public void init(AlgorithmParameterSpec params) throws GeneralSecurityException {
SecureRandom secureRandom = new SecureRandom();
public void init(AlgorithmParameterSpec params, Factory<Random> randomFactory) throws GeneralSecurityException {
Random random = randomFactory.create();
byte[] secretBytes = new byte[32];
secureRandom.nextBytes(secretBytes);
random.fill(secretBytes);
byte[] publicBytes = new byte[32];
djb.Curve25519.keygen(publicBytes, null, secretBytes);
this.secretKey = Arrays.copyOf(secretBytes, secretBytes.length);

View File

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

View File

@@ -15,8 +15,10 @@
*/
package net.schmizz.sshj.transport.kex;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.Random;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
@@ -38,7 +40,7 @@ public class DH extends DHBase {
}
@Override
protected void init(AlgorithmParameterSpec params) throws GeneralSecurityException {
protected void init(AlgorithmParameterSpec params, Factory<Random> randomFactory) throws GeneralSecurityException {
if (!(params instanceof DHParameterSpec)) {
throw new SSHRuntimeException("Wrong algorithm parameters for Diffie Hellman");
}

View File

@@ -15,8 +15,10 @@
*/
package net.schmizz.sshj.transport.kex;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.Random;
import javax.crypto.KeyAgreement;
import java.math.BigInteger;
@@ -42,7 +44,7 @@ abstract class DHBase {
abstract void computeK(byte[] f) throws GeneralSecurityException;
protected abstract void init(AlgorithmParameterSpec params) throws GeneralSecurityException;
protected abstract void init(AlgorithmParameterSpec params, Factory<Random> randomFactory) throws GeneralSecurityException;
void setE(byte[] e) {
this.e = e;

View File

@@ -51,6 +51,6 @@ public class DHG1
@Override
protected void initDH(DHBase dh) throws GeneralSecurityException {
dh.init(new DHParameterSpec(DHGroupData.P1, DHGroupData.G));
dh.init(new DHParameterSpec(DHGroupData.P1, DHGroupData.G), trans.getConfig().getRandomFactory());
}
}

View File

@@ -51,6 +51,6 @@ public class DHG14
@Override
protected void initDH(DHBase dh) throws GeneralSecurityException {
dh.init(new DHParameterSpec(DHGroupData.P14, DHGroupData.G));
dh.init(new DHParameterSpec(DHGroupData.P14, DHGroupData.G), trans.getConfig().getRandomFactory());
}
}

View File

@@ -15,7 +15,9 @@
*/
package net.schmizz.sshj.transport.kex;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.Random;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
@@ -39,7 +41,7 @@ public class ECDH extends DHBase {
super("EC", "ECDH");
}
protected void init(AlgorithmParameterSpec params) throws GeneralSecurityException {
protected void init(AlgorithmParameterSpec params, Factory<Random> randomFactory) throws GeneralSecurityException {
generator.initialize(params);
KeyPair keyPair = generator.generateKeyPair();
agreement.init(keyPair.getPrivate());

View File

@@ -79,7 +79,7 @@ public class ECDHNistP extends AbstractDHG {
@Override
protected void initDH(DHBase dh) throws GeneralSecurityException {
dh.init(new ECNamedCurveGenParameterSpec(curve));
dh.init(new ECNamedCurveGenParameterSpec(curve), trans.getConfig().getRandomFactory());
}
}

View File

@@ -17,6 +17,8 @@ package net.schmizz.sshj.transport.random;
import org.bouncycastle.crypto.prng.RandomGenerator;
import org.bouncycastle.crypto.prng.VMPCRandomGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.SecureRandom;
@@ -27,6 +29,8 @@ import java.security.SecureRandom;
public class BouncyCastleRandom
implements Random {
private static final Logger logger = LoggerFactory.getLogger(BouncyCastleRandom.class);
/** Named factory for the BouncyCastle <code>Random</code> */
public static class Factory
implements net.schmizz.sshj.common.Factory<Random> {
@@ -41,7 +45,10 @@ public class BouncyCastleRandom
private final RandomGenerator random = new VMPCRandomGenerator();
public BouncyCastleRandom() {
logger.info("Generating random seed from SecureRandom.");
long t = System.currentTimeMillis();
byte[] seed = new SecureRandom().generateSeed(8);
logger.debug("Creating random seed took {} ms", System.currentTimeMillis() - t);
random.addSeedMaterial(seed);
}
@@ -50,4 +57,9 @@ public class BouncyCastleRandom
random.nextBytes(bytes, start, len);
}
@Override
public void fill(byte[] bytes) {
random.nextBytes(bytes);
}
}

View File

@@ -16,10 +16,13 @@
package net.schmizz.sshj.transport.random;
import java.security.SecureRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** A {@link Random} implementation using the built-in {@link SecureRandom} PRNG. */
public class JCERandom
implements Random {
private static final Logger logger = LoggerFactory.getLogger(JCERandom.class);
/** Named factory for the JCE {@link Random} */
public static class Factory
@@ -38,7 +41,14 @@ public class JCERandom
}
private byte[] tmp = new byte[16];
private final SecureRandom random = new SecureRandom();
private final SecureRandom random;
JCERandom() {
logger.info("Creating new SecureRandom.");
long t = System.currentTimeMillis();
random = new SecureRandom();
logger.debug("Random creation took {} ms", System.currentTimeMillis() - t);
}
/**
* Fill the given byte-array with random bytes from this PRNG.
@@ -49,15 +59,20 @@ public class JCERandom
*/
@Override
public synchronized void fill(byte[] foo, int start, int len) {
if (start == 0 && len == foo.length)
if (start == 0 && len == foo.length) {
random.nextBytes(foo);
else
} else {
synchronized (this) {
if (len > tmp.length)
tmp = new byte[len];
random.nextBytes(tmp);
System.arraycopy(tmp, 0, foo, start, len);
}
}
}
@Override
public void fill(final byte[] bytes) {
random.nextBytes(bytes);
}
}

View File

@@ -18,6 +18,13 @@ package net.schmizz.sshj.transport.random;
/** A pseudo random number generator. */
public interface Random {
/**
* Fill the array of bytes with random values.
*
* @param bytes byte array to be filled.
*/
void fill(byte[] bytes);
/**
* Fill part of bytes with random values.
*

View File

@@ -37,4 +37,8 @@ public class SingletonRandomFactory
random.fill(bytes, start, len);
}
@Override
public void fill(final byte[] bytes) {
random.fill(bytes);
}
}

View File

@@ -96,7 +96,7 @@ public class KeyProviderUtil {
return KeyFormat.OpenSSH;
} else if (header.contains("BEGIN PRIVATE KEY") || header.contains("BEGIN ENCRYPTED PRIVATE KEY")) {
return KeyFormat.PKCS8;
} else {
} else {
return KeyFormat.PKCS5;
}
} else if (header.startsWith("PuTTY-User-Key-File-")) {

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.connection.channel.direct
import com.hierynomus.sshj.test.SshFixture
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder
import org.junit.Rule
import spock.lang.Specification
class LocalPortForwarderSpec extends Specification {
@Rule
SshFixture tunnelFixture = new SshFixture()
@Rule
SshFixture realServer = new SshFixture()
def "should not hang when disconnect tunnel"() {
given:
def client = tunnelFixture.setupConnectedDefaultClient()
client.authPassword("test", "test")
def socket = new ServerSocket(0)
def lpf = client.newLocalPortForwarder(new LocalPortForwarder.Parameters("localhost", socket.getLocalPort(), "localhost", realServer.server.port), socket)
def thread = new Thread(new Runnable() {
@Override
void run() {
lpf.listen()
}
})
when:
thread.start()
then:
thread.isAlive()
when:
lpf.close()
then:
socket.isClosed()
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.userauth.keyprovider
import com.hierynomus.sshj.test.SshFixture
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.userauth.keyprovider.KeyFormat
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator
import org.junit.Rule
import spock.lang.Specification
import spock.lang.Unroll
class FileKeyProviderSpec extends Specification {
@Rule
SshFixture fixture = new SshFixture(false)
def setup() {
fixture.getServer().setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE)
fixture.start()
}
def cleanup() {
fixture.stopServer()
}
@Unroll
def "should have #format FileKeyProvider enabled by default"() {
given:
SSHClient client = fixture.setupConnectedDefaultClient()
when:
client.authPublickey("jeroen", keyfile)
then:
client.isAuthenticated()
cleanup:
client.disconnect()
where:
format | keyfile
KeyFormat.PKCS5 | "src/test/resources/keyformats/pkcs5"
KeyFormat.OpenSSH | "src/test/resources/keyformats/openssh"
}
}