mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb1629f250 | ||
|
|
8856aaea61 | ||
|
|
1f6615b57a | ||
|
|
e5084ed8db | ||
|
|
3729119e23 | ||
|
|
aed3decf1d | ||
|
|
303c03061c | ||
|
|
5e3a08a637 | ||
|
|
d0800058e8 | ||
|
|
ad9c2d5411 | ||
|
|
ed65176b68 | ||
|
|
28f3280a84 | ||
|
|
d69f722908 | ||
|
|
1d7cb8c2c6 | ||
|
|
6ad6242ed1 | ||
|
|
3310530d42 | ||
|
|
3685f9dc36 | ||
|
|
f8cad120a6 | ||
|
|
56dd4e4af4 | ||
|
|
9f8cf1f298 | ||
|
|
a51270791d | ||
|
|
d43fc4551e | ||
|
|
93bf6c0089 | ||
|
|
7b535a8db3 |
@@ -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,11 @@ 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)::
|
||||
|
||||
@@ -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'
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
@@ -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 + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
1
src/test/resources/keytypes/test_ecdsa_nistp384.pub
Normal file
1
src/test/resources/keytypes/test_ecdsa_nistp384.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBGk0vVkVNYPm6YMwd8G+HtKSFQ6g5CiUPHhuOh7IJQbv521UQ+0A4t82XocxRL4OBkzNZoU76N5PzUDQ0xXWoIXl6w84oPRJ9nHs6iZkJquPLiQR4lkoEGh7Mgh6MdefXw==
|
||||
1
src/test/resources/keytypes/test_ecdsa_nistp521.pub
Normal file
1
src/test/resources/keytypes/test_ecdsa_nistp521.pub
Normal file
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHIN0C3k19elYzS8He+vCmZvmaFuzv4akhLBzixbahNDJ+T4MRAtdAyzbyvS275seDct/m/6Ci2Oivm+8l94KfKkQBRJbc8RWuUczzDFgaAu+R65tuLeZaCF6Z/ScaPw2lEdFWN0Vp6bet/J7XGk0muhvXUqVWc/IXhfqJ/YAniRo/WHA==
|
||||
@@ -0,0 +1,2 @@
|
||||
# incubating feature to allow mocking final classes
|
||||
mock-maker-inline
|
||||
Reference in New Issue
Block a user