mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-07 15:50:57 +03:00
Prefer known algorithm for known host (#721)
* Prefer known algorithm for known host (#642, #635... 10? issues) Try to find the Algorithm that was used when a known_host entry was created and make that the first choice for the current connection attempt. If the current connection algorithm matches the algorithm used when the known_host entry was created we can get a fair verification. * Add support for multiple matching hostkeys, in configuration order Co-authored-by: Bernie Day <bday@jvncomm.com> Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
@@ -170,11 +170,22 @@ final class KeyExchanger
|
|||||||
private void sendKexInit()
|
private void sendKexInit()
|
||||||
throws TransportException {
|
throws TransportException {
|
||||||
log.debug("Sending SSH_MSG_KEXINIT");
|
log.debug("Sending SSH_MSG_KEXINIT");
|
||||||
clientProposal = new Proposal(transport.getConfig());
|
List<String> knownHostAlgs = findKnownHostAlgs(transport.getRemoteHost(), transport.getRemotePort());
|
||||||
|
clientProposal = new Proposal(transport.getConfig(), knownHostAlgs);
|
||||||
transport.write(clientProposal.getPacket());
|
transport.write(clientProposal.getPacket());
|
||||||
kexInitSent.set();
|
kexInitSent.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> findKnownHostAlgs(String hostname, int port) {
|
||||||
|
for (HostKeyVerifier hkv : hostVerifiers) {
|
||||||
|
List<String> keyTypes = hkv.findExistingAlgorithms(hostname, port);
|
||||||
|
if (keyTypes != null && !keyTypes.isEmpty()) {
|
||||||
|
return keyTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
private void sendNewKeys()
|
private void sendNewKeys()
|
||||||
throws TransportException {
|
throws TransportException {
|
||||||
log.debug("Sending SSH_MSG_NEWKEYS");
|
log.debug("Sending SSH_MSG_NEWKEYS");
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ class Proposal {
|
|||||||
private final List<String> s2cComp;
|
private final List<String> s2cComp;
|
||||||
private final SSHPacket packet;
|
private final SSHPacket packet;
|
||||||
|
|
||||||
public Proposal(Config config) {
|
public Proposal(Config config, List<String> knownHostAlgs) {
|
||||||
kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
|
kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
|
||||||
sig = Factory.Named.Util.getNames(config.getKeyAlgorithms());
|
sig = filterKnownHostKeyAlgorithms(Factory.Named.Util.getNames(config.getKeyAlgorithms()), knownHostAlgs);
|
||||||
c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
|
c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
|
||||||
c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
|
c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
|
||||||
c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());
|
c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());
|
||||||
@@ -127,32 +127,43 @@ class Proposal {
|
|||||||
public NegotiatedAlgorithms negotiate(Proposal other)
|
public NegotiatedAlgorithms negotiate(Proposal other)
|
||||||
throws TransportException {
|
throws TransportException {
|
||||||
return new NegotiatedAlgorithms(
|
return new NegotiatedAlgorithms(
|
||||||
firstMatch("KeyExchangeAlgorithms",
|
firstMatch("KeyExchangeAlgorithms", this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
|
||||||
this.getKeyExchangeAlgorithms(),
|
firstMatch("HostKeyAlgorithms", this.getHostKeyAlgorithms(), other.getHostKeyAlgorithms()),
|
||||||
other.getKeyExchangeAlgorithms()),
|
firstMatch("Client2ServerCipherAlgorithms", this.getClient2ServerCipherAlgorithms(),
|
||||||
firstMatch("HostKeyAlgorithms",
|
other.getClient2ServerCipherAlgorithms()),
|
||||||
this.getHostKeyAlgorithms(),
|
firstMatch("Server2ClientCipherAlgorithms", this.getServer2ClientCipherAlgorithms(),
|
||||||
other.getHostKeyAlgorithms()),
|
other.getServer2ClientCipherAlgorithms()),
|
||||||
firstMatch("Client2ServerCipherAlgorithms",
|
firstMatch("Client2ServerMACAlgorithms", this.getClient2ServerMACAlgorithms(),
|
||||||
this.getClient2ServerCipherAlgorithms(),
|
other.getClient2ServerMACAlgorithms()),
|
||||||
other.getClient2ServerCipherAlgorithms()),
|
firstMatch("Server2ClientMACAlgorithms", this.getServer2ClientMACAlgorithms(),
|
||||||
firstMatch("Server2ClientCipherAlgorithms",
|
other.getServer2ClientMACAlgorithms()),
|
||||||
this.getServer2ClientCipherAlgorithms(),
|
firstMatch("Client2ServerCompressionAlgorithms", this.getClient2ServerCompressionAlgorithms(),
|
||||||
other.getServer2ClientCipherAlgorithms()),
|
other.getClient2ServerCompressionAlgorithms()),
|
||||||
firstMatch("Client2ServerMACAlgorithms",
|
firstMatch("Server2ClientCompressionAlgorithms", this.getServer2ClientCompressionAlgorithms(),
|
||||||
this.getClient2ServerMACAlgorithms(),
|
other.getServer2ClientCompressionAlgorithms()),
|
||||||
other.getClient2ServerMACAlgorithms()),
|
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS));
|
||||||
firstMatch("Server2ClientMACAlgorithms",
|
}
|
||||||
this.getServer2ClientMACAlgorithms(),
|
|
||||||
other.getServer2ClientMACAlgorithms()),
|
private List<String> filterKnownHostKeyAlgorithms(List<String> configuredKeyAlgorithms, List<String> knownHostKeyAlgorithms) {
|
||||||
firstMatch("Client2ServerCompressionAlgorithms",
|
if (knownHostKeyAlgorithms != null && !knownHostKeyAlgorithms.isEmpty()) {
|
||||||
this.getClient2ServerCompressionAlgorithms(),
|
List<String> preferredAlgorithms = new ArrayList<String>();
|
||||||
other.getClient2ServerCompressionAlgorithms()),
|
List<String> otherAlgorithms = new ArrayList<String>();
|
||||||
firstMatch("Server2ClientCompressionAlgorithms",
|
|
||||||
this.getServer2ClientCompressionAlgorithms(),
|
for (String configuredKeyAlgorithm : configuredKeyAlgorithms) {
|
||||||
other.getServer2ClientCompressionAlgorithms()),
|
if (knownHostKeyAlgorithms.contains(configuredKeyAlgorithm)) {
|
||||||
other.getHostKeyAlgorithms().containsAll(KeyAlgorithms.SSH_RSA_SHA2_ALGORITHMS)
|
preferredAlgorithms.add(configuredKeyAlgorithm);
|
||||||
);
|
} else {
|
||||||
|
otherAlgorithms.add(configuredKeyAlgorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredAlgorithms.addAll(otherAlgorithms);
|
||||||
|
|
||||||
|
return preferredAlgorithms;
|
||||||
|
} else {
|
||||||
|
return configuredKeyAlgorithms;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String firstMatch(String ofWhat, List<String> a, List<String> b)
|
private static String firstMatch(String ofWhat, List<String> a, List<String> b)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import net.schmizz.sshj.common.Base64;
|
import net.schmizz.sshj.common.Base64;
|
||||||
@@ -74,6 +76,11 @@ public class FingerprintVerifier implements HostKeyVerifier {
|
|||||||
public boolean verify(String h, int p, PublicKey k) {
|
public boolean verify(String h, int p, PublicKey k) {
|
||||||
return SecurityUtils.getFingerprint(k).equals(md5);
|
return SecurityUtils.getFingerprint(k).equals(md5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> findExistingAlgorithms(String hostname, int port) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (SSHRuntimeException e) {
|
} catch (SSHRuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -120,8 +127,13 @@ public class FingerprintVerifier implements HostKeyVerifier {
|
|||||||
return Arrays.equals(fingerprintData, digestData);
|
return Arrays.equals(fingerprintData, digestData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> findExistingAlgorithms(String hostname, int port) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "FingerprintVerifier{digestAlgorithm='" + digestAlgorithm + "'}";
|
return "FingerprintVerifier{digestAlgorithm='" + digestAlgorithm + "'}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package net.schmizz.sshj.transport.verification;
|
package net.schmizz.sshj.transport.verification;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/** Host key verification interface. */
|
/** Host key verification interface. */
|
||||||
public interface HostKeyVerifier {
|
public interface HostKeyVerifier {
|
||||||
@@ -35,4 +36,12 @@ public interface HostKeyVerifier {
|
|||||||
*/
|
*/
|
||||||
boolean verify(String hostname, int port, PublicKey key);
|
boolean verify(String hostname, int port, PublicKey key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is necessary to connect with the type of algorithm that matches an existing know_host entry.
|
||||||
|
* This will allow a match when we later verify with the negotiated key {@code HostKeyVerifier.verify}
|
||||||
|
* @param hostname remote hostname
|
||||||
|
* @param port remote port
|
||||||
|
* @return existing key types or empty list if no keys known for hostname
|
||||||
|
*/
|
||||||
|
List<String> findExistingAlgorithms(String hostname, int port);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ public class OpenSSHKnownHosts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String adjustHostname(final String hostname, final int port) {
|
||||||
|
String lowerHN = hostname.toLowerCase();
|
||||||
|
return (port != 22) ? "[" + lowerHN + "]:" + port : lowerHN;
|
||||||
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
return khFile;
|
return khFile;
|
||||||
@@ -103,7 +107,7 @@ public class OpenSSHKnownHosts
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
|
final String adjustedHostname = adjustHostname(hostname, port);
|
||||||
|
|
||||||
boolean foundApplicableHostEntry = false;
|
boolean foundApplicableHostEntry = false;
|
||||||
for (KnownHostEntry e : entries) {
|
for (KnownHostEntry e : entries) {
|
||||||
@@ -127,6 +131,22 @@ public class OpenSSHKnownHosts
|
|||||||
return hostKeyUnverifiableAction(adjustedHostname, key);
|
return hostKeyUnverifiableAction(adjustedHostname, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> findExistingAlgorithms(String hostname, int port) {
|
||||||
|
final String adjustedHostname = adjustHostname(hostname, port);
|
||||||
|
List<String> knownHostAlgorithms = new ArrayList<String>();
|
||||||
|
for (KnownHostEntry e : entries) {
|
||||||
|
try {
|
||||||
|
if (e.appliesTo(adjustedHostname)) {
|
||||||
|
knownHostAlgorithms.add(e.getType().toString());
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return knownHostAlgorithms;
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
|
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
package net.schmizz.sshj.transport.verification;
|
package net.schmizz.sshj.transport.verification;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class PromiscuousVerifier
|
public final class PromiscuousVerifier
|
||||||
implements HostKeyVerifier {
|
implements HostKeyVerifier {
|
||||||
@@ -25,4 +27,9 @@ public final class PromiscuousVerifier
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> findExistingAlgorithms(String hostname, int port) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user