This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 497853b287e0fe19359fe2e2c38219e99b8df231
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Apr 24 21:37:33 2025 +0200

    Keep track of already known host keys for a session
    
    Track verified host keys for a session, and if a host key in a re-KEX
    matches one of these already known-to-be-good keys, accept it silently
    without calling the ServerKeyVerifier.
    
    If a re-KEX occurs during a session, it's basically possible that the
    server sends a different host key than in the last one.
---
 .../sshd/client/session/AbstractClientSession.java | 46 +++++++++++++++-------
 1 file changed, 31 insertions(+), 15 deletions(-)

diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index 9ad53d068..38225cd12 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -25,9 +25,11 @@ import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.security.KeyPair;
 import java.security.PublicKey;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.sshd.client.ClientFactoryManager;
@@ -58,6 +60,8 @@ import org.apache.sshd.common.channel.Channel;
 import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
 import org.apache.sshd.common.cipher.BuiltinCiphers;
 import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.forward.Forwarder;
 import org.apache.sshd.common.future.KeyExchangeFuture;
 import org.apache.sshd.common.io.IoSession;
@@ -88,6 +92,10 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
     protected final boolean sendImmediateClientIdentification;
     protected final boolean sendImmediateKexInit;
 
+    // Keep a record of already known and accepted host keys for this session. 
This enables us to bypass the
+    // ServerKeyVerifier if a re-KEX returned the same key, or even a 
different key it had announced before via the
+    // OpenSSH "hostkeys...@openssh.com" SSH_MSG_GLOBAL_REQUEST.
+    private final Set<String> sessionHostKeys = new HashSet<>();
     private final List<Object> identities = new CopyOnWriteArrayList<>();
     private final AuthenticationIdentitiesProvider identitiesProvider;
     private final AttributeRepository connectionContext;
@@ -522,23 +530,31 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
 
     @Override
     protected void checkKeys() throws IOException {
-        ServerKeyVerifier verifier = 
Objects.requireNonNull(getServerKeyVerifier(), "No server key verifier");
-        IoSession networkSession = getIoSession();
-        SocketAddress remoteAddress = networkSession.getRemoteAddress();
         PublicKey hostKey = Objects.requireNonNull(getServerKey(), "No server 
key to verify");
-        SshdSocketAddress targetServerAddress = 
getAttribute(ClientSessionCreator.TARGET_SERVER);
-        if (targetServerAddress != null) {
-            remoteAddress = targetServerAddress.toInetSocketAddress();
-        }
-
-        boolean verified = verifier.verifyServerKey(this, remoteAddress, 
hostKey);
-        if (log.isDebugEnabled()) {
-            log.debug("checkKeys({}) key={}-{}, verified={}", this, 
KeyUtils.getKeyType(hostKey),
-                    KeyUtils.getFingerPrint(hostKey), verified);
+        String serializedKey;
+        if (hostKey instanceof OpenSshCertificate) {
+            // Expiration etc. has already been checked.
+            serializedKey = "cert-authority " + 
PublicKeyEntry.toString(((OpenSshCertificate) hostKey).getCaPubKey());
+        } else {
+            serializedKey = PublicKeyEntry.toString(hostKey);
         }
-
-        if (!verified) {
-            throw new 
SshException(SshConstants.SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Server key 
did not validate");
+        if (!sessionHostKeys.contains(serializedKey)) {
+            ServerKeyVerifier verifier = 
Objects.requireNonNull(getServerKeyVerifier(), "No server key verifier");
+            IoSession networkSession = getIoSession();
+            SocketAddress remoteAddress = networkSession.getRemoteAddress();
+            SshdSocketAddress targetServerAddress = 
getAttribute(ClientSessionCreator.TARGET_SERVER);
+            if (targetServerAddress != null) {
+                remoteAddress = targetServerAddress.toInetSocketAddress();
+            }
+            boolean verified = verifier.verifyServerKey(this, remoteAddress, 
hostKey);
+            if (log.isDebugEnabled()) {
+                log.debug("checkKeys({}) key={}-{}, verified={}", this, 
KeyUtils.getKeyType(hostKey),
+                        KeyUtils.getFingerPrint(hostKey), verified);
+            }
+            if (!verified) {
+                throw new 
SshException(SshConstants.SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Server key 
did not validate");
+            }
+            sessionHostKeys.add(serializedKey);
         }
     }
 

Reply via email to