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:
Bernie
2021-09-23 07:09:14 -04:00
committed by GitHub
parent 753e3a50e5
commit 14bf93e677
6 changed files with 101 additions and 31 deletions

View File

@@ -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");

View File

@@ -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)

View File

@@ -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 + "'}";
} }
} }

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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();
}
} }