This is an automated email from the ASF dual-hosted git repository. tomaswolf pushed a commit to branch rc-2.20.0 in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 758b100d74e7f6ee5055edc3058bbf9edba1de24 Author: Thomas Wolf <[email protected]> AuthorDate: Thu Aug 20 21:35:49 2026 +0200 Restrict async authentication Allow it only for password or keyboard-interactive authentication. For other authentication schemes, the current design won't work. --- .../sshd/server/session/ServerUserAuthService.java | 20 +++++++-- .../common/auth/PublicKeyAuthenticationTest.java | 48 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java index de51b0a77..53046183e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java @@ -40,6 +40,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractive; +import org.apache.sshd.client.auth.password.UserAuthPassword; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.Service; import org.apache.sshd.common.SshConstants; @@ -185,8 +187,13 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, try { authed = currentAuth.next(buffer); } catch (AsyncAuthException async) { - async.addListener(authenticated -> asyncAuth(cmd, buffer, authenticated)); - return; + String authName = currentAuth.getName(); + if (UserAuthPassword.NAME.equals(authName) || UserAuthKeyboardInteractive.NAME.equals(authName)) { + async.addListener(authenticated -> asyncAuth(cmd, buffer, authenticated)); + return; + } + throw new IllegalStateException( + "Async authentication only allowed for password or keyboard-interactive authentication", async); } catch (Exception e) { // Continue warn("process({}) Failed ({}) to authenticate using current method={}: {}", @@ -331,8 +338,13 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, Boolean authed = currentAuth.auth(session, username, service, buffer); authHolder.set(authed); } catch (AsyncAuthException async) { - async.addListener(authenticated -> asyncAuth(SshConstants.SSH_MSG_USERAUTH_REQUEST, buffer, authenticated)); - return false; + String authName = currentAuth.getName(); + if (UserAuthPassword.NAME.equals(authName) || UserAuthKeyboardInteractive.NAME.equals(authName)) { + async.addListener(authenticated -> asyncAuth(SshConstants.SSH_MSG_USERAUTH_REQUEST, buffer, authenticated)); + return false; + } + throw new IllegalStateException( + "Async authentication only allowed for password or keyboard-interactive authentication", async); } catch (Exception e) { warn("handleUserAuthRequestMessage({}) Failed ({}) to authenticate using factory method={}: {}", session, e.getClass().getSimpleName(), method, e.getMessage(), e); diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java index 7d8a2db04..47b0a02c6 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; @@ -32,8 +33,11 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import org.apache.sshd.certificate.OpenSshCertificateBuilder; @@ -60,6 +64,7 @@ import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.io.resource.URLResource; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.server.auth.AsyncAuthException; import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator; import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator; import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator; @@ -422,6 +427,49 @@ public class PublicKeyAuthenticationTest extends AuthenticationTestSupport { assertEquals(4 /* 3 attempts + null */, attemptsCount.getAndSet(0), "Mismatched retries count"); } + @Test + void testAsyncAuth() throws Exception { + KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256); + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + try { + sshd.setPublickeyAuthenticator((username, key, session) -> { + AsyncAuthException async = new AsyncAuthException(); + scheduler.schedule(() -> async.setAuthed(KeyUtils.compareKeys(clientIdentity.getPublic(), key)), 100, + TimeUnit.MILLISECONDS); + throw async; + }); + try (SshClient client = setupTestClient()) { + client.start(); + AtomicReference<PrivateKey> signingKey = new AtomicReference<>(); + PublicKeyAuthenticationReporter reporter = new PublicKeyAuthenticationReporter() { + @Override + public void signalSignatureAttempt( + ClientSession session, String service, KeyPair identity, + String signature, byte[] signed) throws Exception { + signingKey.set(identity.getPrivate()); + } + }; + ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(CONNECT_TIMEOUT) + .getSession(); + try { + s.addPublicKeyIdentity(clientIdentity); + s.setPublicKeyAuthenticationReporter(reporter); + s.auth().verify(AUTH_TIMEOUT); + assertTrue(KeyUtils.compareKeys(clientIdentity.getPrivate(), signingKey.get())); + } catch (SshException e) { + assertFalse(s.isOpen()); + } finally { + if (s.isOpen()) { + s.close(); + } + client.stop(); + } + } + } finally { + scheduler.shutdownNow(); + } + } + @Test void rsaAuthenticationOldServer() throws Exception { KeyPair userkey = CommonTestSupportUtils.generateKeyPair(KeyUtils.RSA_ALGORITHM, 2048);
