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

Reply via email to