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

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

commit b11c1594436ed5e11a7a08bfa85a81d7864d555e
Author: Thomas Wolf <[email protected]>
AuthorDate: Fri Apr 17 22:32:12 2026 +0200

    GH-892: Host certificate principals may contain wildcards
    
    For host certificates, OpenSSH supports * and ? wildcards. So when
    checking against the actual hostname, perform a wildcard match.
---
 CHANGES.md                                             |  4 ++++
 .../java/org/apache/sshd/client/kex/DHGClient.java     | 18 +++++++++++++++++-
 .../common/signature/KnownHostsCertificateTest.java    | 13 +++++++++++++
 3 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index 37edef7b6..1f407b6e1 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -35,6 +35,10 @@
 
 ## New Features
 
+* [GH-892](https://github.com/apache/mina-sshd/issues/892) Align handling 
certificates without principals with OpenSSH 10.3
+
+Wildcard principals in host certificates are handled now.
+
 ## Potential Compatibility Issues
 
 * [GH-892](https://github.com/apache/mina-sshd/issues/892) Align handling 
certificates without principals with OpenSSH 10.3
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index 589676714..d8998ddeb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -24,6 +24,8 @@ import java.security.PublicKey;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.sshd.client.session.AbstractClientSession;
 import org.apache.sshd.common.NamedFactory;
@@ -291,7 +293,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
 
             if (connectSocketAddress instanceof InetSocketAddress) {
                 String hostName = ((InetSocketAddress) 
connectSocketAddress).getHostString();
-                if (GenericUtils.isEmpty(principals) || 
(!principals.contains(hostName))) {
+                if (!hostMatches(principals, hostName)) {
                     throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
                             "KeyExchange signature verification failed, 
invalid principal " + hostName + " for key ID=" + keyId
                                                                                
              + " - allowed=" + principals);
@@ -314,4 +316,18 @@ public class DHGClient extends AbstractDHClientKeyExchange 
{
                                                                                
      + keyId);
         }
     }
+
+    private Pattern wildcardToRegex(String wildcardPattern) {
+        String re = "^\\Q" + wildcardPattern + "\\E$";
+        re = re.replace("?", "\\E.\\Q").replaceAll("\\*+", 
Matcher.quoteReplacement("\\E.*?\\Q")).replace("\\Q\\E", "");
+        return Pattern.compile(re);
+    }
+
+    private boolean hostMatches(Collection<String> principals, String 
hostName) {
+        return principals.stream() //
+                .filter(s -> s != null && !s.isEmpty()) //
+                .anyMatch(principal -> (principal.contains("?") || 
principal.contains("*"))
+                        ? 
wildcardToRegex(principal).matcher(hostName).matches()
+                        : principal.equals(hostName));
+    }
 }
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/signature/KnownHostsCertificateTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/signature/KnownHostsCertificateTest.java
index be4472f72..9e0b6a5db 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/signature/KnownHostsCertificateTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/signature/KnownHostsCertificateTest.java
@@ -49,6 +49,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Tests for KEX with host certificates with host key validation through a 
{@link KnownHostsServerKeyVerifier}.
@@ -186,4 +187,16 @@ class KnownHostsCertificateTest extends BaseTestSupport {
             s.auth().verify(AUTH_TIMEOUT);
         }
     }
+
+    @ParameterizedTest(name = "test CA key with {0}")
+    @ValueSource(strings = { "loca?host,127.0.0.?", "loca*ost,127.?.?.*" })
+    void testHostCertificateWithWildcardSucceeds(String principals) throws 
Exception {
+        initKeys(KeyUtils.EC_ALGORITHM, 256, KeyUtils.EC_ALGORITHM, 256, 
"ecdsa-sha2-nistp256", "cert-authority",
+                principals.split(","));
+        try (ClientSession s = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(CONNECT_TIMEOUT)
+                .getSession()) {
+            s.addPasswordIdentity(getCurrentTestName());
+            s.auth().verify(AUTH_TIMEOUT);
+        }
+    }
 }

Reply via email to