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