Compare commits

..

26 Commits

Author SHA1 Message Date
Jeroen van Erp
eb1629f250 Updated README release notes 2017-08-24 09:11:58 +02:00
Jeroen van Erp
8856aaea61 Fixed codacy 2017-08-22 19:32:45 +02:00
Jeroen van Erp
1f6615b57a Check whether filename is a child of the current file (Fixes #341) 2017-08-22 19:32:45 +02:00
Matt Dailey
e5084ed8db Removed Builder, and fixed call to checkFormatString 2017-07-10 09:30:10 +02:00
Matt Dailey
3729119e23 Added assertions to testPromptFormat 2017-07-10 09:30:10 +02:00
Matt Dailey
aed3decf1d Upgraded Mockito, and added message and retries to ConsolePasswordFinder
* Upgraded Mockito to 2.8.47 (latest)
* Added extension to allow mocking final classes
* ConsolePasswordFinder allows custom message and number of retries
* Added builder for ConsolePasswordFinder
* Added more unit tests
2017-07-10 09:30:10 +02:00
Matt Dailey
303c03061c Add ConsolePasswordFinder to read from Console
* There was no example `PasswordFinder` to prompt
a user for their password
2017-07-10 09:30:10 +02:00
Iger
5e3a08a637 - boggle 2017-07-04 10:02:00 +02:00
Iger
d0800058e8 - Test ECDSA signature verifications 2017-07-04 10:02:00 +02:00
Iger
ad9c2d5411 - Test ECDSA fingerprints 2017-07-04 10:02:00 +02:00
Iger
ed65176b68 - Incorrect key format during write 2017-07-04 10:02:00 +02:00
Iger
28f3280a84 - license header 2017-07-04 10:02:00 +02:00
Iger
d69f722908 - Some more indentation fixes 2017-07-04 10:02:00 +02:00
Iger
1d7cb8c2c6 - Some more indentation fixes 2017-07-04 10:02:00 +02:00
Iger
6ad6242ed1 - Ident in spaces 2017-07-04 10:02:00 +02:00
Iger
3310530d42 - cleanup 2017-07-04 10:02:00 +02:00
Iger
3685f9dc36 - Formal generation of ASN.1 encoding for the ecdsa signature
- Support ecdsa-sha2-nistp521
2017-07-04 10:02:00 +02:00
Iger
f8cad120a6 - Pretty honed up implementation of -384 2017-07-04 10:02:00 +02:00
Iger
56dd4e4af4 - A separate enum members take with lots of code duplication 2017-07-04 10:02:00 +02:00
Jeroen van Erp
9f8cf1f298 Upgrade to gradle 4.0 2017-06-26 10:01:19 +02:00
Jeroen van Erp
a51270791d Remove deprecated ZLib usage 2017-06-26 09:56:37 +02:00
Jeroen van Erp
d43fc4551e Minor reformatting 2017-06-26 09:51:40 +02:00
Jan Peter Stotz
93bf6c0089 Fixed small exception logging problem 2017-06-09 11:58:24 +02:00
Jeroen van Erp
7b535a8db3 Added support for wildcard host entries in known_hosts (Fixes #331) 2017-05-22 14:43:30 +02:00
Jeroen van Erp
9d4f8fc46a Updated release notes 2017-04-25 15:32:44 +02:00
David Kocher
2b21ec6032 Fix regression from 40f956b. Invalid length parameter. (#322)
Also Fixes #306
2017-04-25 15:30:20 +02:00
25 changed files with 922 additions and 294 deletions

View File

@@ -1,7 +1,7 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.21.0
:sshj_version: 0.21.1
:source-highlighter: pygments
image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"]
@@ -75,7 +75,7 @@ key exchange::
`diffie-hellman-group16-sha256`, `diffie-hellman-group16-sha384@ssh.com`, `diffie-hellman-group16-sha512@ssh.com`, `diffie-hellman-group18-sha512@ssh.com`
signatures::
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ssh-ed25519`
`ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519`
mac::
`hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512`
@@ -104,6 +104,13 @@ Google Group: http://groups.google.com/group/sshj-users
Fork away!
== Release history
SSHJ 0.22.0 (2017-08-24)::
* Fixed https://github.com/hierynomus/sshj/pulls/341[#341]: Fixed path walking during recursive copy
* Merged https://github.com/hierynomus/sshj/pulls/338[#338]: Added ConsolePasswordFinder to read password from stdin
* Merged https://github.com/hierynomus/sshj/pulls/336[#336]: Added support for ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 signatures
* Fixed https://github.com/hierynomus/sshj/issues/331[#331]: Added support for wildcards in known_hosts file
SSHJ 0.21.1 (2017-04-25)::
* Merged https://github.com/hierynomus/sshj/pulls/322[#322]: Fix regression from 40f956b (invalid length parameter on outputstream)
SSHJ 0.21.0 (2017-04-14)::
* Merged https://github.com/hierynomus/sshj/pulls/319[#319]: Added support for `ssh-rsa-cert-v01@openssh.com` and `ssh-dsa-cert-v01@openssh.com` certificate key files
* Upgraded Gradle to 3.4.1

View File

@@ -36,7 +36,7 @@ dependencies {
testCompile "junit:junit:4.11"
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
testCompile "org.mockito:mockito-core:1.9.5"
testCompile "org.mockito:mockito-core:2.8.47"
testCompile "org.apache.sshd:sshd-core:1.2.0"
testRuntime "ch.qos.logback:logback-classic:1.1.2"
testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17'

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.verification;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.MAC;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class KnownHostMatchers {
public static HostMatcher createMatcher(String hostEntry) throws SSHException {
if (hostEntry.contains(",")) {
return new AnyHostMatcher(hostEntry);
}
if (hostEntry.startsWith("!")) {
return new NegateHostMatcher(hostEntry);
}
if (hostEntry.startsWith("|1|")) {
return new HashedHostMatcher(hostEntry);
}
if (hostEntry.contains("*") || hostEntry.contains("?")) {
return new WildcardHostMatcher(hostEntry);
}
return new EquiHostMatcher(hostEntry);
}
public interface HostMatcher {
boolean match(String hostname) throws IOException;
}
private static class EquiHostMatcher implements HostMatcher {
private String host;
public EquiHostMatcher(String host) {
this.host = host;
}
@Override
public boolean match(String hostname) {
return host.equals(hostname);
}
}
private static class HashedHostMatcher implements HostMatcher {
private final MAC sha1 = new HMACSHA1();
private final String hash;
private final String salt;
private byte[] saltyBytes;
HashedHostMatcher(String hash) throws SSHException {
this.hash = hash;
final String[] hostParts = hash.split("\\|");
if (hostParts.length != 4) {
throw new SSHException("Unrecognized format for hashed hostname");
}
salt = hostParts[2];
}
@Override
public boolean match(String hostname) throws IOException {
return hash.equals(hashHost(hostname));
}
private String hashHost(String host) throws IOException {
sha1.init(getSaltyBytes());
return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
}
private byte[] getSaltyBytes() throws IOException {
if (saltyBytes == null) {
saltyBytes = Base64.decode(salt);
}
return saltyBytes;
}
}
private static class AnyHostMatcher implements HostMatcher {
private final List<HostMatcher> matchers;
AnyHostMatcher(String hostEntry) throws SSHException {
matchers = new ArrayList<HostMatcher>();
for (String subEntry : hostEntry.split(",")) {
matchers.add(KnownHostMatchers.createMatcher(subEntry));
}
}
@Override
public boolean match(String hostname) throws IOException {
for (HostMatcher matcher : matchers) {
if (matcher.match(hostname)) {
return true;
}
}
return false;
}
}
private static class NegateHostMatcher implements HostMatcher {
private final HostMatcher matcher;
NegateHostMatcher(String hostEntry) throws SSHException {
this.matcher = createMatcher(hostEntry.substring(1));
}
@Override
public boolean match(String hostname) throws IOException {
return !matcher.match(hostname);
}
}
private static class WildcardHostMatcher implements HostMatcher {
private final Pattern pattern;
public WildcardHostMatcher(String hostEntry) {
this.pattern = Pattern.compile(hostEntry.replace(".", "\\.").replace("*", ".*").replace("?", "."));
}
@Override
public boolean match(String hostname) throws IOException {
return pattern.matcher(hostname).matches();
}
@Override
public String toString() {
return "WildcardHostMatcher[" + pattern + ']';
}
}
}

View File

@@ -15,12 +15,22 @@
*/
package net.schmizz.sshj;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import org.slf4j.Logger;
import com.hierynomus.sshj.signature.SignatureEdDSA;
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
import com.hierynomus.sshj.transport.cipher.StreamCiphers;
import com.hierynomus.sshj.transport.kex.DHGroups;
import com.hierynomus.sshj.transport.kex.ExtendedDHGroups;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.LoggerFactory;
@@ -28,13 +38,26 @@ import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.signature.SignatureDSA;
import net.schmizz.sshj.signature.SignatureECDSA;
import net.schmizz.sshj.signature.SignatureRSA;
import net.schmizz.sshj.transport.cipher.*;
import net.schmizz.sshj.transport.cipher.AES128CBC;
import net.schmizz.sshj.transport.cipher.AES128CTR;
import net.schmizz.sshj.transport.cipher.AES192CBC;
import net.schmizz.sshj.transport.cipher.AES192CTR;
import net.schmizz.sshj.transport.cipher.AES256CBC;
import net.schmizz.sshj.transport.cipher.AES256CTR;
import net.schmizz.sshj.transport.cipher.BlowfishCBC;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.cipher.TripleDESCBC;
import net.schmizz.sshj.transport.compression.NoneCompression;
import net.schmizz.sshj.transport.kex.Curve25519SHA256;
import net.schmizz.sshj.transport.kex.DHGexSHA1;
import net.schmizz.sshj.transport.kex.DHGexSHA256;
import net.schmizz.sshj.transport.kex.ECDHNistP;
import net.schmizz.sshj.transport.mac.*;
import net.schmizz.sshj.transport.mac.HMACMD5;
import net.schmizz.sshj.transport.mac.HMACMD596;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.HMACSHA196;
import net.schmizz.sshj.transport.mac.HMACSHA2256;
import net.schmizz.sshj.transport.mac.HMACSHA2512;
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
@@ -42,10 +65,6 @@ import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import org.slf4j.Logger;
import java.io.IOException;
import java.util.*;
/**
* A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
@@ -210,7 +229,9 @@ public class DefaultConfig
protected void initSignatureFactories() {
setSignatureFactories(
new SignatureECDSA.Factory(),
new SignatureECDSA.Factory256(),
new SignatureECDSA.Factory384(),
new SignatureECDSA.Factory521(),
new SignatureRSA.Factory(),
new SignatureDSA.Factory(),
new SignatureEdDSA.Factory()

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hierynomus.sshj.secg.SecgUtils;
public class ECDSAVariationsAdapter {
private final static String BASE_ALGORITHM_NAME = "ecdsa-sha2-nistp";
private final static Logger log = LoggerFactory.getLogger(ECDSAVariationsAdapter.class);
public final static Map<String, String> SUPPORTED_CURVES = new HashMap<String, String>();
public final static Map<String, String> NIST_CURVES_NAMES = new HashMap<String, String>();
static {
NIST_CURVES_NAMES.put("256", "p-256");
NIST_CURVES_NAMES.put("384", "p-384");
NIST_CURVES_NAMES.put("521", "p-521");
SUPPORTED_CURVES.put("256", "nistp256");
SUPPORTED_CURVES.put("384", "nistp384");
SUPPORTED_CURVES.put("521", "nistp521");
}
public static PublicKey readPubKeyFromBuffer(Buffer<?> buf, String variation) throws GeneralSecurityException {
String algorithm = BASE_ALGORITHM_NAME + variation;
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + algorithm);
}
try {
// final String algo = buf.readString(); it has been already read
final String curveName = buf.readString();
final int keyLen = buf.readUInt32AsInt();
final byte x04 = buf.readByte(); // it must be 0x04, but don't think
// we need that check
final byte[] x = new byte[(keyLen - 1) / 2];
final byte[] y = new byte[(keyLen - 1) / 2];
buf.readRawBytes(x);
buf.readRawBytes(y);
if (log.isDebugEnabled()) {
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
algorithm, curveName, keyLen, x04, Arrays.toString(x), Arrays.toString(y)));
}
if (!SUPPORTED_CURVES.values().contains(curveName)) {
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
}
BigInteger bigX = new BigInteger(1, x);
BigInteger bigY = new BigInteger(1, y);
X9ECParameters ecParams = NISTNamedCurves.getByName(NIST_CURVES_NAMES.get(variation));
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY);
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(), ecParams.getG(), ecParams.getN());
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
return keyFactory.generatePublic(publicSpec);
} catch (Exception ex) {
throw new GeneralSecurityException(ex);
}
}
public static void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
final ECPublicKey ecdsa = (ECPublicKey) pk;
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
buf.putString("nistp" + Integer.toString(fieldSizeFromKey(ecdsa)))
.putBytes(encoded);
}
public static int fieldSizeFromKey(ECPublicKey ecPublicKey) {
return ecPublicKey.getParams().getCurve().getField().getFieldSize();
}
}

View File

@@ -35,15 +35,9 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hierynomus.sshj.secg.SecgUtils;
import com.hierynomus.sshj.signature.Ed25519PublicKey;
import com.hierynomus.sshj.userauth.certificate.Certificate;
@@ -107,9 +101,9 @@ public enum KeyType {
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
buf.putMPInt(dsaKey.getParams().getP()) // p
.putMPInt(dsaKey.getParams().getQ()) // q
.putMPInt(dsaKey.getParams().getG()) // g
.putMPInt(dsaKey.getY()); // y
.putMPInt(dsaKey.getParams().getQ()) // q
.putMPInt(dsaKey.getParams().getG()) // g
.putMPInt(dsaKey.getY()); // y
}
@Override
@@ -119,69 +113,66 @@ public enum KeyType {
},
/** SSH identifier for ECDSA keys */
ECDSA("ecdsa-sha2-nistp256") {
private final Logger log = LoggerFactory.getLogger(getClass());
/** SSH identifier for ECDSA-256 keys */
ECDSA256("ecdsa-sha2-nistp256") {
@Override
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
throws GeneralSecurityException {
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + sType);
}
try {
// final String algo = buf.readString(); it has been already read
final String curveName = buf.readString();
final int keyLen = buf.readUInt32AsInt();
final byte x04 = buf.readByte(); // it must be 0x04, but don't think we need that check
final byte[] x = new byte[(keyLen - 1) / 2];
final byte[] y = new byte[(keyLen - 1) / 2];
buf.readRawBytes(x);
buf.readRawBytes(y);
if(log.isDebugEnabled()) {
log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s",
sType,
curveName,
keyLen,
x04,
Arrays.toString(x),
Arrays.toString(y))
);
}
if (!NISTP_CURVE.equals(curveName)) {
throw new GeneralSecurityException(String.format("Unknown curve %s", curveName));
}
BigInteger bigX = new BigInteger(1, x);
BigInteger bigY = new BigInteger(1, y);
X9ECParameters ecParams = NISTNamedCurves.getByName("p-256");
ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY);
ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(),
ecParams.getG(), ecParams.getN());
ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec);
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA");
return keyFactory.generatePublic(publicSpec);
} catch (Exception ex) {
throw new GeneralSecurityException(ex);
}
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "256");
}
@Override
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
final ECPublicKey ecdsa = (ECPublicKey) pk;
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
buf.putString(NISTP_CURVE)
.putBytes(encoded);
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
}
@Override
protected boolean isMyType(Key key) {
return ("ECDSA".equals(key.getAlgorithm()));
return ("ECDSA".equals(key.getAlgorithm()) && ECDSAVariationsAdapter.fieldSizeFromKey((ECPublicKey) key) == 256);
}
},
/** SSH identifier for ECDSA-384 keys */
ECDSA384("ecdsa-sha2-nistp384") {
@Override
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
throws GeneralSecurityException {
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "384");
}
@Override
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
}
@Override
protected boolean isMyType(Key key) {
return ("ECDSA".equals(key.getAlgorithm()) && ECDSAVariationsAdapter.fieldSizeFromKey((ECPublicKey) key) == 384);
}
},
/** SSH identifier for ECDSA-521 keys */
ECDSA521("ecdsa-sha2-nistp521") {
@Override
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
throws GeneralSecurityException {
return ECDSAVariationsAdapter.readPubKeyFromBuffer(buf, "521");
}
@Override
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
ECDSAVariationsAdapter.writePubKeyContentsIntoBuffer(pk, buf);
}
@Override
protected boolean isMyType(Key key) {
return ("ECDSA".equals(key.getAlgorithm()) && ECDSAVariationsAdapter.fieldSizeFromKey((ECPublicKey) key) == 521);
}
},
@@ -284,9 +275,6 @@ public enum KeyType {
}
};
private static final String NISTP_CURVE = "nistp256";
protected final String sType;
private KeyType(String type) {
@@ -380,7 +368,7 @@ public enum KeyType {
static Certificate<PublicKey> toCertificate(PublicKey key) {
if (!(key instanceof Certificate)) {
throw new UnsupportedOperationException("Can't convert non-certificate key " +
key.getAlgorithm() + " to certificate");
key.getAlgorithm() + " to certificate");
}
return ((Certificate<PublicKey>) key);
}

View File

@@ -138,7 +138,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
int length = len;
int offset = off;
while (length > 0) {
final int n = buffer.write(data, offset, len);
final int n = buffer.write(data, offset, length);
offset += n;
length -= n;
}
@@ -149,8 +149,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
this.error = error;
}
private void checkClose()
throws SSHException {
private void checkClose() throws SSHException {
if (closed) {
if (error != null)
throw error;
@@ -160,8 +159,7 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
}
@Override
public synchronized void close()
throws IOException {
public synchronized void close() throws IOException {
if (!closed) {
try {
buffer.flush(false);

View File

@@ -77,8 +77,7 @@ public abstract class Window {
super(initialWinSize, maxPacketSize, loggerFactory);
}
public long awaitExpansion(long was)
throws ConnectionException {
public long awaitExpansion(long was) throws ConnectionException {
synchronized (lock) {
while (size <= was) {
log.debug("Waiting, need size to grow from {} bytes", was);

View File

@@ -15,35 +15,73 @@
*/
package net.schmizz.sshj.signature;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.DERSequence;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.math.BigInteger;
import java.security.SignatureException;
/** ECDSA {@link Signature} */
public class SignatureECDSA
extends AbstractSignature {
public class SignatureECDSA extends AbstractSignature {
/** A named factory for ECDSA signature */
public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Signature> {
/** A named factory for ECDSA-256 signature */
public static class Factory256 implements net.schmizz.sshj.common.Factory.Named<Signature> {
@Override
public Signature create() {
return new SignatureECDSA();
return new SignatureECDSA("SHA256withECDSA", KeyType.ECDSA256.toString());
}
@Override
public String getName() {
return KeyType.ECDSA.toString();
return KeyType.ECDSA256.toString();
}
}
public SignatureECDSA() {
super("SHA256withECDSA");
/** A named factory for ECDSA-384 signature */
public static class Factory384 implements net.schmizz.sshj.common.Factory.Named<Signature> {
@Override
public Signature create() {
return new SignatureECDSA("SHA384withECDSA", KeyType.ECDSA384.toString());
}
@Override
public String getName() {
return KeyType.ECDSA384.toString();
}
}
/** A named factory for ECDSA-521 signature */
public static class Factory521 implements net.schmizz.sshj.common.Factory.Named<Signature> {
@Override
public Signature create() {
return new SignatureECDSA("SHA512withECDSA", KeyType.ECDSA521.toString());
}
@Override
public String getName() {
return KeyType.ECDSA521.toString();
}
}
private String keyTypeName;
public SignatureECDSA(String algorithm, String keyTypeName) {
super(algorithm);
this.keyTypeName = keyTypeName;
}
@Override
@@ -75,8 +113,8 @@ public class SignatureECDSA
try {
Buffer sigbuf = new Buffer.PlainBuffer(sig);
final String algo = new String(sigbuf.readBytes());
if (!"ecdsa-sha2-nistp256".equals(algo)) {
throw new SSHRuntimeException(String.format("Signature :: ecdsa-sha2-nistp256 expected, got %s", algo));
if (!keyTypeName.equals(algo)) {
throw new SSHRuntimeException(String.format("Signature :: " + keyTypeName + " expected, got %s", algo));
}
final int rsLen = sigbuf.readUInt32AsInt();
if (sigbuf.available() != rsLen) {
@@ -88,10 +126,23 @@ public class SignatureECDSA
throw new SSHRuntimeException(e);
}
try {
return signature.verify(asnEncode(r, s));
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
} catch (IOException e) {
throw new SSHRuntimeException(e);
}
}
private byte[] asnEncode(byte[] r, byte[] s) throws IOException {
int rLen = r.length;
int sLen = s.length;
/* We can't have the high bit set, so add an extra zero at the beginning if so. */
/*
* We can't have the high bit set, so add an extra zero at the beginning
* if so.
*/
if ((r[0] & 0x80) != 0) {
rLen++;
}
@@ -101,37 +152,17 @@ public class SignatureECDSA
/* Calculate total output length */
int length = 6 + rLen + sLen;
byte[] asn1 = new byte[length];
/* ASN.1 SEQUENCE tag */
asn1[0] = (byte) 0x30;
ASN1EncodableVector vector = new ASN1EncodableVector();
vector.add(new ASN1Integer(r));
vector.add(new ASN1Integer(s));
/* Size of SEQUENCE */
asn1[1] = (byte) (4 + rLen + sLen);
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
ASN1OutputStream asnOS = new ASN1OutputStream(baos);
/* ASN.1 INTEGER tag */
asn1[2] = (byte) 0x02;
asnOS.writeObject(new DERSequence(vector));
asnOS.flush();
/* "r" INTEGER length */
asn1[3] = (byte) rLen;
/* Copy in the "r" INTEGER */
System.arraycopy(r, 0, asn1, 4, rLen);
/* ASN.1 INTEGER tag */
asn1[rLen + 4] = (byte) 0x02;
/* "s" INTEGER length */
asn1[rLen + 5] = (byte) sLen;
/* Copy in the "s" INTEGER */
System.arraycopy(s, 0, asn1, (6 + rLen), sLen);
try {
return signature.verify(asn1);
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
return baos.toByteArray();
}
}

View File

@@ -590,7 +590,7 @@ public final class TransportImpl
try {
if (!close.isSet()) {
log.error("Dying because - {}", ex);
log.error("Dying because - {}", ex.getMessage(), ex);
final SSHException causeOfDeath = SSHException.chainer.chain(ex);

View File

@@ -15,8 +15,10 @@
*/
package net.schmizz.sshj.transport.compression;
import com.jcraft.jzlib.Deflater;
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZStream;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHRuntimeException;
@@ -45,20 +47,24 @@ public class ZlibCompression
private final byte[] tempBuf = new byte[BUF_SIZE];
private ZStream stream;
private Deflater deflater;
private Inflater inflater;
@Override
public void init(Mode mode) {
stream = new ZStream();
switch (mode) {
case DEFLATE:
stream.deflateInit(JZlib.Z_DEFAULT_COMPRESSION);
break;
case INFLATE:
stream.inflateInit();
break;
default:
assert false;
try {
switch (mode) {
case DEFLATE:
deflater = new Deflater(JZlib.Z_DEFAULT_COMPRESSION);
break;
case INFLATE:
inflater = new Inflater();
break;
default:
assert false;
}
} catch (GZIPException gze) {
}
}
@@ -69,44 +75,43 @@ public class ZlibCompression
@Override
public void compress(Buffer buffer) {
stream.next_in = buffer.array();
stream.next_in_index = buffer.rpos();
stream.avail_in = buffer.available();
deflater.setNextIn(buffer.array());
deflater.setNextInIndex(buffer.rpos());
deflater.setAvailIn(buffer.available());
buffer.wpos(buffer.rpos());
do {
stream.next_out = tempBuf;
stream.next_out_index = 0;
stream.avail_out = BUF_SIZE;
final int status = stream.deflate(JZlib.Z_PARTIAL_FLUSH);
deflater.setNextOut(tempBuf);
deflater.setNextOutIndex(0);
deflater.setAvailOut(BUF_SIZE);
final int status = deflater.deflate(JZlib.Z_PARTIAL_FLUSH);
if (status == JZlib.Z_OK) {
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - stream.avail_out);
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - deflater.getAvailOut());
} else {
throw new SSHRuntimeException("compress: deflate returned " + status);
}
} while (stream.avail_out == 0);
} while (deflater.getAvailOut() == 0);
}
@Override
public void uncompress(Buffer from, Buffer to)
throws TransportException {
stream.next_in = from.array();
stream.next_in_index = from.rpos();
stream.avail_in = from.available();
inflater.setNextIn(from.array());
inflater.setNextInIndex(from.rpos());
inflater.setAvailIn(from.available());
while (true) {
stream.next_out = tempBuf;
stream.next_out_index = 0;
stream.avail_out = BUF_SIZE;
final int status = stream.inflate(JZlib.Z_PARTIAL_FLUSH);
inflater.setNextOut(tempBuf);
inflater.setNextOutIndex(0);
inflater.setAvailOut(BUF_SIZE);
final int status = inflater.inflate(JZlib.Z_PARTIAL_FLUSH);
switch (status) {
case JZlib.Z_OK:
to.putRawBytes(tempBuf, 0, BUF_SIZE - stream.avail_out);
to.putRawBytes(tempBuf, 0, BUF_SIZE - inflater.getAvailOut());
break;
case JZlib.Z_BUF_ERROR:
return;
default:
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned "
+ status);
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned " + status);
}
}
}

View File

@@ -48,7 +48,7 @@ public class ConsoleKnownHostsVerifier
}
if (response.equalsIgnoreCase(YES)) {
try {
entries().add(new SimpleEntry(null, hostname, KeyType.fromKey(key), key));
entries().add(new HostEntry(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,7 +60,7 @@ public class ConsoleKnownHostsVerifier
}
@Override
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
protected boolean hostKeyChangedAction(KnownHostEntry entry, String hostname, PublicKey key) {
final KeyType type = KeyType.fromKey(key);
final String fp = SecurityUtils.getFingerprint(key);
final String path = getFile().getAbsolutePath();

View File

@@ -15,9 +15,8 @@
*/
package net.schmizz.sshj.transport.verification;
import com.hierynomus.sshj.transport.verification.KnownHostMatchers;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.MAC;
import org.slf4j.Logger;
import java.io.*;
@@ -26,7 +25,6 @@ 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;
/**
@@ -40,7 +38,7 @@ public class OpenSSHKnownHosts
protected final Logger log;
protected final File khFile;
protected final List<HostEntry> entries = new ArrayList<HostEntry>();
protected final List<KnownHostEntry> entries = new ArrayList<KnownHostEntry>();
public OpenSSHKnownHosts(File khFile)
throws IOException {
@@ -59,7 +57,7 @@ public class OpenSSHKnownHosts
String line;
while ((line = br.readLine()) != null) {
try {
HostEntry entry = entryFactory.parseEntry(line);
KnownHostEntry entry = entryFactory.parseEntry(line);
if (entry != null) {
entries.add(entry);
}
@@ -83,12 +81,13 @@ public class OpenSSHKnownHosts
public boolean verify(final String hostname, final int port, final PublicKey key) {
final KeyType type = KeyType.fromKey(key);
if (type == KeyType.UNKNOWN)
if (type == KeyType.UNKNOWN) {
return false;
}
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
for (HostEntry e : entries) {
for (KnownHostEntry e : entries) {
try {
if (e.appliesTo(type, adjustedHostname))
return e.verify(key) || hostKeyChangedAction(e, adjustedHostname, key);
@@ -105,12 +104,12 @@ public class OpenSSHKnownHosts
return false;
}
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
protected boolean hostKeyChangedAction(KnownHostEntry entry, String hostname, PublicKey key) {
log.warn("Host key for `{}` has changed!", hostname);
return false;
}
public List<HostEntry> entries() {
public List<KnownHostEntry> entries() {
return entries;
}
@@ -120,7 +119,7 @@ public class OpenSSHKnownHosts
throws IOException {
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(khFile));
try {
for (HostEntry entry : entries)
for (KnownHostEntry entry : entries)
bos.write((entry.getLine() + LS).getBytes(IOUtils.UTF8));
} finally {
bos.close();
@@ -130,7 +129,7 @@ public class OpenSSHKnownHosts
/**
* Append a single entry
*/
public void write(HostEntry entry)
public void write(KnownHostEntry entry)
throws IOException {
final BufferedWriter writer = new BufferedWriter(new FileWriter(khFile, true));
try {
@@ -185,7 +184,7 @@ public class OpenSSHKnownHosts
EntryFactory() {
}
public HostEntry parseEntry(String line)
public KnownHostEntry parseEntry(String line)
throws IOException {
if (isComment(line)) {
return new CommentEntry(line);
@@ -228,11 +227,7 @@ public class OpenSSHKnownHosts
return null;
}
if (isHashed(hostnames)) {
return new HashedEntry(marker, hostnames, type, key);
} else {
return new SimpleEntry(marker, hostnames, type, key);
}
return new HostEntry(marker, hostnames, type, key);
}
private boolean isBits(String type) {
@@ -254,25 +249,22 @@ public class OpenSSHKnownHosts
}
public interface HostEntry {
public interface KnownHostEntry {
KeyType getType();
String getFingerprint();
boolean appliesTo(String host)
throws IOException;
boolean appliesTo(String host) throws IOException;
boolean appliesTo(KeyType type, String host)
throws IOException;
boolean appliesTo(KeyType type, String host) throws IOException;
boolean verify(PublicKey key)
throws IOException;
boolean verify(PublicKey key) throws IOException;
String getLine();
}
public static class CommentEntry
implements HostEntry {
implements KnownHostEntry {
private final String comment;
public CommentEntry(String comment) {
@@ -290,8 +282,7 @@ public class OpenSSHKnownHosts
}
@Override
public boolean appliesTo(String host)
throws IOException {
public boolean appliesTo(String host) throws IOException {
return false;
}
@@ -311,17 +302,20 @@ public class OpenSSHKnownHosts
}
}
public static abstract class AbstractEntry
implements HostEntry {
public static class HostEntry implements KnownHostEntry {
protected final OpenSSHKnownHosts.Marker marker;
final OpenSSHKnownHosts.Marker marker;
private final String hostPart;
protected final KeyType type;
protected final PublicKey key;
private final KnownHostMatchers.HostMatcher matcher;
public AbstractEntry(Marker marker, KeyType type, PublicKey key) {
HostEntry(Marker marker, String hostPart, KeyType type, PublicKey key) throws SSHException {
this.marker = marker;
this.hostPart = hostPart;
this.type = type;
this.key = key;
this.matcher = KnownHostMatchers.createMatcher(hostPart);
}
@Override
@@ -335,8 +329,17 @@ public class OpenSSHKnownHosts
}
@Override
public boolean verify(PublicKey key)
throws IOException {
public boolean appliesTo(String host) throws IOException {
return matcher.match(host);
}
@Override
public boolean appliesTo(KeyType type, String host) throws IOException {
return this.type == type && matcher.match(host);
}
@Override
public boolean verify(PublicKey key) throws IOException {
return key.equals(this.key) && marker != Marker.REVOKED;
}
@@ -356,88 +359,8 @@ public class OpenSSHKnownHosts
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(String host)
throws IOException {
return hosts.contains(host);
}
@Override
public boolean appliesTo(KeyType type, String host)
throws IOException {
return type == this.type && hosts.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(String host)
throws IOException {
return hashedHost.equals(hashHost(host));
}
@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
protected String getHostPart() {
return hashedHost;
return hostPart;
}
}
@@ -456,12 +379,12 @@ public class OpenSSHKnownHosts
}
public static Marker fromString(String str) {
for (Marker m: values())
if (m.sMarker.equals(str))
for (Marker m: values()) {
if (m.sMarker.equals(str)) {
return m;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.userauth.password;
import java.io.Console;
import java.util.IllegalFormatException;
/** A PasswordFinder that reads a password from a console */
public class ConsolePasswordFinder implements PasswordFinder {
public static final String DEFAULT_FORMAT = "Enter passphrase for %s:";
private final Console console;
private final String promptFormat;
private final int maxTries;
private int numTries;
public ConsolePasswordFinder() {
this(System.console());
}
public ConsolePasswordFinder(Console console) {
this(console, DEFAULT_FORMAT, 3);
}
public ConsolePasswordFinder(Console console, String promptFormat, int maxTries) {
checkFormatString(promptFormat);
this.console = console;
this.promptFormat = promptFormat;
this.maxTries = maxTries;
this.numTries = 0;
}
@Override
public char[] reqPassword(Resource<?> resource) {
numTries++;
if (console == null) {
// the request cannot be serviced
return null;
}
return console.readPassword(promptFormat, resource.toString());
}
@Override
public boolean shouldRetry(Resource<?> resource) {
return numTries < maxTries;
}
private static void checkFormatString(String promptFormat) {
try {
String.format(promptFormat, "");
} catch (IllegalFormatException e) {
throw new IllegalArgumentException("promptFormat must have no more than one %s and no other markers", e);
}
}
}

View File

@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class FileSystemFile
implements LocalSourceFile, LocalDestFile {
@@ -83,8 +84,9 @@ public class FileSystemFile
}
});
if (childFiles == null)
if (childFiles == null) {
throw new IOException("Error listing files in directory: " + this);
}
final List<FileSystemFile> children = new ArrayList<FileSystemFile>();
for (File f : childFiles) {
@@ -113,12 +115,13 @@ public class FileSystemFile
@Override
public int getPermissions()
throws IOException {
if (isDirectory())
if (isDirectory()) {
return 0755;
else if (isFile())
} else if (isFile()) {
return 0644;
else
} else {
throw new IOException("Unsupported file type");
}
}
@Override
@@ -130,8 +133,9 @@ public class FileSystemFile
@Override
public void setLastModifiedTime(long t)
throws IOException {
if (!file.setLastModified(t * 1000))
if (!file.setLastModified(t * 1000)) {
log.warn("Could not set last modified time for {} to {}", file, t);
}
}
@Override
@@ -143,22 +147,41 @@ public class FileSystemFile
!(FilePermission.OTH_W.isIn(perms) || FilePermission.GRP_W.isIn(perms)));
final boolean x = file.setExecutable(FilePermission.USR_X.isIn(perms),
!(FilePermission.OTH_X.isIn(perms) || FilePermission.GRP_X.isIn(perms)));
if (!(r && w && x))
if (!(r && w && x)) {
log.warn("Could not set permissions for {} to {}", file, Integer.toString(perms, 16));
}
}
@Override
public FileSystemFile getChild(String name) {
validateIsChildPath(name);
return new FileSystemFile(new File(file, name));
}
private void validateIsChildPath(String name) {
String[] split = name.split("/");
Stack<String> s = new Stack<String>();
for (String component : split) {
if (component == null || component.isEmpty() || ".".equals(component)) {
continue;
} else if ("..".equals(component) && !s.isEmpty()) {
s.pop();
continue;
} else if ("..".equals(component)) {
throw new IllegalArgumentException("Cannot traverse higher than " + file + " to get child " + name);
}
s.push(component);
}
}
@Override
public FileSystemFile getTargetFile(String filename)
throws IOException {
FileSystemFile f = this;
if (f.isDirectory())
if (f.isDirectory()) {
f = f.getChild(filename);
}
if (!f.getFile().exists()) {
if (!f.getFile().createNewFile())
@@ -174,12 +197,15 @@ public class FileSystemFile
throws IOException {
FileSystemFile f = this;
if (f.getFile().exists())
if (f.getFile().exists()) {
if (f.isDirectory()) {
if (!f.getName().equals(dirname))
if (!f.getName().equals(dirname)) {
f = f.getChild(dirname);
} else
}
} else {
throw new IOException(f + " - already exists as a file; directory required");
}
}
if (!f.getFile().exists() && !f.getFile().mkdir())
throw new IOException("Failed to create directory: " + f);

View File

@@ -75,9 +75,9 @@ public class SCPDownloadClient extends AbstractSCPClient {
engine.signal("Start status OK");
String msg = engine.readMessage();
do
do {
process(engine.getTransferListener(), null, msg, targetFile);
while (!(msg = engine.readMessage()).isEmpty());
} while (!(msg = engine.readMessage()).isEmpty());
}
private long parseLong(String longString, String valType)
@@ -93,15 +93,17 @@ public class SCPDownloadClient extends AbstractSCPClient {
private int parsePermissions(String cmd)
throws SCPException {
if (cmd.length() != 5)
if (cmd.length() != 5) {
throw new SCPException("Could not parse permissions from `" + cmd + "`");
}
return Integer.parseInt(cmd.substring(1), 8);
}
private boolean process(TransferListener listener, String bufferedTMsg, String msg, LocalDestFile f)
throws IOException {
if (msg.length() < 1)
if (msg.length() < 1) {
throw new SCPException("Could not parse message `" + msg + "`");
}
switch (msg.charAt(0)) {
@@ -139,8 +141,9 @@ public class SCPDownloadClient extends AbstractSCPClient {
final List<String> dMsgParts = tokenize(dMsg, 3, true); // D<perms> 0 <dirname>
final long length = parseLong(dMsgParts.get(1), "dir length");
final String dirname = dMsgParts.get(2);
if (length != 0)
if (length != 0) {
throw new IOException("Remote SCP command sent strange directory length: " + length);
}
final TransferListener dirListener = listener.directory(dirname);
{
@@ -186,9 +189,9 @@ public class SCPDownloadClient extends AbstractSCPClient {
private static List<String> tokenize(String msg, int totalParts, boolean consolidateTail)
throws IOException {
List<String> parts = Arrays.asList(msg.split(" "));
if (parts.size() < totalParts ||
(!consolidateTail && parts.size() != totalParts))
if (parts.size() < totalParts || (!consolidateTail && parts.size() != totalParts)) {
throw new IOException("Could not parse message received from remote SCP: " + msg);
}
if (consolidateTail && totalParts < parts.size()) {
final StringBuilder sb = new StringBuilder(parts.get(totalParts - 1));

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.transport.verification
import com.hierynomus.sshj.transport.verification.KnownHostMatchers
import spock.lang.Specification
import spock.lang.Unroll
class KnownHostMatchersSpec extends Specification {
@Unroll
def "should #yesno match host #host with pattern #pattern"() {
given:
def matcher = KnownHostMatchers.createMatcher(pattern)
expect:
match == matcher.match(host)
where:
pattern | host | match
"aaa.bbb.com" | "aaa.bbb.com" | true
"aaa.bbb.com" | "aaa.ccc.com" | false
"*.bbb.com" | "aaa.bbb.com" | true
"*.bbb.com" | "aaa.ccc.com" | false
"aaa.*.com" | "aaa.bbb.com" | true
"aaa.*.com" | "aaa.ccc.com" | true
"aaa.bbb.*" | "aaa.bbb.com" | true
"aaa.bbb.*" | "aaa.ccc.com" | false
"!*.bbb.com" | "aaa.bbb.com" | false
"!*.bbb.com" | "aaa.ccc.com" | true
"aaa.bbb.com,!*.ccc.com" | "xxx.yyy.com" | true
"aaa.bbb.com,!*.ccc.com" | "aaa.bbb.com" | true
"aaa.bbb.com,!*.ccc.com" | "aaa.ccc.com" | false
"aaa.b??.com" | "aaa.bbb.com" | true
"aaa.b??.com" | "aaa.bcd.com" | true
"aaa.b??.com" | "aaa.ccd.com" | false
"aaa.b??.com" | "aaa.bccd.com" | false
"|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg=" | "192.168.1.61" | true
"|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg=" | "192.168.2.61" | false
yesno = match ? "" : "no"
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer
import spock.lang.Specification
class FileSystemFileSpec extends Specification {
def "should get child path"() {
given:
def file = new FileSystemFile("foo")
when:
def child = file.getChild("bar")
then:
child.getName() == "bar"
}
def "should not traverse higher than original path when getChild is called"() {
given:
def file = new FileSystemFile("foo")
when:
file.getChild("bar/.././foo/../../")
then:
thrown(IllegalArgumentException.class)
}
def "should ignore double slash (empty path component)"() {
given:
def file = new FileSystemFile("foo")
when:
def child = file.getChild("bar//etc/passwd")
then:
child.getFile().getPath() endsWith "foo/bar/etc/passwd"
}
}

View File

@@ -146,7 +146,7 @@ public class OpenSSHKeyFileTest {
}
@Test
public void shouldHaveCorrectFingerprintForECDSA() throws IOException, GeneralSecurityException {
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n";
@@ -155,6 +155,26 @@ public class OpenSSHKeyFileTest {
assertThat(expected, containsString(sshjFingerprintSshjKey));
}
@Test
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384"));
String expected = "384 MD5:ee:9b:82:d1:47:01:16:1b:27:da:f5:27:fd:b2:eb:e2";
PublicKey aPublic = keyFile.getPublic();
String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic);
assertThat(expected, containsString(sshjFingerprintSshjKey));
}
@Test
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException, GeneralSecurityException {
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521"));
String expected = "521 MD5:22:e2:f4:3c:61:ae:e9:85:a1:4d:d9:6c:13:aa:eb:00";
PublicKey aPublic = keyFile.getPublic();
String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic);
assertThat(expected, containsString(sshjFingerprintSshjKey));
}
@Test
public void shouldHaveCorrectFingerprintForED25519() throws IOException {
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.signature;
import java.security.PublicKey;
import org.junit.Assert;
import org.junit.Test;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.Buffer.BufferException;
public class VerificationTest {
@Test
public void testECDSA256Verifies() throws BufferException {
byte[] K_S = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, -8, 35, 96, -97, 65, -33, -128, -58, -64, -73, -51, 10, -28, 20, -59, 86, -88, -24, 126, 29, 115, 26, -88, 31, -115, 87, -109, -4, 61, 108, 28, 31, -66, 79, 107, 17, 24, 93, 114, -25, 121, 57, -58, 10, 26, -36, -100, -120, -7, -103, 86, 72, -109, -82, 111, 73, 4, -98, 58, 28, -3, -91, 28, 84");
byte[] H = fromString("61, 55, -62, -122, -93, 82, -63, 25, -52, -13, -41, -29, 78, 101, 22, -75, 113, 59, -72, -92, -2, 39, -52, -89, 127, 80, -77, -82, 67, 3, -21, -53");
byte[] sig = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 73, 0, 0, 0, 33, 0, -19, 50, -123, -35, 93, 50, 3, 40, -79, 110, -99, 6, -78, 40, -31, -26, -119, 113, -101, 109, -27, 12, 47, -119, -83, 107, -7, 116, 2, 97, 84, 32, 0, 0, 0, 32, 69, -44, 52, -119, 22, -60, -33, -105, -41, 45, 36, 112, -59, 49, -90, -110, -13, -114, 115, -86, 29, 30, 127, -44, 96, 57, -49, 39, -83, 50, -8, 123");
PublicKey hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
Signature signature = new SignatureECDSA.Factory256().create();
signature.init(hostKey, null);
signature.update(H, 0, H.length);
Assert.assertTrue("ECDSA256 signature verifies", signature.verify(sig));
}
@Test
public void testECDSA384Verifies() throws BufferException {
byte[] K_S = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 51, 56, 52, 0, 0, 0, 8, 110, 105, 115, 116, 112, 51, 56, 52, 0, 0, 0, 97, 4, 105, 52, -67, 89, 21, 53, -125, -26, -23, -125, 48, 119, -63, -66, 30, -46, -110, 21, 14, -96, -28, 40, -108, 60, 120, 110, 58, 30, -56, 37, 6, -17, -25, 109, 84, 67, -19, 0, -30, -33, 54, 94, -121, 49, 68, -66, 14, 6, 76, -51, 102, -123, 59, -24, -34, 79, -51, 64, -48, -45, 21, -42, -96, -123, -27, -21, 15, 56, -96, -12, 73, -10, 113, -20, -22, 38, 100, 38, -85, -113, 46, 36, 17, -30, 89, 40, 16, 104, 123, 50, 8, 122, 49, -41, -97, 95");
byte[] H = fromString("-46, 22, -52, 62, -100, -43, -68, -88, 98, 31, 116, -77, 27, -92, 127, 25, -43, -63, -42, -106, -53, 26, -61, 69, -38, -73, 94, -70, -99, -6, -78, 61");
byte[] sig = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 51, 56, 52, 0, 0, 0, 105, 0, 0, 0, 48, 58, -5, -53, 17, -127, -32, 74, 123, -84, -1, 80, 96, 49, -77, -109, 22, -90, 115, -111, 40, 2, 4, 56, 51, 92, -30, 39, -61, -92, -76, -105, 45, 52, -31, 116, 44, -32, -65, 57, 44, 26, 45, 59, -115, 95, 113, 114, -89, 0, 0, 0, 49, 0, -56, 65, 59, 111, -26, -72, 127, 47, -15, 14, -34, 56, 5, 34, 28, -78, -13, 26, -22, -41, -86, -36, -112, 10, 91, 48, -77, -84, 93, 111, -84, 59, 42, -128, -22, 91, -4, -31, -89, -37, 107, -27, 28, -119, -36, 93, 25, -49");
PublicKey hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
Signature signature = new SignatureECDSA.Factory384().create();
signature.init(hostKey, null);
signature.update(H, 0, H.length);
Assert.assertTrue("ECDSA384 signature verifies", signature.verify(sig));
}
@Test
public void testECDSA521Verifies() throws BufferException {
byte[] K_S = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 53, 50, 49, 0, 0, 0, 8, 110, 105, 115, 116, 112, 53, 50, 49, 0, 0, 0, -123, 4, 1, -56, 55, 64, -73, -109, 95, 94, -107, -116, -46, -16, 119, -66, -68, 41, -103, -66, 102, -123, -69, 59, -8, 106, 72, 75, 7, 56, -79, 109, -88, 77, 12, -97, -109, -32, -60, 64, -75, -48, 50, -51, -68, -81, 75, 110, -7, -79, -32, -36, -73, -7, -65, -24, 40, -74, 58, 43, -26, -5, -55, 125, -32, -89, -54, -111, 0, 81, 37, -73, 60, 69, 107, -108, 115, 60, -61, 22, 6, -128, -69, -28, 122, -26, -37, -117, 121, -106, -126, 23, -90, 127, 73, -58, -113, -61, 105, 68, 116, 85, -115, -47, 90, 122, 109, -21, 127, 39, -75, -58, -109, 73, -82, -122, -11, -44, -87, 85, -100, -4, -123, -31, 126, -94, 127, 96, 9, -30, 70, -113, -42, 28");
byte[] H = fromString("-36, -107, -95, 2, 93, -111, -19, -107, 118, -7, 116, -33, 58, -90, -63, -60, -5, -23, 7, 56, -128, -22, -15, 26, -97, 2, 50, -93, 21, -21, 69, 105");
byte[] sig = fromString("0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 53, 50, 49, 0, 0, 0, -117, 0, 0, 0, 66, 1, 31, -111, 69, -37, 33, 24, -95, 53, -124, -33, 41, 65, -96, -112, -102, -33, 123, 30, -108, 102, 127, -27, 72, 101, -108, -123, 6, 107, 83, -72, -121, 87, -86, 75, 114, 50, -60, -75, -46, 7, -63, 84, -114, -91, 113, 52, 26, 102, -11, 76, 99, 9, 19, -73, -42, -3, 57, 41, -42, 13, -81, 18, -3, -49, -50, 0, 0, 0, 65, 102, 60, -2, 123, 91, -8, 120, 42, 118, 118, -9, -112, 72, 8, 61, -49, -45, 63, 112, 61, -55, -122, -109, 4, -39, 95, 3, -4, -43, 98, 39, 4, 63, 78, 78, 51, 77, 75, -23, 19, -46, 117, -115, -95, 90, -43, 108, -47, -90, 84, 98, 50, -97, -37, -14, -115, -76, 14, -61, 91, 107, 23, -112, 22, -15");
PublicKey hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
Signature signature = new SignatureECDSA.Factory521().create();
signature.init(hostKey, null);
signature.update(H, 0, H.length);
Assert.assertTrue("ECDSA521 signature verifies", signature.verify(sig));
}
private byte[] fromString(String string) {
String[] split = string.split(", ");
byte[] res = new byte[split.length];
for (int i = 0; i < split.length; i++)
res[i] = Byte.parseByte(split[i]);
return res;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.userauth.password;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.io.Console;
public class TestConsolePasswordFinder {
private static final String FORMAT = "%s";
@Test
public void testReqPassword() {
char[] expectedPassword = "password".toCharArray();
Console console = Mockito.mock(Console.class);
Mockito.when(console.readPassword(Mockito.anyString(), Mockito.any()))
.thenReturn(expectedPassword);
Resource resource = Mockito.mock(Resource.class);
char[] password = new ConsolePasswordFinder(console).reqPassword(resource);
Assert.assertArrayEquals("Password should match mocked return value",
expectedPassword, password);
Mockito.verifyNoMoreInteractions(resource);
}
@Test
public void testReqPasswordNullConsole() {
Resource<?> resource = Mockito.mock(Resource.class);
char[] password = new ConsolePasswordFinder(null, FORMAT, 1).reqPassword(resource);
Assert.assertNull("Password should be null with null console", password);
Mockito.verifyNoMoreInteractions(resource);
}
@Test
public void testShouldRetry() {
Resource<String> resource = new PrivateKeyStringResource("");
ConsolePasswordFinder finder = new ConsolePasswordFinder(null, FORMAT, 1);
Assert.assertTrue("Should allow a retry at first", finder.shouldRetry(resource));
finder.reqPassword(resource);
Assert.assertFalse("Should stop allowing retries after one interaction", finder.shouldRetry(resource));
}
@Test
public void testPromptFormat() {
Assert.assertNotNull(
"Empty format should create valid ConsolePasswordFinder",
new ConsolePasswordFinder(null, "", 1));
Assert.assertNotNull(
"Single-string format should create valid ConsolePasswordFinder",
new ConsolePasswordFinder(null, FORMAT, 1));
}
@Test(expected = IllegalArgumentException.class)
public void testPromptFormatTooManyMarkers() {
new ConsolePasswordFinder(null, "%s%s", 1);
}
@Test(expected = IllegalArgumentException.class)
public void testPromptFormatWrongMarkerType() {
new ConsolePasswordFinder(null, "%d", 1);
}
}

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBGk0vVkVNYPm6YMwd8G+HtKSFQ6g5CiUPHhuOh7IJQbv521UQ+0A4t82XocxRL4OBkzNZoU76N5PzUDQ0xXWoIXl6w84oPRJ9nHs6iZkJquPLiQR4lkoEGh7Mgh6MdefXw==

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHIN0C3k19elYzS8He+vCmZvmaFuzv4akhLBzixbahNDJ+T4MRAtdAyzbyvS275seDct/m/6Ci2Oivm+8l94KfKkQBRJbc8RWuUczzDFgaAu+R65tuLeZaCF6Z/ScaPw2lEdFWN0Vp6bet/J7XGk0muhvXUqVWc/IXhfqJ/YAniRo/WHA==

View File

@@ -0,0 +1,2 @@
# incubating feature to allow mocking final classes
mock-maker-inline