You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2021/01/02 07:14:51 UTC

[mina-sshd] 12/15: [SSHD-1114] Split AuthenticationTest class into several classes

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

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

commit 6f0513c18876476cc5602f1489597e27d7552fa6
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Jan 1 06:58:35 2021 +0200

    [SSHD-1114] Split AuthenticationTest class into several classes
---
 .../sshd/common/auth/AuthenticationTest.java       | 1046 +-------------------
 .../common/auth/AuthenticationTestSupport.java     |  105 ++
 .../common/auth/HostBasedAuthenticationTest.java   |  150 +++
 .../KeyboardInteractiveAuthenticationTest.java     |  233 +++++
 .../common/auth/PasswordAuthenticationTest.java    |  440 ++++++++
 .../common/auth/PublicKeyAuthenticationTest.java   |  327 ++++++
 .../sshd/util/test/server/TestServerSession.java   |   39 +
 7 files changed, 1295 insertions(+), 1045 deletions(-)

diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
index beb3141..d3aece6 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
@@ -19,556 +19,33 @@
 package org.apache.sshd.common.auth;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
 
 import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.auth.hostbased.HostBasedAuthenticationReporter;
-import org.apache.sshd.client.auth.hostbased.HostKeyIdentityProvider;
-import org.apache.sshd.client.auth.keyboard.UserInteraction;
-import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
-import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
-import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
 import org.apache.sshd.client.future.AuthFuture;
 import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.AttributeRepository;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.kex.KeyExchange;
-import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.session.SessionListener;
-import org.apache.sshd.common.signature.BuiltinSignatures;
-import org.apache.sshd.common.signature.Signature;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
-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.net.SshdSocketAddress;
-import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.core.CoreModuleProperties;
-import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
-import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
-import org.apache.sshd.server.auth.keyboard.InteractiveChallenge;
 import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
-import org.apache.sshd.server.auth.keyboard.PromptEntry;
-import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
 import org.apache.sshd.server.auth.password.PasswordAuthenticator;
-import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
-import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
-import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
-import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.server.session.ServerSessionImpl;
-import org.apache.sshd.server.session.SessionFactory;
-import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.apache.sshd.util.test.CoreTestSupportUtils;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
 
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class AuthenticationTest extends BaseTestSupport {
-
-    private static final AttributeRepository.AttributeKey<Boolean> PASSWORD_ATTR = new AttributeRepository.AttributeKey<>();
-
-    private SshServer sshd;
-    private int port;
-
+public class AuthenticationTest extends AuthenticationTestSupport {
     public AuthenticationTest() {
         super();
     }
 
-    @Before
-    public void setUp() throws Exception {
-        sshd = setupTestServer();
-        sshd.setSessionFactory(new SessionFactory(sshd) {
-            @Override
-            protected ServerSessionImpl doCreateSession(IoSession ioSession) throws Exception {
-                return new TestSession(getServer(), ioSession);
-            }
-        });
-        sshd.start();
-        port = sshd.getPort();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (sshd != null) {
-            sshd.stop(true);
-        }
-    }
-
-    @Test
-    public void testWrongPassword() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            client.start();
-            try (ClientSession s = client.connect("user", TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                s.addPasswordIdentity("bad password");
-                assertAuthenticationResult(getCurrentTestName(), s.auth(), false);
-            }
-        }
-    }
-
-    @Test
-    public void testChangeUser() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                Collection<ClientSession.ClientSessionEvent> mask
-                        = EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH);
-                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(mask, DEFAULT_TIMEOUT);
-                assertFalse("Timeout while waiting on session events",
-                        result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
-                String password = "the-password";
-                for (String username : new String[] { "user1", "user2" }) {
-                    try {
-                        assertAuthenticationResult(username, authPassword(s, username, password), false);
-                    } finally {
-                        s.removePasswordIdentity(password);
-                    }
-                }
-
-                // Note that WAIT_AUTH flag should be false, but since the internal
-                // authentication future is not updated, it's still returned
-                result = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), DEFAULT_TIMEOUT);
-                assertTrue("Mismatched client session close mask: " + result, result.containsAll(mask));
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-196
-    public void testChangePassword() throws Exception {
-        PasswordAuthenticator delegate = sshd.getPasswordAuthenticator();
-        AtomicInteger attemptsCount = new AtomicInteger(0);
-        AtomicInteger changesCount = new AtomicInteger(0);
-        sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
-            @Override
-            public boolean authenticate(String username, String password, ServerSession session) {
-                if (attemptsCount.incrementAndGet() == 1) {
-                    throw new PasswordChangeRequiredException(
-                            attemptsCount.toString(),
-                            getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
-                }
-
-                return delegate.authenticate(username, password, session);
-            }
-
-            @Override
-            public boolean handleClientPasswordChangeRequest(
-                    ServerSession session, String username, String oldPassword, String newPassword) {
-                if (changesCount.incrementAndGet() == 1) {
-                    assertNotEquals("Non-different passwords", oldPassword, newPassword);
-                    return authenticate(username, newPassword, session);
-                } else {
-                    return PasswordAuthenticator.super.handleClientPasswordChangeRequest(
-                            session, username, oldPassword, newPassword);
-                }
-            }
-        });
-        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
-
-        try (SshClient client = setupTestClient()) {
-            AtomicInteger updatesCount = new AtomicInteger(0);
-            client.setUserInteraction(new UserInteraction() {
-                @Override
-                public boolean isInteractionAllowed(ClientSession session) {
-                    return true;
-                }
-
-                @Override
-                public String[] interactive(
-                        ClientSession session, String name, String instruction,
-                        String lang, String[] prompt, boolean[] echo) {
-                    throw new UnsupportedOperationException("Unexpected call");
-                }
-
-                @Override
-                public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
-                    assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
-                    assertEquals("Mismatched language",
-                            CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
-                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
-                    return getCurrentTestName();
-                }
-            });
-
-            AtomicInteger sentCount = new AtomicInteger(0);
-            client.setUserAuthFactories(Collections.singletonList(
-                    new org.apache.sshd.client.auth.password.UserAuthPasswordFactory() {
-                        @Override
-                        public org.apache.sshd.client.auth.password.UserAuthPassword createUserAuth(ClientSession session)
-                                throws IOException {
-                            return new org.apache.sshd.client.auth.password.UserAuthPassword() {
-                                @Override
-                                protected IoWriteFuture sendPassword(
-                                        Buffer buffer, ClientSession session, String oldPassword, String newPassword)
-                                        throws Exception {
-                                    int count = sentCount.incrementAndGet();
-                                    // 1st one is the original one (which is denied by the server)
-                                    // 2nd one is the updated one retrieved from the user interaction
-                                    if (count == 2) {
-                                        return super.sendPassword(buffer, session, getClass().getName(), newPassword);
-                                    } else {
-                                        return super.sendPassword(buffer, session, oldPassword, newPassword);
-                                    }
-                                }
-                            };
-                        }
-                    }));
-            CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
-
-            client.start();
-
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                s.addPasswordIdentity(getCurrentTestName());
-                s.auth().verify(AUTH_TIMEOUT);
-                assertEquals("No password change request generated", 2, attemptsCount.get());
-                assertEquals("No password change handled", 1, changesCount.get());
-                assertEquals("No user interaction invoked", 1, updatesCount.get());
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testAuthPasswordOnly() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
-
-            client.start();
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
-                        EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
-                        DEFAULT_TIMEOUT);
-                assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
-                String password = getCurrentTestName();
-                try {
-                    assertAuthenticationResult(getCurrentTestName(),
-                            authPassword(s, getCurrentTestName(), password), false);
-                } finally {
-                    s.removePasswordIdentity(password);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testAuthKeyPassword() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
-            sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
-                        EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
-                        DEFAULT_TIMEOUT);
-                assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
-                KeyPairProvider provider = createTestHostKeyProvider();
-                KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
-                try {
-                    assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
-                            authPublicKey(s, getCurrentTestName(), pair), false);
-                } finally {
-                    s.removePublicKeyIdentity(pair);
-                }
-
-                String password = getCurrentTestName();
-                try {
-                    assertAuthenticationResult(UserAuthMethodFactory.PASSWORD,
-                            authPassword(s, getCurrentTestName(), password), true);
-                } finally {
-                    s.removePasswordIdentity(password);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-612
-    public void testAuthDefaultKeyInteractive() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
-            sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
-                @Override
-                public InteractiveChallenge generateChallenge(
-                        ServerSession session, String username, String lang, String subMethods)
-                        throws Exception {
-                    assertEquals("Mismatched user language",
-                            CoreModuleProperties.INTERACTIVE_LANGUAGE_TAG.getRequired(client),
-                            lang);
-                    assertEquals("Mismatched client sub-methods",
-                            CoreModuleProperties.INTERACTIVE_SUBMETHODS.getRequired(client),
-                            subMethods);
-
-                    InteractiveChallenge challenge = super.generateChallenge(session, username, lang, subMethods);
-                    assertEquals("Mismatched interaction name", getInteractionName(session), challenge.getInteractionName());
-                    assertEquals("Mismatched interaction instruction", getInteractionInstruction(session),
-                            challenge.getInteractionInstruction());
-                    assertEquals("Mismatched language tag", getInteractionLanguage(session), challenge.getLanguageTag());
-
-                    List<PromptEntry> entries = challenge.getPrompts();
-                    assertEquals("Mismatched prompts count", 1, GenericUtils.size(entries));
-
-                    PromptEntry entry = entries.get(0);
-                    assertEquals("Mismatched prompt", getInteractionPrompt(session), entry.getPrompt());
-                    assertEquals("Mismatched echo", isInteractionPromptEchoEnabled(session), entry.isEcho());
-
-                    return challenge;
-                }
-
-                @Override
-                public boolean authenticate(
-                        ServerSession session, String username, List<String> responses)
-                        throws Exception {
-                    return super.authenticate(session, username, responses);
-                }
-
-            });
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
-                        EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
-                        DEFAULT_TIMEOUT);
-                assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
-                KeyPairProvider provider = createTestHostKeyProvider();
-                KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
-                try {
-                    assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
-                            authPublicKey(s, getCurrentTestName(), pair), false);
-                } finally {
-                    s.removePublicKeyIdentity(pair);
-                }
-
-                try {
-                    assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE,
-                            authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
-                } finally {
-                    s.setUserInteraction(null);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-563
-    public void testAuthMultiChallengeKeyInteractive() throws Exception {
-        Class<?> anchor = getClass();
-        InteractiveChallenge challenge = new InteractiveChallenge();
-        challenge.setInteractionName(getCurrentTestName());
-        challenge.setInteractionInstruction(anchor.getPackage().getName());
-        challenge.setLanguageTag(Locale.getDefault().getLanguage());
-
-        Map<String, String> rspMap = NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER)
-                .put("class", anchor.getSimpleName())
-                .put("package", anchor.getPackage().getName())
-                .put("test", getCurrentTestName())
-                .build();
-        for (String prompt : rspMap.keySet()) {
-            challenge.addPrompt(prompt, (GenericUtils.size(challenge.getPrompts()) & 0x1) != 0);
-        }
-
-        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
-        AtomicInteger genCount = new AtomicInteger(0);
-        AtomicInteger authCount = new AtomicInteger(0);
-        sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() {
-            @Override
-            public InteractiveChallenge generateChallenge(
-                    ServerSession session, String username, String lang, String subMethods)
-                    throws Exception {
-                assertEquals("Unexpected challenge call", 1, genCount.incrementAndGet());
-                return challenge;
-            }
-
-            @Override
-            public boolean authenticate(
-                    ServerSession session, String username, List<String> responses)
-                    throws Exception {
-                assertEquals("Unexpected authenticate call", 1, authCount.incrementAndGet());
-                assertEquals("Mismatched number of responses", GenericUtils.size(rspMap), GenericUtils.size(responses));
-
-                int index = 0;
-                // Cannot use forEach because the index is not effectively final
-                for (Map.Entry<String, String> re : rspMap.entrySet()) {
-                    String prompt = re.getKey();
-                    String expected = re.getValue();
-                    String actual = responses.get(index);
-                    assertEquals("Mismatched response for prompt=" + prompt, expected, actual);
-                    index++;
-                }
-                return true;
-            }
-        });
-        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
-
-        try (SshClient client = setupTestClient()) {
-            AtomicInteger interactiveCount = new AtomicInteger(0);
-            client.setUserInteraction(new UserInteraction() {
-                @Override
-                public boolean isInteractionAllowed(ClientSession session) {
-                    return true;
-                }
-
-                @Override
-                public String[] interactive(
-                        ClientSession session, String name, String instruction,
-                        String lang, String[] prompt, boolean[] echo) {
-                    assertEquals("Unexpected multiple calls", 1, interactiveCount.incrementAndGet());
-                    assertEquals("Mismatched name", challenge.getInteractionName(), name);
-                    assertEquals("Mismatched instruction", challenge.getInteractionInstruction(), instruction);
-                    assertEquals("Mismatched language", challenge.getLanguageTag(), lang);
-
-                    List<PromptEntry> entries = challenge.getPrompts();
-                    assertEquals("Mismatched prompts count", GenericUtils.size(entries), GenericUtils.length(prompt));
-
-                    String[] responses = new String[prompt.length];
-                    for (int index = 0; index < prompt.length; index++) {
-                        PromptEntry e = entries.get(index);
-                        String key = e.getPrompt();
-                        assertEquals("Mismatched prompt at index=" + index, key, prompt[index]);
-                        assertEquals("Mismatched echo at index=" + index, e.isEcho(), echo[index]);
-                        responses[index] = ValidateUtils.checkNotNull(rspMap.get(key), "No value for prompt=%s", key);
-                    }
-
-                    return responses;
-                }
-
-                @Override
-                public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
-                    throw new UnsupportedOperationException("Unexpected call");
-                }
-            });
-            CoreModuleProperties.AUTH_METHODS.set(client, UserAuthKeyboardInteractiveFactory.NAME);
-
-            client.start();
-
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                s.auth().verify(AUTH_TIMEOUT);
-                assertEquals("Bad generated challenge count", 1, genCount.get());
-                assertEquals("Bad authentication count", 1, authCount.get());
-                assertEquals("Bad interactive count", 1, interactiveCount.get());
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-196
-    public void testAuthPasswordChangeRequest() throws Exception {
-        PasswordAuthenticator delegate = Objects.requireNonNull(sshd.getPasswordAuthenticator(), "No password authenticator");
-        AtomicInteger attemptsCount = new AtomicInteger(0);
-        sshd.setPasswordAuthenticator((username, password, session) -> {
-            if (attemptsCount.incrementAndGet() == 1) {
-                throw new PasswordChangeRequiredException(
-                        attemptsCount.toString(),
-                        getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
-            }
-
-            return delegate.authenticate(username, password, session);
-        });
-        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
-
-        try (SshClient client = setupTestClient()) {
-            AtomicInteger updatesCount = new AtomicInteger(0);
-            client.setUserInteraction(new UserInteraction() {
-                @Override
-                public boolean isInteractionAllowed(ClientSession session) {
-                    return true;
-                }
-
-                @Override
-                public String[] interactive(
-                        ClientSession session, String name, String instruction,
-                        String lang, String[] prompt, boolean[] echo) {
-                    throw new UnsupportedOperationException("Unexpected call");
-                }
-
-                @Override
-                public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
-                    assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
-                    assertEquals("Mismatched language",
-                            CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
-                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
-                    return getCurrentTestName();
-                }
-            });
-            CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
-
-            client.start();
-
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                s.addPasswordIdentity(getCurrentTestName());
-                s.auth().verify(AUTH_TIMEOUT);
-                assertEquals("No password change request generated", 2, attemptsCount.get());
-                assertEquals("No user interaction invoked", 1, updatesCount.get());
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
     @Test // see SSHD-600
     public void testAuthExceptionPropagation() throws Exception {
         try (SshClient client = setupTestClient()) {
@@ -619,257 +96,6 @@ public class AuthenticationTest extends BaseTestSupport {
         }
     }
 
-    @Test
-    public void testPasswordIdentityProviderPropagation() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            List<String> passwords = Collections.singletonList(getCurrentTestName());
-            AtomicInteger loadCount = new AtomicInteger(0);
-            PasswordIdentityProvider provider = () -> {
-                loadCount.incrementAndGet();
-                outputDebugMessage("loadPasswords - count=%s", loadCount);
-                return passwords;
-            };
-            client.setPasswordIdentityProvider(provider);
-
-            client.start();
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                s.auth().verify(AUTH_TIMEOUT);
-                assertEquals("Mismatched load passwords count", 1, loadCount.get());
-                assertSame("Mismatched passwords identity provider", provider, s.getPasswordIdentityProvider());
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-618
-    public void testPublicKeyAuthDifferentThanKex() throws Exception {
-        KeyPairProvider serverKeys = KeyPairProvider.wrap(
-                CommonTestSupportUtils.generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024),
-                CommonTestSupportUtils.generateKeyPair(KeyUtils.DSS_ALGORITHM, 512),
-                CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256));
-        sshd.setKeyPairProvider(serverKeys);
-        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
-
-        KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
-        sshd.setPublickeyAuthenticator((username, key, session) -> {
-            String keyType = KeyUtils.getKeyType(key);
-            String expType = KeyUtils.getKeyType(clientIdentity);
-            assertEquals("Mismatched client key types", expType, keyType);
-            assertKeyEquals("Mismatched authentication public keys", clientIdentity.getPublic(), key);
-            return true;
-        });
-
-        // since we need to use RSA
-        CoreTestSupportUtils.setupFullSignaturesSupport(sshd);
-        try (SshClient client = setupTestClient()) {
-            // force server to use only RSA
-            NamedFactory<Signature> kexSignature = BuiltinSignatures.rsa;
-            client.setSignatureFactories(Collections.singletonList(kexSignature));
-            client.setServerKeyVerifier((sshClientSession, remoteAddress, serverKey) -> {
-                String keyType = KeyUtils.getKeyType(serverKey);
-                String expType = kexSignature.getName();
-                assertEquals("Mismatched server key type", expType, keyType);
-
-                KeyPair kp;
-                try {
-                    kp = ValidateUtils.checkNotNull(serverKeys.loadKey(null, keyType), "No server key for type=%s", keyType);
-                } catch (IOException | GeneralSecurityException e) {
-                    throw new RuntimeException(
-                            "Unexpected " + e.getClass().getSimpleName() + ")"
-                                               + " keys loading exception: " + e.getMessage(),
-                            e);
-                }
-                assertKeyEquals("Mismatched server public keys", kp.getPublic(), serverKey);
-                return true;
-            });
-
-            // allow only EC keys for public key authentication
-            org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory factory
-                    = new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory();
-            factory.setSignatureFactories(
-                    Arrays.asList(
-                            BuiltinSignatures.nistp256, BuiltinSignatures.nistp384, BuiltinSignatures.nistp521));
-            client.setUserAuthFactories(Collections.singletonList(factory));
-
-            client.start();
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                s.addPublicKeyIdentity(clientIdentity);
-                s.auth().verify(AUTH_TIMEOUT);
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-624
-    public void testMismatchedUserAuthPkOkData() throws Exception {
-        AtomicInteger challengeCounter = new AtomicInteger(0);
-        sshd.setUserAuthFactories(Collections.singletonList(
-                new org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory() {
-                    @Override
-                    public org.apache.sshd.server.auth.pubkey.UserAuthPublicKey createUserAuth(ServerSession session)
-                            throws IOException {
-                        return new org.apache.sshd.server.auth.pubkey.UserAuthPublicKey() {
-                            @Override
-                            protected void sendPublicKeyResponse(
-                                    ServerSession session, String username, String alg, PublicKey key,
-                                    byte[] keyBlob, int offset, int blobLen, Buffer buffer)
-                                    throws Exception {
-                                int count = challengeCounter.incrementAndGet();
-                                outputDebugMessage("sendPublicKeyChallenge(%s)[%s]: count=%d", session, alg, count);
-                                if (count == 1) {
-                                    // send wrong key type
-                                    super.sendPublicKeyResponse(session, username,
-                                            KeyPairProvider.SSH_DSS, key, keyBlob, offset, blobLen, buffer);
-                                } else if (count == 2) {
-                                    // send another key
-                                    KeyPair otherPair = org.apache.sshd.util.test.CommonTestSupportUtils
-                                            .generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024);
-                                    PublicKey otherKey = otherPair.getPublic();
-                                    Buffer buf = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_PK_OK,
-                                            blobLen + alg.length() + Long.SIZE);
-                                    buf.putString(alg);
-                                    buf.putPublicKey(otherKey);
-                                    session.writePacket(buf);
-                                } else {
-                                    super.sendPublicKeyResponse(session, username, alg, key, keyBlob, offset, blobLen, buffer);
-                                }
-                            }
-                        };
-                    }
-
-                }));
-
-        try (SshClient client = setupTestClient()) {
-            KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(
-                    CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
-                    CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
-            client.start();
-
-            try {
-                for (int index = 1; index <= 4; index++) {
-                    try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                            .verify(CONNECT_TIMEOUT)
-                            .getSession()) {
-                        s.addPublicKeyIdentity(clientIdentity);
-                        s.auth().verify(AUTH_TIMEOUT);
-                        assertEquals("Mismatched number of challenges", 3, challengeCounter.get());
-                        break;
-                    } catch (SshException e) { // expected
-                        outputDebugMessage("%s on retry #%d: %s", e.getClass().getSimpleName(), index, e.getMessage());
-
-                        Throwable t = e.getCause();
-                        assertObjectInstanceOf("Unexpected failure cause at retry #" + index, InvalidKeySpecException.class, t);
-                    }
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-620
-    public void testHostBasedAuthentication() throws Exception {
-        AtomicInteger invocationCount = new AtomicInteger(0);
-        testHostBasedAuthentication(
-                (
-                        session, username, clientHostKey, clientHostName, clientUsername,
-                        certificates) -> invocationCount.incrementAndGet() > 0,
-                session -> {
-                    /* ignored */ });
-        assertEquals("Mismatched authenticator invocation count", 1, invocationCount.get());
-    }
-
-    @Test   // see SSHD-1114
-    public void testHostBasedAuthenticationReporter() throws Exception {
-        AtomicReference<String> hostnameClientHolder = new AtomicReference<>();
-        AtomicReference<String> usernameClientHolder = new AtomicReference<>();
-        AtomicReference<PublicKey> keyClientHolder = new AtomicReference<>();
-        HostBasedAuthenticator authenticator
-                = (session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
-                    return Objects.equals(clientHostName, hostnameClientHolder.get())
-                            && Objects.equals(clientUsername, usernameClientHolder.get())
-                            && KeyUtils.compareKeys(clientHostKey, keyClientHolder.get());
-                };
-
-        HostBasedAuthenticationReporter reporter = new HostBasedAuthenticationReporter() {
-            @Override
-            public void signalAuthenticationAttempt(
-                    ClientSession session, String service, KeyPair identity, String hostname, String username, byte[] signature)
-                    throws Exception {
-                hostnameClientHolder.set(hostname);
-                usernameClientHolder.set(username);
-                keyClientHolder.set(identity.getPublic());
-            }
-
-            @Override
-            public void signalAuthenticationSuccess(
-                    ClientSession session, String service, KeyPair identity, String hostname, String username)
-                    throws Exception {
-                assertEquals("Host", hostname, hostnameClientHolder.get());
-                assertEquals("User", username, usernameClientHolder.get());
-                assertKeyEquals("Identity", identity.getPublic(), keyClientHolder.get());
-            }
-
-            @Override
-            public void signalAuthenticationFailure(
-                    ClientSession session, String service, KeyPair identity,
-                    String hostname, String username, boolean partial, List<String> serverMethods)
-                    throws Exception {
-                fail("Unexpected failure signalled");
-            }
-        };
-
-        testHostBasedAuthentication(authenticator, session -> session.setHostBasedAuthenticationReporter(reporter));
-    }
-
-    private void testHostBasedAuthentication(
-            HostBasedAuthenticator delegate, Consumer<? super ClientSession> preAuthInitializer)
-            throws Exception {
-        String hostClientUser = getClass().getSimpleName();
-        String hostClientName = SshdSocketAddress.toAddressString(SshdSocketAddress.getFirstExternalNetwork4Address());
-        KeyPair hostClientKey = CommonTestSupportUtils.generateKeyPair(
-                CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
-                CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
-        sshd.setHostBasedAuthenticator((session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
-            return hostClientUser.equals(clientUsername)
-                    && hostClientName.equals(clientHostName)
-                    && KeyUtils.compareKeys(hostClientKey.getPublic(), clientHostKey)
-                    && delegate.authenticate(session, username, clientHostKey, clientHostName, clientUsername, certificates);
-        });
-        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
-        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-        sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
-        sshd.setUserAuthFactories(
-                Collections.singletonList(
-                        org.apache.sshd.server.auth.hostbased.UserAuthHostBasedFactory.INSTANCE));
-
-        try (SshClient client = setupTestClient()) {
-            org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory factory
-                    = new org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory();
-            // TODO factory.setClientHostname(CLIENT_HOSTNAME);
-            factory.setClientUsername(hostClientUser);
-            factory.setClientHostKeys(HostKeyIdentityProvider.wrap(hostClientKey));
-
-            client.setUserAuthFactories(Collections.singletonList(factory));
-            client.start();
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                preAuthInitializer.accept(session);
-                session.auth().verify(AUTH_TIMEOUT);
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
     @Test // see SSHD-625
     public void testRuntimeErrorsInAuthenticators() throws Exception {
         Error thrown = new OutOfMemoryError(getCurrentTestName());
@@ -925,97 +151,6 @@ public class AuthenticationTest extends BaseTestSupport {
         }
     }
 
-    @Test // see SSHD-714
-    public void testPasswordIdentityWithSpacesPrefixOrSuffix() throws Exception {
-        sshd.setPasswordAuthenticator((username, password, session) -> {
-            return (username != null) && (!username.trim().isEmpty())
-                    && (password != null) && (!password.isEmpty())
-                    && ((password.charAt(0) == ' ') || (password.charAt(password.length() - 1) == ' '));
-        });
-
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            try {
-                for (String password : new String[] {
-                        " ", "    ", "  " + getCurrentTestName(), getCurrentTestName() + "    "
-                }) {
-                    try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                            .verify(CONNECT_TIMEOUT)
-                            .getSession()) {
-                        s.addPasswordIdentity(password);
-
-                        AuthFuture auth = s.auth();
-                        assertTrue("No authentication result in time for password='" + password + "'",
-                                auth.await(AUTH_TIMEOUT));
-                        assertTrue("Failed to authenticate with password='" + password + "'", auth.isSuccess());
-                    }
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test // see SSHD-862
-    public void testSessionContextPropagatedToKeyFilePasswordProvider() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
-                String keyLocation = "super-secret-passphrase-ec256-key";
-                FilePasswordProvider passwordProvider = new FilePasswordProvider() {
-                    @Override
-                    @SuppressWarnings("synthetic-access")
-                    public String getPassword(
-                            SessionContext session, NamedResource resourceKey, int retryIndex)
-                            throws IOException {
-                        assertSame("Mismatched session context", s, session);
-                        assertEquals("Mismatched retry index", 0, retryIndex);
-
-                        String name = resourceKey.getName();
-                        int pos = name.lastIndexOf('/');
-                        if (pos >= 0) {
-                            name = name.substring(pos + 1);
-                        }
-                        assertEquals("Mismatched location", keyLocation, name);
-
-                        Boolean passwordRequested = session.getAttribute(PASSWORD_ATTR);
-                        assertNull("Password already requested", passwordRequested);
-                        session.setAttribute(PASSWORD_ATTR, Boolean.TRUE);
-                        return "super secret passphrase";
-                    }
-                };
-                s.setKeyIdentityProvider(new KeyIdentityProvider() {
-                    @Override
-                    public Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException {
-                        assertSame("Mismatched session context", s, session);
-                        URL location = getClass().getResource(keyLocation);
-                        assertNotNull("Missing key file " + keyLocation, location);
-
-                        URLResource resourceKey = new URLResource(location);
-                        Iterable<KeyPair> ids;
-                        try (InputStream keyData = resourceKey.openInputStream()) {
-                            ids = SecurityUtils.loadKeyPairIdentities(session, resourceKey, keyData, passwordProvider);
-                        }
-                        KeyPair kp = GenericUtils.head(ids);
-                        assertNotNull("No identity loaded from " + resourceKey, kp);
-                        return Collections.singletonList(kp);
-                    }
-                });
-                s.auth().verify(AUTH_TIMEOUT);
-
-                Boolean passwordRequested = s.getAttribute(PASSWORD_ATTR);
-                assertNotNull("Password provider not invoked", passwordRequested);
-                assertTrue("Password not requested", passwordRequested.booleanValue());
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
     @Test   // see SSHD-1040
     public void testServerKeyAvailableAfterAuth() throws Exception {
         KeyPairProvider keyPairProvider = sshd.getKeyPairProvider();
@@ -1050,183 +185,4 @@ public class AuthenticationTest extends BaseTestSupport {
 
         fail("No matching server key found for " + actualKey);
     }
-
-    @Test   // see SSHD-1114
-    public void testPasswordAuthenticationReporter() throws Exception {
-        String goodPassword = getCurrentTestName();
-        String badPassword = getClass().getSimpleName();
-        List<String> attempted = new ArrayList<>();
-        sshd.setPasswordAuthenticator((user, password, session) -> {
-            attempted.add(password);
-            return goodPassword.equals(password);
-        });
-        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-        sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
-
-        List<String> reported = new ArrayList<>();
-        PasswordAuthenticationReporter reporter = new PasswordAuthenticationReporter() {
-            @Override
-            public void signalAuthenticationAttempt(
-                    ClientSession session, String service, String oldPassword, boolean modified, String newPassword)
-                    throws Exception {
-                reported.add(oldPassword);
-            }
-
-            @Override
-            public void signalAuthenticationSuccess(ClientSession session, String service, String password)
-                    throws Exception {
-                assertEquals("Mismatched succesful password", goodPassword, password);
-            }
-
-            @Override
-            public void signalAuthenticationFailure(
-                    ClientSession session, String service, String password, boolean partial, List<String> serverMethods)
-                    throws Exception {
-                assertEquals("Mismatched failed password", badPassword, password);
-            }
-        };
-
-        try (SshClient client = setupTestClient()) {
-            client.setUserAuthFactories(
-                    Collections.singletonList(new org.apache.sshd.client.auth.password.UserAuthPasswordFactory()));
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT).getSession()) {
-                session.addPasswordIdentity(badPassword);
-                session.addPasswordIdentity(goodPassword);
-                session.setPasswordAuthenticationReporter(reporter);
-                session.auth().verify(AUTH_TIMEOUT);
-            } finally {
-                client.stop();
-            }
-        }
-
-        List<String> expected = Arrays.asList(badPassword, goodPassword);
-        assertListEquals("Attempted passwords", expected, attempted);
-        assertListEquals("Reported passwords", expected, reported);
-    }
-
-    @Test   // see SSHD-1114
-    public void testPublicKeyAuthenticationReporter() throws Exception {
-        KeyPair goodIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
-        KeyPair badIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
-        List<PublicKey> attempted = new ArrayList<>();
-        sshd.setPublickeyAuthenticator((username, key, session) -> {
-            attempted.add(key);
-            return KeyUtils.compareKeys(goodIdentity.getPublic(), key);
-        });
-        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
-        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-
-        List<PublicKey> reported = new ArrayList<>();
-        List<PublicKey> signed = new ArrayList<>();
-        PublicKeyAuthenticationReporter reporter = new PublicKeyAuthenticationReporter() {
-            @Override
-            public void signalAuthenticationAttempt(
-                    ClientSession session, String service, KeyPair identity, String signature)
-                    throws Exception {
-                reported.add(identity.getPublic());
-            }
-
-            @Override
-            public void signalSignatureAttempt(
-                    ClientSession session, String service, KeyPair identity, String signature, byte[] sigData)
-                    throws Exception {
-                signed.add(identity.getPublic());
-            }
-
-            @Override
-            public void signalAuthenticationSuccess(ClientSession session, String service, KeyPair identity)
-                    throws Exception {
-                assertTrue("Mismatched success identity", KeyUtils.compareKeys(goodIdentity.getPublic(), identity.getPublic()));
-            }
-
-            @Override
-            public void signalAuthenticationFailure(
-                    ClientSession session, String service, KeyPair identity, boolean partial, List<String> serverMethods)
-                    throws Exception {
-                assertTrue("Mismatched failed identity", KeyUtils.compareKeys(badIdentity.getPublic(), identity.getPublic()));
-            }
-        };
-
-        try (SshClient client = setupTestClient()) {
-            client.setUserAuthFactories(
-                    Collections.singletonList(new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT).getSession()) {
-                session.addPublicKeyIdentity(badIdentity);
-                session.addPublicKeyIdentity(goodIdentity);
-                session.setPublicKeyAuthenticationReporter(reporter);
-                session.auth().verify(AUTH_TIMEOUT);
-            } finally {
-                client.stop();
-            }
-        }
-
-        List<PublicKey> expected = Arrays.asList(badIdentity.getPublic(), goodIdentity.getPublic());
-        // The server public key authenticator is called twice with the good identity
-        int numAttempted = attempted.size();
-        assertKeyListEquals("Attempted", expected, (numAttempted > 0) ? attempted.subList(0, numAttempted - 1) : attempted);
-        assertKeyListEquals("Reported", expected, reported);
-        // The signing is attempted only if the initial public key is accepted
-        assertKeyListEquals("Signed", Collections.singletonList(goodIdentity.getPublic()), signed);
-    }
-
-    private static void assertAuthenticationResult(String message, AuthFuture future, boolean expected) throws IOException {
-        assertTrue(message + ": failed to get result on time", future.await(AUTH_TIMEOUT));
-        assertEquals(message + ": mismatched authentication result", expected, future.isSuccess());
-    }
-
-    private static AuthFuture authPassword(ClientSession s, String user, String pswd) throws IOException {
-        s.setUsername(user);
-        s.addPasswordIdentity(pswd);
-        return s.auth();
-    }
-
-    private static AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
-        s.setUsername(user);
-        final String[] response = { pswd };
-        s.setUserInteraction(new UserInteraction() {
-            @Override
-            public boolean isInteractionAllowed(ClientSession session) {
-                return true;
-            }
-
-            @Override
-            public String[] interactive(
-                    ClientSession session, String name, String instruction,
-                    String lang, String[] prompt, boolean[] echo) {
-                assertSame("Mismatched session instance", s, session);
-                assertEquals("Mismatched prompt size", 1, GenericUtils.length(prompt));
-                assertTrue("Mismatched prompt: " + prompt[0], prompt[0].toLowerCase().contains("password"));
-                return response;
-            }
-
-            @Override
-            public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
-                throw new UnsupportedOperationException("Unexpected password update request");
-            }
-        });
-        return s.auth();
-    }
-
-    private static AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
-        s.setUsername(user);
-        s.addPublicKeyIdentity(pair);
-        return s.auth();
-    }
-
-    public static class TestSession extends ServerSessionImpl {
-        public TestSession(ServerFactoryManager server, IoSession ioSession) throws Exception {
-            super(server, ioSession);
-        }
-
-        @Override
-        public void handleMessage(Buffer buffer) throws Exception {
-            super.handleMessage(buffer); // debug breakpoint
-        }
-    }
 }
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTestSupport.java
new file mode 100644
index 0000000..3fab5de
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTestSupport.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.io.IOException;
+import java.security.KeyPair;
+
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.AttributeRepository;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AuthenticationTestSupport extends BaseTestSupport {
+    protected static final AttributeRepository.AttributeKey<Boolean> PASSWORD_ATTR = new AttributeRepository.AttributeKey<>();
+
+    protected SshServer sshd;
+    protected int port;
+
+    protected AuthenticationTestSupport() {
+        super();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = setupTestServer();
+        sshd.start();
+        port = sshd.getPort();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (sshd != null) {
+            sshd.stop(true);
+        }
+    }
+
+    protected static void assertAuthenticationResult(String message, AuthFuture future, boolean expected) throws IOException {
+        assertTrue(message + ": failed to get result on time", future.await(AUTH_TIMEOUT));
+        assertEquals(message + ": mismatched authentication result", expected, future.isSuccess());
+    }
+
+    protected static AuthFuture authPassword(ClientSession s, String user, String pswd) throws IOException {
+        s.setUsername(user);
+        s.addPasswordIdentity(pswd);
+        return s.auth();
+    }
+
+    protected static AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
+        s.setUsername(user);
+        final String[] response = { pswd };
+        s.setUserInteraction(new UserInteraction() {
+            @Override
+            public boolean isInteractionAllowed(ClientSession session) {
+                return true;
+            }
+
+            @Override
+            public String[] interactive(
+                    ClientSession session, String name, String instruction,
+                    String lang, String[] prompt, boolean[] echo) {
+                assertSame("Mismatched session instance", s, session);
+                assertEquals("Mismatched prompt size", 1, GenericUtils.length(prompt));
+                assertTrue("Mismatched prompt: " + prompt[0], prompt[0].toLowerCase().contains("password"));
+                return response;
+            }
+
+            @Override
+            public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+                throw new UnsupportedOperationException("Unexpected password update request");
+            }
+        });
+        return s.auth();
+    }
+
+    protected static AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
+        s.setUsername(user);
+        s.addPublicKeyIdentity(pair);
+        return s.auth();
+    }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/HostBasedAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/HostBasedAuthenticationTest.java
new file mode 100644
index 0000000..7568bc8
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/HostBasedAuthenticationTest.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.hostbased.HostBasedAuthenticationReporter;
+import org.apache.sshd.client.auth.hostbased.HostKeyIdentityProvider;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class HostBasedAuthenticationTest extends AuthenticationTestSupport {
+    public HostBasedAuthenticationTest() {
+        super();
+    }
+
+    @Test // see SSHD-620
+    public void testHostBasedAuthentication() throws Exception {
+        AtomicInteger invocationCount = new AtomicInteger(0);
+        testHostBasedAuthentication(
+                (
+                        session, username, clientHostKey, clientHostName, clientUsername,
+                        certificates) -> invocationCount.incrementAndGet() > 0,
+                session -> {
+                    /* ignored */ });
+        assertEquals("Mismatched authenticator invocation count", 1, invocationCount.get());
+    }
+
+    @Test   // see SSHD-1114
+    public void testHostBasedAuthenticationReporter() throws Exception {
+        AtomicReference<String> hostnameClientHolder = new AtomicReference<>();
+        AtomicReference<String> usernameClientHolder = new AtomicReference<>();
+        AtomicReference<PublicKey> keyClientHolder = new AtomicReference<>();
+        HostBasedAuthenticator authenticator
+                = (session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
+                    return Objects.equals(clientHostName, hostnameClientHolder.get())
+                            && Objects.equals(clientUsername, usernameClientHolder.get())
+                            && KeyUtils.compareKeys(clientHostKey, keyClientHolder.get());
+                };
+
+        HostBasedAuthenticationReporter reporter = new HostBasedAuthenticationReporter() {
+            @Override
+            public void signalAuthenticationAttempt(
+                    ClientSession session, String service, KeyPair identity, String hostname, String username, byte[] signature)
+                    throws Exception {
+                hostnameClientHolder.set(hostname);
+                usernameClientHolder.set(username);
+                keyClientHolder.set(identity.getPublic());
+            }
+
+            @Override
+            public void signalAuthenticationSuccess(
+                    ClientSession session, String service, KeyPair identity, String hostname, String username)
+                    throws Exception {
+                assertEquals("Host", hostname, hostnameClientHolder.get());
+                assertEquals("User", username, usernameClientHolder.get());
+                assertKeyEquals("Identity", identity.getPublic(), keyClientHolder.get());
+            }
+
+            @Override
+            public void signalAuthenticationFailure(
+                    ClientSession session, String service, KeyPair identity,
+                    String hostname, String username, boolean partial, List<String> serverMethods)
+                    throws Exception {
+                fail("Unexpected failure signalled");
+            }
+        };
+
+        testHostBasedAuthentication(authenticator, session -> session.setHostBasedAuthenticationReporter(reporter));
+    }
+
+    private void testHostBasedAuthentication(
+            HostBasedAuthenticator delegate, Consumer<? super ClientSession> preAuthInitializer)
+            throws Exception {
+        String hostClientUser = getClass().getSimpleName();
+        String hostClientName = SshdSocketAddress.toAddressString(SshdSocketAddress.getFirstExternalNetwork4Address());
+        KeyPair hostClientKey = CommonTestSupportUtils.generateKeyPair(
+                CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
+                CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
+        sshd.setHostBasedAuthenticator((session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
+            return hostClientUser.equals(clientUsername)
+                    && hostClientName.equals(clientHostName)
+                    && KeyUtils.compareKeys(hostClientKey.getPublic(), clientHostKey)
+                    && delegate.authenticate(session, username, clientHostKey, clientHostName, clientUsername, certificates);
+        });
+        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+        sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+        sshd.setUserAuthFactories(
+                Collections.singletonList(
+                        org.apache.sshd.server.auth.hostbased.UserAuthHostBasedFactory.INSTANCE));
+
+        try (SshClient client = setupTestClient()) {
+            org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory factory
+                    = new org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory();
+            // TODO factory.setClientHostname(CLIENT_HOSTNAME);
+            factory.setClientUsername(hostClientUser);
+            factory.setClientHostKeys(HostKeyIdentityProvider.wrap(hostClientKey));
+
+            client.setUserAuthFactories(Collections.singletonList(factory));
+            client.start();
+            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                preAuthInitializer.accept(session);
+                session.auth().verify(AUTH_TIMEOUT);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
new file mode 100644
index 0000000..feeabf3
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.keyboard.InteractiveChallenge;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.keyboard.PromptEntry;
+import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class KeyboardInteractiveAuthenticationTest extends AuthenticationTestSupport {
+    public KeyboardInteractiveAuthenticationTest() {
+        super();
+    }
+
+    @Test // see SSHD-612
+    public void testAuthDefaultKeyInteractive() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+            sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
+                @Override
+                public InteractiveChallenge generateChallenge(
+                        ServerSession session, String username, String lang, String subMethods)
+                        throws Exception {
+                    assertEquals("Mismatched user language",
+                            CoreModuleProperties.INTERACTIVE_LANGUAGE_TAG.getRequired(client),
+                            lang);
+                    assertEquals("Mismatched client sub-methods",
+                            CoreModuleProperties.INTERACTIVE_SUBMETHODS.getRequired(client),
+                            subMethods);
+
+                    InteractiveChallenge challenge = super.generateChallenge(session, username, lang, subMethods);
+                    assertEquals("Mismatched interaction name", getInteractionName(session), challenge.getInteractionName());
+                    assertEquals("Mismatched interaction instruction", getInteractionInstruction(session),
+                            challenge.getInteractionInstruction());
+                    assertEquals("Mismatched language tag", getInteractionLanguage(session), challenge.getLanguageTag());
+
+                    List<PromptEntry> entries = challenge.getPrompts();
+                    assertEquals("Mismatched prompts count", 1, GenericUtils.size(entries));
+
+                    PromptEntry entry = entries.get(0);
+                    assertEquals("Mismatched prompt", getInteractionPrompt(session), entry.getPrompt());
+                    assertEquals("Mismatched echo", isInteractionPromptEchoEnabled(session), entry.isEcho());
+
+                    return challenge;
+                }
+
+                @Override
+                public boolean authenticate(
+                        ServerSession session, String username, List<String> responses)
+                        throws Exception {
+                    return super.authenticate(session, username, responses);
+                }
+
+            });
+            client.start();
+
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
+                        EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
+                        DEFAULT_TIMEOUT);
+                assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+                KeyPairProvider provider = createTestHostKeyProvider();
+                KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
+                            authPublicKey(s, getCurrentTestName(), pair), false);
+                } finally {
+                    s.removePublicKeyIdentity(pair);
+                }
+
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE,
+                            authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
+                } finally {
+                    s.setUserInteraction(null);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test // see SSHD-563
+    public void testAuthMultiChallengeKeyInteractive() throws Exception {
+        Class<?> anchor = getClass();
+        InteractiveChallenge challenge = new InteractiveChallenge();
+        challenge.setInteractionName(getCurrentTestName());
+        challenge.setInteractionInstruction(anchor.getPackage().getName());
+        challenge.setLanguageTag(Locale.getDefault().getLanguage());
+
+        Map<String, String> rspMap = NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER)
+                .put("class", anchor.getSimpleName())
+                .put("package", anchor.getPackage().getName())
+                .put("test", getCurrentTestName())
+                .build();
+        for (String prompt : rspMap.keySet()) {
+            challenge.addPrompt(prompt, (GenericUtils.size(challenge.getPrompts()) & 0x1) != 0);
+        }
+
+        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
+        AtomicInteger genCount = new AtomicInteger(0);
+        AtomicInteger authCount = new AtomicInteger(0);
+        sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() {
+            @Override
+            public InteractiveChallenge generateChallenge(
+                    ServerSession session, String username, String lang, String subMethods)
+                    throws Exception {
+                assertEquals("Unexpected challenge call", 1, genCount.incrementAndGet());
+                return challenge;
+            }
+
+            @Override
+            public boolean authenticate(
+                    ServerSession session, String username, List<String> responses)
+                    throws Exception {
+                assertEquals("Unexpected authenticate call", 1, authCount.incrementAndGet());
+                assertEquals("Mismatched number of responses", GenericUtils.size(rspMap), GenericUtils.size(responses));
+
+                int index = 0;
+                // Cannot use forEach because the index is not effectively final
+                for (Map.Entry<String, String> re : rspMap.entrySet()) {
+                    String prompt = re.getKey();
+                    String expected = re.getValue();
+                    String actual = responses.get(index);
+                    assertEquals("Mismatched response for prompt=" + prompt, expected, actual);
+                    index++;
+                }
+                return true;
+            }
+        });
+        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
+
+        try (SshClient client = setupTestClient()) {
+            AtomicInteger interactiveCount = new AtomicInteger(0);
+            client.setUserInteraction(new UserInteraction() {
+                @Override
+                public boolean isInteractionAllowed(ClientSession session) {
+                    return true;
+                }
+
+                @Override
+                public String[] interactive(
+                        ClientSession session, String name, String instruction,
+                        String lang, String[] prompt, boolean[] echo) {
+                    assertEquals("Unexpected multiple calls", 1, interactiveCount.incrementAndGet());
+                    assertEquals("Mismatched name", challenge.getInteractionName(), name);
+                    assertEquals("Mismatched instruction", challenge.getInteractionInstruction(), instruction);
+                    assertEquals("Mismatched language", challenge.getLanguageTag(), lang);
+
+                    List<PromptEntry> entries = challenge.getPrompts();
+                    assertEquals("Mismatched prompts count", GenericUtils.size(entries), GenericUtils.length(prompt));
+
+                    String[] responses = new String[prompt.length];
+                    for (int index = 0; index < prompt.length; index++) {
+                        PromptEntry e = entries.get(index);
+                        String key = e.getPrompt();
+                        assertEquals("Mismatched prompt at index=" + index, key, prompt[index]);
+                        assertEquals("Mismatched echo at index=" + index, e.isEcho(), echo[index]);
+                        responses[index] = ValidateUtils.checkNotNull(rspMap.get(key), "No value for prompt=%s", key);
+                    }
+
+                    return responses;
+                }
+
+                @Override
+                public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+                    throw new UnsupportedOperationException("Unexpected call");
+                }
+            });
+            CoreModuleProperties.AUTH_METHODS.set(client, UserAuthKeyboardInteractiveFactory.NAME);
+
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                s.auth().verify(AUTH_TIMEOUT);
+                assertEquals("Bad generated challenge count", 1, genCount.get());
+                assertEquals("Bad authentication count", 1, authCount.get());
+                assertEquals("Bad interactive count", 1, interactiveCount.get());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java
new file mode 100644
index 0000000..628338d
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java
@@ -0,0 +1,440 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
+import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class PasswordAuthenticationTest extends AuthenticationTestSupport {
+    public PasswordAuthenticationTest() {
+        super();
+    }
+
+    @Test
+    public void testWrongPassword() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            client.start();
+            try (ClientSession s = client.connect("user", TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                s.addPasswordIdentity("bad password");
+                assertAuthenticationResult(getCurrentTestName(), s.auth(), false);
+            }
+        }
+    }
+
+    @Test
+    public void testChangeUser() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            client.start();
+
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                Collection<ClientSession.ClientSessionEvent> mask
+                        = EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH);
+                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(mask, DEFAULT_TIMEOUT);
+                assertFalse("Timeout while waiting on session events",
+                        result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+                String password = "the-password";
+                for (String username : new String[] { "user1", "user2" }) {
+                    try {
+                        assertAuthenticationResult(username, authPassword(s, username, password), false);
+                    } finally {
+                        s.removePasswordIdentity(password);
+                    }
+                }
+
+                // Note that WAIT_AUTH flag should be false, but since the internal
+                // authentication future is not updated, it's still returned
+                result = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), DEFAULT_TIMEOUT);
+                assertTrue("Mismatched client session close mask: " + result, result.containsAll(mask));
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test // see SSHD-196
+    public void testChangePassword() throws Exception {
+        PasswordAuthenticator delegate = sshd.getPasswordAuthenticator();
+        AtomicInteger attemptsCount = new AtomicInteger(0);
+        AtomicInteger changesCount = new AtomicInteger(0);
+        sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
+            @Override
+            public boolean authenticate(String username, String password, ServerSession session) {
+                if (attemptsCount.incrementAndGet() == 1) {
+                    throw new PasswordChangeRequiredException(
+                            attemptsCount.toString(),
+                            getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
+                }
+
+                return delegate.authenticate(username, password, session);
+            }
+
+            @Override
+            public boolean handleClientPasswordChangeRequest(
+                    ServerSession session, String username, String oldPassword, String newPassword) {
+                if (changesCount.incrementAndGet() == 1) {
+                    assertNotEquals("Non-different passwords", oldPassword, newPassword);
+                    return authenticate(username, newPassword, session);
+                } else {
+                    return PasswordAuthenticator.super.handleClientPasswordChangeRequest(
+                            session, username, oldPassword, newPassword);
+                }
+            }
+        });
+        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
+
+        try (SshClient client = setupTestClient()) {
+            AtomicInteger updatesCount = new AtomicInteger(0);
+            client.setUserInteraction(new UserInteraction() {
+                @Override
+                public boolean isInteractionAllowed(ClientSession session) {
+                    return true;
+                }
+
+                @Override
+                public String[] interactive(
+                        ClientSession session, String name, String instruction,
+                        String lang, String[] prompt, boolean[] echo) {
+                    throw new UnsupportedOperationException("Unexpected call");
+                }
+
+                @Override
+                public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+                    assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
+                    assertEquals("Mismatched language",
+                            CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
+                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
+                    return getCurrentTestName();
+                }
+            });
+
+            AtomicInteger sentCount = new AtomicInteger(0);
+            client.setUserAuthFactories(Collections.singletonList(
+                    new org.apache.sshd.client.auth.password.UserAuthPasswordFactory() {
+                        @Override
+                        public org.apache.sshd.client.auth.password.UserAuthPassword createUserAuth(ClientSession session)
+                                throws IOException {
+                            return new org.apache.sshd.client.auth.password.UserAuthPassword() {
+                                @Override
+                                protected IoWriteFuture sendPassword(
+                                        Buffer buffer, ClientSession session, String oldPassword, String newPassword)
+                                        throws Exception {
+                                    int count = sentCount.incrementAndGet();
+                                    // 1st one is the original one (which is denied by the server)
+                                    // 2nd one is the updated one retrieved from the user interaction
+                                    if (count == 2) {
+                                        return super.sendPassword(buffer, session, getClass().getName(), newPassword);
+                                    } else {
+                                        return super.sendPassword(buffer, session, oldPassword, newPassword);
+                                    }
+                                }
+                            };
+                        }
+                    }));
+            CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
+
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                s.addPasswordIdentity(getCurrentTestName());
+                s.auth().verify(AUTH_TIMEOUT);
+                assertEquals("No password change request generated", 2, attemptsCount.get());
+                assertEquals("No password change handled", 1, changesCount.get());
+                assertEquals("No user interaction invoked", 1, updatesCount.get());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testAuthPasswordOnly() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+
+            client.start();
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
+                        EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
+                        DEFAULT_TIMEOUT);
+                assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+                String password = getCurrentTestName();
+                try {
+                    assertAuthenticationResult(getCurrentTestName(),
+                            authPassword(s, getCurrentTestName(), password), false);
+                } finally {
+                    s.removePasswordIdentity(password);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testAuthKeyPassword() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+            sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+
+            client.start();
+
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
+                        EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
+                        DEFAULT_TIMEOUT);
+                assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+                KeyPairProvider provider = createTestHostKeyProvider();
+                KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
+                            authPublicKey(s, getCurrentTestName(), pair), false);
+                } finally {
+                    s.removePublicKeyIdentity(pair);
+                }
+
+                String password = getCurrentTestName();
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.PASSWORD,
+                            authPassword(s, getCurrentTestName(), password), true);
+                } finally {
+                    s.removePasswordIdentity(password);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test // see SSHD-196
+    public void testAuthPasswordChangeRequest() throws Exception {
+        PasswordAuthenticator delegate = Objects.requireNonNull(sshd.getPasswordAuthenticator(), "No password authenticator");
+        AtomicInteger attemptsCount = new AtomicInteger(0);
+        sshd.setPasswordAuthenticator((username, password, session) -> {
+            if (attemptsCount.incrementAndGet() == 1) {
+                throw new PasswordChangeRequiredException(
+                        attemptsCount.toString(),
+                        getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
+            }
+
+            return delegate.authenticate(username, password, session);
+        });
+        CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
+
+        try (SshClient client = setupTestClient()) {
+            AtomicInteger updatesCount = new AtomicInteger(0);
+            client.setUserInteraction(new UserInteraction() {
+                @Override
+                public boolean isInteractionAllowed(ClientSession session) {
+                    return true;
+                }
+
+                @Override
+                public String[] interactive(
+                        ClientSession session, String name, String instruction,
+                        String lang, String[] prompt, boolean[] echo) {
+                    throw new UnsupportedOperationException("Unexpected call");
+                }
+
+                @Override
+                public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+                    assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
+                    assertEquals("Mismatched language",
+                            CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
+                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
+                    return getCurrentTestName();
+                }
+            });
+            CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
+
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                s.addPasswordIdentity(getCurrentTestName());
+                s.auth().verify(AUTH_TIMEOUT);
+                assertEquals("No password change request generated", 2, attemptsCount.get());
+                assertEquals("No user interaction invoked", 1, updatesCount.get());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testPasswordIdentityProviderPropagation() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            List<String> passwords = Collections.singletonList(getCurrentTestName());
+            AtomicInteger loadCount = new AtomicInteger(0);
+            PasswordIdentityProvider provider = () -> {
+                loadCount.incrementAndGet();
+                outputDebugMessage("loadPasswords - count=%s", loadCount);
+                return passwords;
+            };
+            client.setPasswordIdentityProvider(provider);
+
+            client.start();
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                s.auth().verify(AUTH_TIMEOUT);
+                assertEquals("Mismatched load passwords count", 1, loadCount.get());
+                assertSame("Mismatched passwords identity provider", provider, s.getPasswordIdentityProvider());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test // see SSHD-714
+    public void testPasswordIdentityWithSpacesPrefixOrSuffix() throws Exception {
+        sshd.setPasswordAuthenticator((username, password, session) -> {
+            return (username != null) && (!username.trim().isEmpty())
+                    && (password != null) && (!password.isEmpty())
+                    && ((password.charAt(0) == ' ') || (password.charAt(password.length() - 1) == ' '));
+        });
+
+        try (SshClient client = setupTestClient()) {
+            client.start();
+
+            try {
+                for (String password : new String[] {
+                        " ", "    ", "  " + getCurrentTestName(), getCurrentTestName() + "    "
+                }) {
+                    try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                            .verify(CONNECT_TIMEOUT)
+                            .getSession()) {
+                        s.addPasswordIdentity(password);
+
+                        AuthFuture auth = s.auth();
+                        assertTrue("No authentication result in time for password='" + password + "'",
+                                auth.await(AUTH_TIMEOUT));
+                        assertTrue("Failed to authenticate with password='" + password + "'", auth.isSuccess());
+                    }
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test   // see SSHD-1114
+    public void testPasswordAuthenticationReporter() throws Exception {
+        String goodPassword = getCurrentTestName();
+        String badPassword = getClass().getSimpleName();
+        List<String> attempted = new ArrayList<>();
+        sshd.setPasswordAuthenticator((user, password, session) -> {
+            attempted.add(password);
+            return goodPassword.equals(password);
+        });
+        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+        sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+
+        List<String> reported = new ArrayList<>();
+        PasswordAuthenticationReporter reporter = new PasswordAuthenticationReporter() {
+            @Override
+            public void signalAuthenticationAttempt(
+                    ClientSession session, String service, String oldPassword, boolean modified, String newPassword)
+                    throws Exception {
+                reported.add(oldPassword);
+            }
+
+            @Override
+            public void signalAuthenticationSuccess(ClientSession session, String service, String password)
+                    throws Exception {
+                assertEquals("Mismatched succesful password", goodPassword, password);
+            }
+
+            @Override
+            public void signalAuthenticationFailure(
+                    ClientSession session, String service, String password, boolean partial, List<String> serverMethods)
+                    throws Exception {
+                assertEquals("Mismatched failed password", badPassword, password);
+            }
+        };
+
+        try (SshClient client = setupTestClient()) {
+            client.setUserAuthFactories(
+                    Collections.singletonList(new org.apache.sshd.client.auth.password.UserAuthPasswordFactory()));
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT).getSession()) {
+                session.addPasswordIdentity(badPassword);
+                session.addPasswordIdentity(goodPassword);
+                session.setPasswordAuthenticationReporter(reporter);
+                session.auth().verify(AUTH_TIMEOUT);
+            } finally {
+                client.stop();
+            }
+        }
+
+        List<String> expected = Arrays.asList(badPassword, goodPassword);
+        assertListEquals("Attempted passwords", expected, attempted);
+        assertListEquals("Reported passwords", expected, reported);
+    }
+}
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
new file mode 100644
index 0000000..300b77e
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.GenericUtils;
+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.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.CoreTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class PublicKeyAuthenticationTest extends AuthenticationTestSupport {
+    public PublicKeyAuthenticationTest() {
+        super();
+    }
+
+    @Test // see SSHD-618
+    public void testPublicKeyAuthDifferentThanKex() throws Exception {
+        KeyPairProvider serverKeys = KeyPairProvider.wrap(
+                CommonTestSupportUtils.generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024),
+                CommonTestSupportUtils.generateKeyPair(KeyUtils.DSS_ALGORITHM, 512),
+                CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256));
+        sshd.setKeyPairProvider(serverKeys);
+        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+
+        KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+        sshd.setPublickeyAuthenticator((username, key, session) -> {
+            String keyType = KeyUtils.getKeyType(key);
+            String expType = KeyUtils.getKeyType(clientIdentity);
+            assertEquals("Mismatched client key types", expType, keyType);
+            assertKeyEquals("Mismatched authentication public keys", clientIdentity.getPublic(), key);
+            return true;
+        });
+
+        // since we need to use RSA
+        CoreTestSupportUtils.setupFullSignaturesSupport(sshd);
+        try (SshClient client = setupTestClient()) {
+            // force server to use only RSA
+            NamedFactory<Signature> kexSignature = BuiltinSignatures.rsa;
+            client.setSignatureFactories(Collections.singletonList(kexSignature));
+            client.setServerKeyVerifier((sshClientSession, remoteAddress, serverKey) -> {
+                String keyType = KeyUtils.getKeyType(serverKey);
+                String expType = kexSignature.getName();
+                assertEquals("Mismatched server key type", expType, keyType);
+
+                KeyPair kp;
+                try {
+                    kp = ValidateUtils.checkNotNull(serverKeys.loadKey(null, keyType), "No server key for type=%s", keyType);
+                } catch (IOException | GeneralSecurityException e) {
+                    throw new RuntimeException(
+                            "Unexpected " + e.getClass().getSimpleName() + ")"
+                                               + " keys loading exception: " + e.getMessage(),
+                            e);
+                }
+                assertKeyEquals("Mismatched server public keys", kp.getPublic(), serverKey);
+                return true;
+            });
+
+            // allow only EC keys for public key authentication
+            org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory factory
+                    = new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory();
+            factory.setSignatureFactories(
+                    Arrays.asList(
+                            BuiltinSignatures.nistp256, BuiltinSignatures.nistp384, BuiltinSignatures.nistp521));
+            client.setUserAuthFactories(Collections.singletonList(factory));
+
+            client.start();
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                s.addPublicKeyIdentity(clientIdentity);
+                s.auth().verify(AUTH_TIMEOUT);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test // see SSHD-624
+    public void testMismatchedUserAuthPkOkData() throws Exception {
+        AtomicInteger challengeCounter = new AtomicInteger(0);
+        sshd.setUserAuthFactories(Collections.singletonList(
+                new org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory() {
+                    @Override
+                    public org.apache.sshd.server.auth.pubkey.UserAuthPublicKey createUserAuth(ServerSession session)
+                            throws IOException {
+                        return new org.apache.sshd.server.auth.pubkey.UserAuthPublicKey() {
+                            @Override
+                            protected void sendPublicKeyResponse(
+                                    ServerSession session, String username, String alg, PublicKey key,
+                                    byte[] keyBlob, int offset, int blobLen, Buffer buffer)
+                                    throws Exception {
+                                int count = challengeCounter.incrementAndGet();
+                                outputDebugMessage("sendPublicKeyChallenge(%s)[%s]: count=%d", session, alg, count);
+                                if (count == 1) {
+                                    // send wrong key type
+                                    super.sendPublicKeyResponse(session, username,
+                                            KeyPairProvider.SSH_DSS, key, keyBlob, offset, blobLen, buffer);
+                                } else if (count == 2) {
+                                    // send another key
+                                    KeyPair otherPair = org.apache.sshd.util.test.CommonTestSupportUtils
+                                            .generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024);
+                                    PublicKey otherKey = otherPair.getPublic();
+                                    Buffer buf = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_PK_OK,
+                                            blobLen + alg.length() + Long.SIZE);
+                                    buf.putString(alg);
+                                    buf.putPublicKey(otherKey);
+                                    session.writePacket(buf);
+                                } else {
+                                    super.sendPublicKeyResponse(session, username, alg, key, keyBlob, offset, blobLen, buffer);
+                                }
+                            }
+                        };
+                    }
+
+                }));
+
+        try (SshClient client = setupTestClient()) {
+            KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(
+                    CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
+                    CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
+            client.start();
+
+            try {
+                for (int index = 1; index <= 4; index++) {
+                    try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                            .verify(CONNECT_TIMEOUT)
+                            .getSession()) {
+                        s.addPublicKeyIdentity(clientIdentity);
+                        s.auth().verify(AUTH_TIMEOUT);
+                        assertEquals("Mismatched number of challenges", 3, challengeCounter.get());
+                        break;
+                    } catch (SshException e) { // expected
+                        outputDebugMessage("%s on retry #%d: %s", e.getClass().getSimpleName(), index, e.getMessage());
+
+                        Throwable t = e.getCause();
+                        assertObjectInstanceOf("Unexpected failure cause at retry #" + index, InvalidKeySpecException.class, t);
+                    }
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test // see SSHD-862
+    public void testSessionContextPropagatedToKeyFilePasswordProvider() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT)
+                    .getSession()) {
+                String keyLocation = "super-secret-passphrase-ec256-key";
+                FilePasswordProvider passwordProvider = new FilePasswordProvider() {
+                    @Override
+                    public String getPassword(
+                            SessionContext session, NamedResource resourceKey, int retryIndex)
+                            throws IOException {
+                        assertSame("Mismatched session context", s, session);
+                        assertEquals("Mismatched retry index", 0, retryIndex);
+
+                        String name = resourceKey.getName();
+                        int pos = name.lastIndexOf('/');
+                        if (pos >= 0) {
+                            name = name.substring(pos + 1);
+                        }
+                        assertEquals("Mismatched location", keyLocation, name);
+
+                        Boolean passwordRequested = session.getAttribute(PASSWORD_ATTR);
+                        assertNull("Password already requested", passwordRequested);
+                        session.setAttribute(PASSWORD_ATTR, Boolean.TRUE);
+                        return "super secret passphrase";
+                    }
+                };
+                s.setKeyIdentityProvider(new KeyIdentityProvider() {
+                    @Override
+                    public Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException {
+                        assertSame("Mismatched session context", s, session);
+                        URL location = getClass().getResource(keyLocation);
+                        assertNotNull("Missing key file " + keyLocation, location);
+
+                        URLResource resourceKey = new URLResource(location);
+                        Iterable<KeyPair> ids;
+                        try (InputStream keyData = resourceKey.openInputStream()) {
+                            ids = SecurityUtils.loadKeyPairIdentities(session, resourceKey, keyData, passwordProvider);
+                        }
+                        KeyPair kp = GenericUtils.head(ids);
+                        assertNotNull("No identity loaded from " + resourceKey, kp);
+                        return Collections.singletonList(kp);
+                    }
+                });
+                s.auth().verify(AUTH_TIMEOUT);
+
+                Boolean passwordRequested = s.getAttribute(PASSWORD_ATTR);
+                assertNotNull("Password provider not invoked", passwordRequested);
+                assertTrue("Password not requested", passwordRequested.booleanValue());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test   // see SSHD-1114
+    public void testPublicKeyAuthenticationReporter() throws Exception {
+        KeyPair goodIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+        KeyPair badIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+        List<PublicKey> attempted = new ArrayList<>();
+        sshd.setPublickeyAuthenticator((username, key, session) -> {
+            attempted.add(key);
+            return KeyUtils.compareKeys(goodIdentity.getPublic(), key);
+        });
+        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+        sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+
+        List<PublicKey> reported = new ArrayList<>();
+        List<PublicKey> signed = new ArrayList<>();
+        PublicKeyAuthenticationReporter reporter = new PublicKeyAuthenticationReporter() {
+            @Override
+            public void signalAuthenticationAttempt(
+                    ClientSession session, String service, KeyPair identity, String signature)
+                    throws Exception {
+                reported.add(identity.getPublic());
+            }
+
+            @Override
+            public void signalSignatureAttempt(
+                    ClientSession session, String service, KeyPair identity, String signature, byte[] sigData)
+                    throws Exception {
+                signed.add(identity.getPublic());
+            }
+
+            @Override
+            public void signalAuthenticationSuccess(ClientSession session, String service, KeyPair identity)
+                    throws Exception {
+                assertTrue("Mismatched success identity", KeyUtils.compareKeys(goodIdentity.getPublic(), identity.getPublic()));
+            }
+
+            @Override
+            public void signalAuthenticationFailure(
+                    ClientSession session, String service, KeyPair identity, boolean partial, List<String> serverMethods)
+                    throws Exception {
+                assertTrue("Mismatched failed identity", KeyUtils.compareKeys(badIdentity.getPublic(), identity.getPublic()));
+            }
+        };
+
+        try (SshClient client = setupTestClient()) {
+            client.setUserAuthFactories(
+                    Collections.singletonList(new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(CONNECT_TIMEOUT).getSession()) {
+                session.addPublicKeyIdentity(badIdentity);
+                session.addPublicKeyIdentity(goodIdentity);
+                session.setPublicKeyAuthenticationReporter(reporter);
+                session.auth().verify(AUTH_TIMEOUT);
+            } finally {
+                client.stop();
+            }
+        }
+
+        List<PublicKey> expected = Arrays.asList(badIdentity.getPublic(), goodIdentity.getPublic());
+        // The server public key authenticator is called twice with the good identity
+        int numAttempted = attempted.size();
+        assertKeyListEquals("Attempted", expected, (numAttempted > 0) ? attempted.subList(0, numAttempted - 1) : attempted);
+        assertKeyListEquals("Reported", expected, reported);
+        // The signing is attempted only if the initial public key is accepted
+        assertKeyListEquals("Signed", Collections.singletonList(goodIdentity.getPublic()), signed);
+    }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/server/TestServerSession.java b/sshd-core/src/test/java/org/apache/sshd/util/test/server/TestServerSession.java
new file mode 100644
index 0000000..652092b
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/server/TestServerSession.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test.server;
+
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.session.ServerSessionImpl;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class TestServerSession extends ServerSessionImpl {
+    public TestServerSession(ServerFactoryManager server, IoSession ioSession) throws Exception {
+        super(server, ioSession);
+    }
+
+    @Override
+    public void handleMessage(Buffer buffer) throws Exception {
+        super.handleMessage(buffer); // debug breakpoint
+    }
+}