Skip to content

Commit b11c159

Browse files
committed
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.
1 parent de65c12 commit b11c159

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535

3636
## New Features
3737

38+
* [GH-892](https://github.com/apache/mina-sshd/issues/892) Align handling certificates without principals with OpenSSH 10.3
39+
40+
Wildcard principals in host certificates are handled now.
41+
3842
## Potential Compatibility Issues
3943

4044
* [GH-892](https://github.com/apache/mina-sshd/issues/892) Align handling certificates without principals with OpenSSH 10.3

sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.util.Arrays;
2525
import java.util.Collection;
2626
import java.util.Objects;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
2729

2830
import org.apache.sshd.client.session.AbstractClientSession;
2931
import org.apache.sshd.common.NamedFactory;
@@ -291,7 +293,7 @@ protected void verifyCertificate(Session session, OpenSshCertificate openSshKey)
291293

292294
if (connectSocketAddress instanceof InetSocketAddress) {
293295
String hostName = ((InetSocketAddress) connectSocketAddress).getHostString();
294-
if (GenericUtils.isEmpty(principals) || (!principals.contains(hostName))) {
296+
if (!hostMatches(principals, hostName)) {
295297
throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
296298
"KeyExchange signature verification failed, invalid principal " + hostName + " for key ID=" + keyId
297299
+ " - allowed=" + principals);
@@ -314,4 +316,18 @@ protected void verifyCertificate(Session session, OpenSshCertificate openSshKey)
314316
+ keyId);
315317
}
316318
}
319+
320+
private Pattern wildcardToRegex(String wildcardPattern) {
321+
String re = "^\\Q" + wildcardPattern + "\\E$";
322+
re = re.replace("?", "\\E.\\Q").replaceAll("\\*+", Matcher.quoteReplacement("\\E.*?\\Q")).replace("\\Q\\E", "");
323+
return Pattern.compile(re);
324+
}
325+
326+
private boolean hostMatches(Collection<String> principals, String hostName) {
327+
return principals.stream() //
328+
.filter(s -> s != null && !s.isEmpty()) //
329+
.anyMatch(principal -> (principal.contains("?") || principal.contains("*"))
330+
? wildcardToRegex(principal).matcher(hostName).matches()
331+
: principal.equals(hostName));
332+
}
317333
}

sshd-core/src/test/java/org/apache/sshd/common/signature/KnownHostsCertificateTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.junit.jupiter.api.io.TempDir;
5050
import org.junit.jupiter.params.ParameterizedTest;
5151
import org.junit.jupiter.params.provider.MethodSource;
52+
import org.junit.jupiter.params.provider.ValueSource;
5253

5354
/**
5455
* Tests for KEX with host certificates with host key validation through a {@link KnownHostsServerKeyVerifier}.
@@ -186,4 +187,16 @@ void testHostCertificateWithoutPrincipalsSucceeds() throws Exception {
186187
s.auth().verify(AUTH_TIMEOUT);
187188
}
188189
}
190+
191+
@ParameterizedTest(name = "test CA key with {0}")
192+
@ValueSource(strings = { "loca?host,127.0.0.?", "loca*ost,127.?.?.*" })
193+
void testHostCertificateWithWildcardSucceeds(String principals) throws Exception {
194+
initKeys(KeyUtils.EC_ALGORITHM, 256, KeyUtils.EC_ALGORITHM, 256, "ecdsa-sha2-nistp256", "cert-authority",
195+
principals.split(","));
196+
try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(CONNECT_TIMEOUT)
197+
.getSession()) {
198+
s.addPasswordIdentity(getCurrentTestName());
199+
s.auth().verify(AUTH_TIMEOUT);
200+
}
201+
}
189202
}

0 commit comments

Comments
 (0)