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 2015/09/17 08:00:02 UTC

[1/3] mina-sshd git commit: [SSHD-563] Provide server-side KeyboardInteractiveAuthenticator

Repository: mina-sshd
Updated Branches:
  refs/heads/master d19f5eca5 -> 75643fcb1


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..f6cc26e
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
@@ -0,0 +1,523 @@
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.UserInteraction;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.client.session.ClientConnectionServiceFactory;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+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.deprecated.ClientUserAuthServiceOld;
+import org.apache.sshd.deprecated.UserAuthKeyboardInteractive;
+import org.apache.sshd.deprecated.UserAuthPassword;
+import org.apache.sshd.deprecated.UserAuthPublicKey;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.UserAuthKeyboardInteractiveFactory;
+import org.apache.sshd.server.auth.UserAuthPasswordFactory;
+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.password.PasswordAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
+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.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 String WELCOME = "Welcome to SSHD";
+
+    private SshServer sshd;
+    private int port;
+
+    public AuthenticationTest() {
+        super();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = setupTestServer();
+        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, WELCOME);
+        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, "publickey,password publickey,keyboard-interactive");
+        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(7L, TimeUnit.SECONDS).getSession()) {
+                s.addPasswordIdentity("bad password");
+                assertAuthenticationResult(getCurrentTestName(), s.auth(), false);
+            }
+        }
+    }
+
+    @Test
+    public void testChangeUser() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            client.setServiceFactories(Arrays.asList(
+                    new ClientUserAuthServiceOld.Factory(),
+                    ClientConnectionServiceFactory.INSTANCE
+            ));
+
+            client.start();
+
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
+
+                for (String username : new String[]{"user1", "user2"}) {
+                    assertAuthenticationResult(username, authPassword(s, username, "the-password"), false);
+                }
+
+                // Note that WAIT_AUTH flag should be false, but since the internal
+                // authentication future is not updated, it's still returned
+                assertEquals("Mismatched client session close mask", ClientSession.CLOSED | ClientSession.WAIT_AUTH, s.waitFor(ClientSession.CLOSED, 1000));
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test   // see SSHD-196
+    public void testChangePassword() throws Exception {
+        final PasswordAuthenticator delegate = sshd.getPasswordAuthenticator();
+        final AtomicInteger attemptsCount = new AtomicInteger(0);
+        sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
+            @Override
+            public boolean authenticate(String username, String password, ServerSession session)
+                    throws PasswordChangeRequiredException {
+                if (attemptsCount.incrementAndGet() == 1) {
+                    throw new PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
+                }
+
+                return delegate.authenticate(username, password, session);
+            }
+        });
+
+        final AtomicInteger changesCount = new AtomicInteger(0);
+        sshd.setUserAuthFactories(Collections.<NamedFactory<org.apache.sshd.server.auth.UserAuth>>singletonList(
+            new org.apache.sshd.server.auth.UserAuthPasswordFactory() {
+                @Override
+                public org.apache.sshd.server.auth.UserAuth create() {
+                    return new org.apache.sshd.server.auth.UserAuthPassword() {
+                        @Override
+                        protected Boolean handleClientPasswordChangeRequest(
+                                Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword)
+                                        throws Exception {
+                            if (changesCount.incrementAndGet() == 1) {
+                                assertNotEquals("Non-different passwords", oldPassword, newPassword);
+                                return checkPassword(buffer, session, username, newPassword);
+                            } else {
+                                return super.handleClientPasswordChangeRequest(buffer, session, username, oldPassword, newPassword);
+                            }
+                        }
+                    };
+                }
+            }
+        ));
+        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+
+        try (SshClient client = setupTestClient()) {
+            final AtomicInteger updatesCount = new AtomicInteger(0);
+            client.setUserInteraction(new UserInteraction() {
+                @Override
+                public void welcome(ClientSession session, String banner, String lang) {
+                    // ignored
+                }
+
+                @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", ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
+                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
+                    return getCurrentTestName();
+                }
+            });
+
+            final AtomicInteger sentCount = new AtomicInteger(0);
+            client.setUserAuthFactories(Collections.<NamedFactory<org.apache.sshd.client.auth.UserAuth>>singletonList(
+                new org.apache.sshd.client.auth.UserAuthPasswordFactory() {
+                    @Override
+                    public org.apache.sshd.client.auth.UserAuth create() {
+                        return new org.apache.sshd.client.auth.UserAuthPassword() {
+                            @Override
+                            protected void sendPassword(Buffer buffer, ClientSession session, String oldPassword, String newPassword) throws IOException {
+                                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) {
+                                    super.sendPassword(buffer, session, getClass().getName(), newPassword);
+                                } else {
+                                    super.sendPassword(buffer, session, oldPassword, newPassword);
+                                }
+                            }
+                        };
+                    }
+            }));
+            FactoryManagerUtils.updateProperty(client, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.addPasswordIdentity(getCurrentTestName());
+                s.auth().verify(11L, TimeUnit.SECONDS);
+                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()) {
+            client.setServiceFactories(Arrays.asList(
+                    new ClientUserAuthServiceOld.Factory(),
+                    ClientConnectionServiceFactory.INSTANCE
+            ));
+            client.start();
+
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
+                assertAuthenticationResult(getCurrentTestName(), authPassword(s, getCurrentTestName(), getCurrentTestName()), false);
+                s.close(true);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testAuthKeyPassword() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            client.setServiceFactories(Arrays.asList(
+                    new ClientUserAuthServiceOld.Factory(),
+                    ClientConnectionServiceFactory.INSTANCE
+            ));
+            client.start();
+
+            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
+
+                KeyPair pair = createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
+                assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
+                assertAuthenticationResult(UserAuthMethodFactory.PASSWORD, authPassword(s, getCurrentTestName(), getCurrentTestName()), true);
+                s.close(true);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testAuthDefaultKeyInteractive() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            client.setServiceFactories(Arrays.asList(
+                    new ClientUserAuthServiceOld.Factory(),
+                    ClientConnectionServiceFactory.INSTANCE
+            ));
+
+            sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
+                @Override
+                public InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods) {
+                    assertEquals("Mismatched user language",
+                            FactoryManagerUtils.getStringProperty(
+                                    client,
+                                    org.apache.sshd.client.auth.UserAuthKeyboardInteractive.INTERACTIVE_LANGUAGE_TAG,
+                                    org.apache.sshd.client.auth.UserAuthKeyboardInteractive.DEFAULT_INTERACTIVE_LANGUAGE_TAG),
+                            lang);
+                    assertEquals("Mismatched client sub-methods",
+                            FactoryManagerUtils.getStringProperty(
+                                    client,
+                                    org.apache.sshd.client.auth.UserAuthKeyboardInteractive.INTERACTIVE_SUBMETHODS,
+                                    org.apache.sshd.client.auth.UserAuthKeyboardInteractive.DEFAULT_INTERACTIVE_SUBMETHODS),
+                            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(7L, TimeUnit.SECONDS).getSession()) {
+                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
+
+                KeyPair pair = createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
+                assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
+                assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE, authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
+
+                s.close(true);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test   // see SSHD-563
+    public void testAuthMultiChallengeKeyInteractive() throws Exception {
+        final Class<?> anchor = getClass();
+        final InteractiveChallenge challenge = new InteractiveChallenge();
+        challenge.setInteractionName(getCurrentTestName());
+        challenge.setInteractionInstruction(anchor.getPackage().getName());
+        challenge.setLanguageTag(Locale.getDefault().getLanguage());
+
+        final Map<String,String> rspMap = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER) {
+            private static final long serialVersionUID = 1L;    // we're not serializing it
+
+            {
+                put("class", anchor.getSimpleName());
+                put("package", anchor.getPackage().getName());
+                put("test", getCurrentTestName());
+            }
+        };
+        for (String prompt : rspMap.keySet()) {
+            challenge.addPrompt(prompt, (GenericUtils.size(challenge.getPrompts()) & 0x1) != 0);
+        }
+
+        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthKeyboardInteractiveFactory.NAME);
+        final AtomicInteger genCount = new AtomicInteger(0);
+        final AtomicInteger authCount = new AtomicInteger(0);
+        sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() {
+            @Override
+            public InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods) {
+                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;
+                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;
+            }
+        });
+        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthKeyboardInteractiveFactory.NAME);
+
+        try (SshClient client = setupTestClient()) {
+            final AtomicInteger interactiveCount = new AtomicInteger(0);
+            client.setUserInteraction(new UserInteraction() {
+                @Override
+                public void welcome(ClientSession session, String banner, String lang) {
+                    // ignored
+                }
+
+                @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");
+                }
+            });
+            FactoryManagerUtils.updateProperty(client, ServerFactoryManager.AUTH_METHODS, UserAuthKeyboardInteractiveFactory.NAME);
+
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.auth().verify(11L, TimeUnit.SECONDS);
+                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 {
+        final PasswordAuthenticator delegate = ValidateUtils.checkNotNull(sshd.getPasswordAuthenticator(), "No password authenticator");
+        final AtomicInteger attemptsCount = new AtomicInteger(0);
+        sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
+            @Override
+            public boolean authenticate(String username, String password, ServerSession session)
+                    throws PasswordChangeRequiredException {
+                if (attemptsCount.incrementAndGet() == 1) {
+                    throw new PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
+                }
+
+                return delegate.authenticate(username, password, session);
+            }
+        });
+        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+
+        try (SshClient client = setupTestClient()) {
+            final AtomicInteger updatesCount = new AtomicInteger(0);
+            client.setUserInteraction(new UserInteraction() {
+                @Override
+                public void welcome(ClientSession session, String banner, String lang) {
+                    // ignored
+                }
+
+                @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", ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
+                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
+                    return getCurrentTestName();
+                }
+            });
+            FactoryManagerUtils.updateProperty(client, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
+
+            client.start();
+
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.addPasswordIdentity(getCurrentTestName());
+                s.auth().verify(11L, TimeUnit.SECONDS);
+                assertEquals("No password change request generated", 2, attemptsCount.get());
+                assertEquals("No user interaction invoked", 1, updatesCount.get());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    private static void assertAuthenticationResult(String message, AuthFuture future, boolean expected) throws IOException {
+        assertTrue(message + ": failed to get result on time", future.await(5L, TimeUnit.SECONDS));
+        assertEquals(message + ": mismatched authentication result", expected, future.isSuccess());
+    }
+
+    private AuthFuture authPassword(ClientSession s, String user, String pswd) throws IOException {
+        s.setUsername(user);
+        return s.getService(ClientUserAuthServiceOld.class)
+                .auth(new UserAuthPassword(s, "ssh-connection", pswd));
+    }
+
+    private AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
+        s.setUsername(user);
+        return s.getService(ClientUserAuthServiceOld.class)
+                .auth(new UserAuthKeyboardInteractive(s, "ssh-connection", pswd));
+    }
+
+    private AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
+        s.setUsername(user);
+        return s.getService(ClientUserAuthServiceOld.class)
+                .auth(new UserAuthPublicKey(s, "ssh-connection", pair));
+    }
+
+    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
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java b/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
index 62f8a0c..72237c3 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/cipher/CipherTest.java
@@ -105,7 +105,7 @@ public class CipherTest extends BaseTestSupport {
         Assume.assumeTrue("No internal support for " + builtInCipher.getName(), builtInCipher.isSupported() && checkCipher(jschCipher.getName()));
 
         try (SshServer sshd = setupTestServer()) {
-            sshd.setCipherFactories(Arrays.<NamedFactory<org.apache.sshd.common.cipher.Cipher>>asList(builtInCipher));
+            sshd.setCipherFactories(Collections.<NamedFactory<org.apache.sshd.common.cipher.Cipher>>singletonList(builtInCipher));
             sshd.start();
 
             try {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
index ef7d199..bfe983d 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
@@ -159,13 +159,13 @@ public class ServerTest extends BaseTestSupport {
             AuthFuture authFuture;
             do {
                 nbTrials++;
-                assertTrue(nbTrials < 100);
+                assertTrue("Number of trials below max.", nbTrials < 100);
                 authFuture = s.getService(ClientUserAuthServiceOld.class)
                         .auth(new org.apache.sshd.deprecated.UserAuthPassword(s, "ssh-connection", "buggy"));
-                assertTrue("Authentication wait failed", authFuture.await(5000));
+                assertTrue("Authentication wait failed", authFuture.await(5L, TimeUnit.SECONDS));
                 assertTrue("Authentication not done", authFuture.isDone());
                 assertFalse("Authentication unexpectedly successful", authFuture.isSuccess());
-            } while (authFuture.isFailure());
+            } while (authFuture.getException() == null);
 
             assertNotNull("Missing auth future exception", authFuture.getException());
             assertTrue("Number trials (" + nbTrials + ") below min.=" + MAX_AUTH_REQUESTS, nbTrials > MAX_AUTH_REQUESTS);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
index c20029f..777ea52 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
@@ -74,7 +74,7 @@ public class GitPackCommandTest extends BaseTestSupport {
                 Path localDir = localRootDir.resolve(serverDir.getFileName());
                 Utils.deleteRecursive(localDir);
                 Git.cloneRepository()
-                        .setURI("ssh://" + getCurrentTestName() + "@localhost:" + port + "/" + serverDir.getFileName())
+                        .setURI("ssh://" + getCurrentTestName() + "@" + TEST_LOCALHOST + ":" + port + "/" + serverDir.getFileName())
                         .setDirectory(localDir.toFile())
                         .call();
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
index c8ea484..677ead5 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
@@ -48,7 +48,7 @@ public class GitPgmCommandTest extends BaseTestSupport {
     }
 
     @Test
-    public void testGitpgm() throws Exception {
+    public void testGitPgm() throws Exception {
         Path targetParent = detectTargetFolder().getParent();
         Path serverDir = getTempTargetRelativeFile(getClass().getSimpleName());
 


[2/3] mina-sshd git commit: [SSHD-563] Provide server-side KeyboardInteractiveAuthenticator

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/DefaultKeyboardInteractiveAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/DefaultKeyboardInteractiveAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/DefaultKeyboardInteractiveAuthenticator.java
new file mode 100644
index 0000000..71283ff
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/DefaultKeyboardInteractiveAuthenticator.java
@@ -0,0 +1,117 @@
+/*
+ * 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.server.auth.keyboard;
+
+import java.util.List;
+
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Provides a default implementation for {@link KeyboardInteractiveAuthenticator}
+ * where it prompts for the password.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultKeyboardInteractiveAuthenticator
+        extends AbstractLoggingBean
+        implements KeyboardInteractiveAuthenticator {
+    // configuration parameters on the FactoryManager to configure the message values
+    public static final String KB_INTERACTIVE_NAME_PROP = "kb-server-interactive-name";
+    public static final String DEFAULT_KB_INTERACTIVE_NAME = "Password authentication";
+    public static final String KB_INTERACTIVE_INSTRUCTION_PROP = "kb-server-interactive-instruction";
+    public static final String DEFAULT_KB_INTERACTIVE_INSTRUCTION = "";
+    public static final String KB_INTERACTIVE_LANG_PROP = "kb-server-interactive-language";
+    public static final String DEFAULT_KB_INTERACTIVE_LANG = "en-US";
+    public static final String KB_INTERACTIVE_PROMPT_PROP = "kb-server-interactive-prompt";
+    public static final String DEFAULT_KB_INTERACTIVE_PROMPT = "Password: ";
+    public static final String KB_INTERACTIVE_ECHO_PROMPT_PROP = "kb-server-interactive-echo-prompt";
+    public static final boolean DEFAULT_KB_INTERACTIVE_ECHO_PROMPT = false;
+
+    public static final DefaultKeyboardInteractiveAuthenticator INSTANCE = new DefaultKeyboardInteractiveAuthenticator();
+
+    public DefaultKeyboardInteractiveAuthenticator() {
+        super();
+    }
+
+    @Override
+    public InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods) {
+        ServerFactoryManager manager = ValidateUtils.checkNotNull(session.getFactoryManager(), "No factory manager");
+        PasswordAuthenticator auth = manager.getPasswordAuthenticator();
+        if (auth == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("generateChallenge({}) no password authenticator", session);
+            }
+            return null;
+        }
+
+        InteractiveChallenge challenge = new InteractiveChallenge();
+        challenge.setInteractionName(getInteractionName(session));
+        challenge.setInteractionInstruction(getInteractionInstruction(session));
+        challenge.setLanguageTag(getInteractionLanguage(session));
+        challenge.addPrompt(getInteractionPrompt(session), isInteractionPromptEchoEnabled(session));
+        return challenge;
+    }
+
+    @Override
+    public boolean authenticate(ServerSession session, String username, List<String> responses) throws Exception {
+        ServerFactoryManager manager = ValidateUtils.checkNotNull(session.getFactoryManager(), "No factory manager");
+        PasswordAuthenticator auth = manager.getPasswordAuthenticator();
+        if (auth == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("authenticate({}) no password authenticator", session);
+            }
+            return false;
+        }
+
+        int numResp = GenericUtils.size(responses);
+        if (numResp != 1) {
+            throw new SshException("Mismatched number of responses");
+        }
+
+        return auth.authenticate(username, responses.get(0), session);
+    }
+
+    protected String getInteractionName(ServerSession session) {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_NAME_PROP, DEFAULT_KB_INTERACTIVE_NAME);
+    }
+
+    protected String getInteractionInstruction(ServerSession session) {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_INSTRUCTION_PROP, DEFAULT_KB_INTERACTIVE_INSTRUCTION);
+    }
+
+    protected String getInteractionLanguage(ServerSession session) {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_LANG_PROP, DEFAULT_KB_INTERACTIVE_LANG);
+    }
+
+    protected String getInteractionPrompt(ServerSession session) {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_PROMPT_PROP, DEFAULT_KB_INTERACTIVE_PROMPT);
+    }
+
+    protected boolean isInteractionPromptEchoEnabled(ServerSession session) {
+        return FactoryManagerUtils.getBooleanProperty(session, KB_INTERACTIVE_ECHO_PROMPT_PROP, DEFAULT_KB_INTERACTIVE_ECHO_PROMPT);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
new file mode 100644
index 0000000..1bbe2fc
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.auth.keyboard;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * Represents a server &quot;challenge&quot; as per
+ * <A HREF="https://www.ietf.org/rfc/rfc4256.txt">RFC-4256</A>
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class InteractiveChallenge implements Cloneable {
+    private String interactionName;
+    private String interactionInstruction;
+    private String languageTag;
+    private List<PromptEntry> prompts = new ArrayList<>();
+
+    public InteractiveChallenge() {
+        super();
+    }
+
+    public String getInteractionName() {
+        return interactionName;
+    }
+
+    public void setInteractionName(String interactionName) {
+        this.interactionName = interactionName;
+    }
+
+    public String getInteractionInstruction() {
+        return interactionInstruction;
+    }
+
+    public void setInteractionInstruction(String interactionInstruction) {
+        this.interactionInstruction = interactionInstruction;
+    }
+
+    public String getLanguageTag() {
+        return languageTag;
+    }
+
+    public void setLanguageTag(String languageTag) {
+        this.languageTag = languageTag;
+    }
+
+    public void addPrompt(String prompt, boolean echo) {
+        addPrompt(new PromptEntry(prompt, echo));
+    }
+
+    public void addPrompt(PromptEntry entry) {
+        this.prompts.add(ValidateUtils.checkNotNull(entry, "No entry"));
+    }
+
+    public List<PromptEntry> getPrompts() {
+        return prompts;
+    }
+
+    // NOTE: prompts are COPIED to the local one
+    public void setPrompts(Collection<? extends PromptEntry> prompts) {
+        clearPrompts();
+
+        if (GenericUtils.size(prompts) > 0) {
+            this.prompts.addAll(prompts);
+        }
+    }
+
+    public void clearPrompts() {
+        this.prompts.clear();
+    }
+
+    public <B extends Buffer> B append(B buffer) {
+        buffer.putString(getInteractionName());
+        buffer.putString(getInteractionInstruction());
+        buffer.putString(getLanguageTag());
+
+        List<PromptEntry> entries = getPrompts();
+        int numEntries = GenericUtils.size(entries);
+        buffer.putInt(numEntries);
+
+        for (int index = 0; index < numEntries; index++) {
+            PromptEntry e = entries.get(index);
+            e.append(buffer);
+        }
+
+        return buffer;
+    }
+
+    @Override
+    public InteractiveChallenge clone() {
+        try {
+            InteractiveChallenge other = getClass().cast(super.clone());
+            other.prompts = new ArrayList<>();
+            for (PromptEntry entry : getPrompts()) {
+                other.addPrompt(entry.clone());
+            }
+            return other;
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Failed to clone " + toString() + ": " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getInteractionName() + "[" + getInteractionInstruction() + "](" + getLanguageTag() + "): " + getPrompts();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/KeyboardInteractiveAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/KeyboardInteractiveAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/KeyboardInteractiveAuthenticator.java
new file mode 100644
index 0000000..80c94f1
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/KeyboardInteractiveAuthenticator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.server.auth.keyboard;
+
+import java.util.List;
+
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Provides pluggable authentication using the &quot;keyboard-interactive&quot;
+ * method as specified by <A HREF="https://www.ietf.org/rfc/rfc4256.txt">RFC-4256</A>?
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyboardInteractiveAuthenticator {
+    /**
+     * Generates the interactive &quot;challenge&quot; to send to the client
+     *
+     * @param session The {@link ServerSession} through which the request was received
+     * @param username The username
+     * @param lang The language tag
+     * @param subMethods Sub-methods hints sent by the client
+     * @return The {@link InteractiveChallenge} - if {@code null} then authentication
+     * attempt via &quot;keyboard-interactive&quot; method is rejected
+     */
+    InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods);
+
+    /**
+     * Called to authenticate the response to the challenge(s) sent previously
+     *
+     * @param session The {@link ServerSession} through which the response was received
+     * @param username The username
+     * @param responses The received responses - <B>Note:</B> it is up to the authenticator
+     * to make sure that the number of responses matches the number of prompts sent in
+     * the initial challenge. The <U>order</U> of the responses matches the order of the
+     * prompts sent to the client
+     * @return {@code true} if responses have been validated
+     * @throws Exception if bad responses and server should terminate the connection
+     */
+    boolean authenticate(ServerSession session, String username, List<String> responses) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/PromptEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/PromptEntry.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/PromptEntry.java
new file mode 100644
index 0000000..f8dab48
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/PromptEntry.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.auth.keyboard;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PromptEntry implements Serializable, Cloneable {
+    private static final long serialVersionUID = 8206049800536373640L;
+
+    private String prompt;
+    private boolean echo;
+
+    public PromptEntry() {
+        super();
+    }
+
+    public PromptEntry(String prompt, boolean echo) {
+        this.prompt = prompt;
+        this.echo = echo;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public void setPrompt(String prompt) {
+        this.prompt = prompt;
+    }
+
+    public boolean isEcho() {
+        return echo;
+    }
+
+    public void setEcho(boolean echo) {
+        this.echo = echo;
+    }
+
+    public <B extends Buffer> B append(B buffer) {
+        buffer.putString(getPrompt());
+        buffer.putBoolean(isEcho());
+        return buffer;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(getPrompt()) + (isEcho() ? 1 : 0);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        PromptEntry other = (PromptEntry) obj;
+        return Objects.equals(getPrompt(), other.getPrompt()) && (isEcho() == other.isEcho());
+    }
+
+    @Override
+    public PromptEntry clone() {
+        try {
+            return getClass().cast(super.clone());
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Failed to clone " + toString() + ": " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getPrompt() + "(echo=" + isEcho() + ")";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordChangeRequiredException.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordChangeRequiredException.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordChangeRequiredException.java
index e12e0ff..a0fc27d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordChangeRequiredException.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/password/PasswordChangeRequiredException.java
@@ -41,19 +41,19 @@ public class PasswordChangeRequiredException extends RuntimeException {
 
     public PasswordChangeRequiredException(String message, String prompt, String lang, Throwable cause) {
         super(message, cause);
-        this.prompt = (prompt == null) ? "" : prompt;
-        this.lang = (lang == null) ? "" : lang;
+        this.prompt = prompt;
+        this.lang = lang;
     }
 
     /**
-     * @return The prompt to show to the user - may be empty
+     * @return The prompt to show to the user - may be {@code null}/empty
      */
     public final String getPrompt() {
         return prompt;
     }
 
     /**
-     * @return The language code for the prompt - may be empty
+     * @return The language code for the prompt - may be {@code null}/empty
      */
     public final String getLanguage() {
         return lang;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/CachingPublicKeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/CachingPublicKeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/CachingPublicKeyAuthenticator.java
new file mode 100644
index 0000000..61667b1
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/CachingPublicKeyAuthenticator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.server.auth.pubkey;
+
+import java.security.PublicKey;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Caches the result per session
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener {
+
+    protected final PublickeyAuthenticator authenticator;
+    protected final Map<ServerSession, Map<PublicKey, Boolean>> cache = new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>();
+
+    public CachingPublicKeyAuthenticator(PublickeyAuthenticator authenticator) {
+        this.authenticator = authenticator;
+    }
+
+    @Override
+    public boolean authenticate(String username, PublicKey key, ServerSession session) {
+        Map<PublicKey, Boolean> map = cache.get(session);
+        if (map == null) {
+            map = new ConcurrentHashMap<>();
+            cache.put(session, map);
+            session.addSessionListener(this);
+        }
+
+        Boolean result = map.get(key);
+        if (result == null) {
+            result = authenticator.authenticate(username, key, session);
+            map.put(key, result);
+        }
+
+        return result;
+    }
+
+    @Override
+    public void sessionCreated(Session session) {
+        // ignored
+    }
+
+    @Override
+    public void sessionEvent(Session session, Event event) {
+        // ignored
+    }
+
+    @Override
+    public void sessionClosed(Session session) {
+        cache.remove(session);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
index fc5bea5..d0b1a01 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
@@ -27,6 +27,7 @@ import java.nio.file.Path;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.Collection;
 
+import org.apache.sshd.common.auth.UsernameHolder;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -40,7 +41,7 @@ import org.apache.sshd.server.session.ServerSession;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthenticator {
+public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthenticator implements UsernameHolder {
 
     /**
      * The default instance that enforces the same permissions regime as {@code OpenSSH}
@@ -81,6 +82,7 @@ public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthentica
         this.strict = strict;
     }
 
+    @Override
     public final String getUsername() {
         return user;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
index db2c7ec..6c7a096 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
@@ -182,14 +182,14 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
     }
 
     protected void handleAuthenticationInProgress(int cmd, Buffer buffer) throws Exception {
-        String username = (currentAuth == null) ? null : currentAuth.getUserName();
+        String username = (currentAuth == null) ? null : currentAuth.getUsername();
         if (log.isDebugEnabled()) {
             log.debug("handleAuthenticationInProgress({}@{})", username, session);
         }
     }
 
     protected void handleAuthenticationSuccess(int cmd, Buffer buffer) throws Exception {
-        String username = ValidateUtils.checkNotNull(currentAuth, "No current auth").getUserName();
+        String username = ValidateUtils.checkNotNull(currentAuth, "No current auth").getUsername();
         if (log.isDebugEnabled()) {
             log.debug("handleAuthenticationSuccess({}@{})", username, session);
         }
@@ -264,7 +264,7 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
     }
 
     protected void handleAuthenticationFailure(int cmd, Buffer buffer) throws Exception {
-        String username = (currentAuth == null) ? null : currentAuth.getUserName();
+        String username = (currentAuth == null) ? null : currentAuth.getUsername();
         if (log.isDebugEnabled()) {
             log.debug("handleAuthenticationFailure({}@{})", username, session);
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
deleted file mode 100644
index 4e00e85..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * 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;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.auth.UserInteraction;
-import org.apache.sshd.client.future.AuthFuture;
-import org.apache.sshd.client.session.ClientConnectionServiceFactory;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.auth.UserAuthMethodFactory;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.deprecated.ClientUserAuthServiceOld;
-import org.apache.sshd.deprecated.UserAuthKeyboardInteractive;
-import org.apache.sshd.deprecated.UserAuthPassword;
-import org.apache.sshd.deprecated.UserAuthPublicKey;
-import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.UserAuthPasswordFactory;
-import org.apache.sshd.server.auth.password.PasswordAuthenticator;
-import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
-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.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 String WELCOME = "Welcome to SSHD";
-
-    private SshServer sshd;
-    private int port;
-
-    public AuthenticationTest() {
-        super();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        sshd = setupTestServer();
-        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, WELCOME);
-        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, "publickey,password publickey,keyboard-interactive");
-        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(7L, TimeUnit.SECONDS).getSession()) {
-                s.addPasswordIdentity("bad password");
-                assertAuthenticationResult(getCurrentTestName(), s.auth(), false);
-            }
-        }
-    }
-
-    @Test
-    public void testChangeUser() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
-
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
-
-                for (String username : new String[]{"user1", "user2"}) {
-                    assertAuthenticationResult(username, authPassword(s, username, "the-password"), false);
-                }
-
-                // Note that WAIT_AUTH flag should be false, but since the internal
-                // authentication future is not updated, it's still returned
-                assertEquals("Mismatched client session close mask", ClientSession.CLOSED | ClientSession.WAIT_AUTH, s.waitFor(ClientSession.CLOSED, 1000));
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testChangePassword() throws Exception {
-        final PasswordAuthenticator delegate = sshd.getPasswordAuthenticator();
-        final AtomicInteger attemptsCount = new AtomicInteger(0);
-        sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
-            @Override
-            public boolean authenticate(String username, String password, ServerSession session)
-                    throws PasswordChangeRequiredException {
-                if (attemptsCount.incrementAndGet() == 1) {
-                    throw new PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
-                }
-
-                return delegate.authenticate(username, password, session);
-            }
-        });
-
-        final AtomicInteger changesCount = new AtomicInteger(0);
-        sshd.setUserAuthFactories(Collections.<NamedFactory<org.apache.sshd.server.auth.UserAuth>>singletonList(
-            new org.apache.sshd.server.auth.UserAuthPasswordFactory() {
-                @Override
-                public org.apache.sshd.server.auth.UserAuth create() {
-                    return new org.apache.sshd.server.auth.UserAuthPassword() {
-                        @Override
-                        protected Boolean handleClientPasswordChangeRequest(
-                                Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword)
-                                        throws Exception {
-                            if (changesCount.incrementAndGet() == 1) {
-                                assertNotEquals("Non-different passwords", oldPassword, newPassword);
-                                return checkPassword(buffer, session, username, newPassword);
-                            } else {
-                                return super.handleClientPasswordChangeRequest(buffer, session, username, oldPassword, newPassword);
-                            }
-                        }
-                    };
-                }
-            }
-        ));
-        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
-
-        try (SshClient client = setupTestClient()) {
-            final AtomicInteger updatesCount = new AtomicInteger(0);
-            client.setUserInteraction(new UserInteraction() {
-                @Override
-                public void welcome(ClientSession session, String banner, String lang) {
-                    // ignored
-                }
-
-                @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", ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
-                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
-                    return getCurrentTestName();
-                }
-            });
-
-            final AtomicInteger sentCount = new AtomicInteger(0);
-            client.setUserAuthFactories(Collections.<NamedFactory<org.apache.sshd.client.auth.UserAuth>>singletonList(
-                new org.apache.sshd.client.auth.UserAuthPasswordFactory() {
-                    @Override
-                    public org.apache.sshd.client.auth.UserAuth create() {
-                        return new org.apache.sshd.client.auth.UserAuthPassword() {
-                            @Override
-                            protected void sendPassword(Buffer buffer, ClientSession session, String oldPassword, String newPassword) throws IOException {
-                                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) {
-                                    super.sendPassword(buffer, session, getClass().getName(), newPassword);
-                                } else {
-                                    super.sendPassword(buffer, session, oldPassword, newPassword);
-                                }
-                            }
-                        };
-                    }
-            }));
-            FactoryManagerUtils.updateProperty(client, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
-
-            client.start();
-
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                s.addPasswordIdentity(getCurrentTestName());
-                s.auth().verify(11L, TimeUnit.SECONDS);
-                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()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
-                assertAuthenticationResult(getCurrentTestName(), authPassword(s, getCurrentTestName(), getCurrentTestName()), false);
-                s.close(true);
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testAuthKeyPassword() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
-
-                KeyPair pair = createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
-                assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
-                assertAuthenticationResult(UserAuthMethodFactory.PASSWORD, authPassword(s, getCurrentTestName(), getCurrentTestName()), true);
-                s.close(true);
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testAuthKeyInteractive() throws Exception {
-        try (SshClient client = setupTestClient()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
-            client.start();
-
-            try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
-
-                KeyPair pair = createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
-                assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
-                assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE, authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
-
-                s.close(true);
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testAuthPasswordChangeRequest() throws Exception {
-        final PasswordAuthenticator delegate = ValidateUtils.checkNotNull(sshd.getPasswordAuthenticator(), "No password authenticator");
-        final AtomicInteger attemptsCount = new AtomicInteger(0);
-        sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
-            @Override
-            public boolean authenticate(String username, String password, ServerSession session)
-                    throws PasswordChangeRequiredException {
-                if (attemptsCount.incrementAndGet() == 1) {
-                    throw new PasswordChangeRequiredException(attemptsCount.toString(), getCurrentTestName(), ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE);
-                }
-
-                return delegate.authenticate(username, password, session);
-            }
-        });
-        FactoryManagerUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
-
-        try (SshClient client = setupTestClient()) {
-            final AtomicInteger updatesCount = new AtomicInteger(0);
-            client.setUserInteraction(new UserInteraction() {
-                @Override
-                public void welcome(ClientSession session, String banner, String lang) {
-                    // ignored
-                }
-
-                @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", ServerFactoryManager.DEFAULT_WELCOME_BANNER_LANGUAGE, lang);
-                    assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
-                    return getCurrentTestName();
-                }
-            });
-            FactoryManagerUtils.updateProperty(client, ServerFactoryManager.AUTH_METHODS, UserAuthPasswordFactory.NAME);
-
-            client.start();
-
-            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                s.addPasswordIdentity(getCurrentTestName());
-                s.auth().verify(11L, TimeUnit.SECONDS);
-                assertEquals("No password change request generated", 2, attemptsCount.get());
-                assertEquals("No user interaction invoked", 1, updatesCount.get());
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    private static void assertAuthenticationResult(String message, AuthFuture future, boolean expected) throws IOException {
-        assertTrue(message + ": failed to get result on time", future.await(5L, TimeUnit.SECONDS));
-        assertEquals(message + ": mismatched authentication result", expected, future.isSuccess());
-    }
-
-    private AuthFuture authPassword(ClientSession s, String user, String pswd) throws IOException {
-        s.setUsername(user);
-        return s.getService(ClientUserAuthServiceOld.class)
-                .auth(new UserAuthPassword(s, "ssh-connection", pswd));
-    }
-
-    private AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
-        s.setUsername(user);
-        return s.getService(ClientUserAuthServiceOld.class)
-                .auth(new UserAuthKeyboardInteractive(s, "ssh-connection", pswd));
-    }
-
-    private AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
-        s.setUsername(user);
-        return s.getService(ClientUserAuthServiceOld.class)
-                .auth(new UserAuthPublicKey(s, "ssh-connection", pair));
-    }
-
-    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
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java b/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
index ae882e2..308d23b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/PortForwardingTest.java
@@ -87,6 +87,10 @@ public class PortForwardingTest extends BaseTestSupport {
     private IoAcceptor acceptor;
     private SshClient client;
 
+    public PortForwardingTest() {
+        super();
+    }
+
     @Before
     public void setUp() throws Exception {
         sshd = setupTestServer();
@@ -211,9 +215,9 @@ public class PortForwardingTest extends BaseTestSupport {
                 int n = input.read(buf);
                 String res = new String(buf, 0, n, StandardCharsets.UTF_8);
                 assertEquals("Mismatched data", expected, res);
+            } finally {
+                session.delPortForwardingR(forwardedPort);
             }
-
-            session.delPortForwardingR(forwardedPort);
         } finally {
             session.disconnect();
         }
@@ -248,8 +252,9 @@ public class PortForwardingTest extends BaseTestSupport {
                 int n = input.read(buf);
                 String res = new String(buf, 0, n, StandardCharsets.UTF_8);
                 assertEquals("Mismatched data", expected, res);
+            } finally {
+                session.delPortForwardingR(TEST_LOCALHOST, forwardedPort);
             }
-            session.delPortForwardingR(TEST_LOCALHOST, forwardedPort);
         } finally {
             session.disconnect();
         }
@@ -277,10 +282,9 @@ public class PortForwardingTest extends BaseTestSupport {
                 int n = input.read(buf);
                 String res = new String(buf, 0, n);
                 assertEquals("Mismatched data", expected, res);
+            } finally {
+                session.stopRemotePortForwarding(remote);
             }
-
-            session.stopRemotePortForwarding(remote);
-            session.close(false).await();
         }
     }
 
@@ -309,10 +313,9 @@ public class PortForwardingTest extends BaseTestSupport {
                     String res = new String(buf, 0, n);
                     assertEquals("Mismatched data at iteration #" + i, expected, res);
                 }
+            } finally {
+                session.stopRemotePortForwarding(remote);
             }
-
-            session.stopRemotePortForwarding(remote);
-            session.close(false).await();
         }
     }
 
@@ -339,9 +342,9 @@ public class PortForwardingTest extends BaseTestSupport {
                 int n = input.read(buf);
                 String res = new String(buf, 0, n);
                 assertEquals("Mismatched data", expected, res);
+            } finally {
+                session.delPortForwardingL(forwardedPort);
             }
-
-            session.delPortForwardingL(forwardedPort);
         } finally {
             session.disconnect();
         }
@@ -370,10 +373,9 @@ public class PortForwardingTest extends BaseTestSupport {
                 int n = input.read(buf);
                 String res = new String(buf, 0, n);
                 assertEquals("Mismatched data", expected, res);
+            } finally {
+                session.stopLocalPortForwarding(bound);
             }
-
-            session.stopLocalPortForwarding(bound);
-            session.close(false).await();
         }
     }
 
@@ -388,21 +390,19 @@ public class PortForwardingTest extends BaseTestSupport {
 
             SshdSocketAddress bound2 = session.startLocalPortForwarding(local, remote);
             session.stopLocalPortForwarding(bound2);
-
-            session.close(false).await();
         }
     }
 
     @Test
     public void testLocalForwardingNativeBigPayload() throws Exception {
         try (ClientSession session = createNativeSession()) {
-            SshdSocketAddress local = new SshdSocketAddress("", 0);
-            SshdSocketAddress remote = new SshdSocketAddress(TEST_LOCALHOST, echoPort);
-            SshdSocketAddress bound = session.startLocalPortForwarding(local, remote);
-
             String expected = getCurrentTestName();
             byte[] bytes = expected.getBytes(StandardCharsets.UTF_8);
             byte[] buf = new byte[bytes.length + Long.SIZE];
+
+            SshdSocketAddress local = new SshdSocketAddress("", 0);
+            SshdSocketAddress remote = new SshdSocketAddress(TEST_LOCALHOST, echoPort);
+            SshdSocketAddress bound = session.startLocalPortForwarding(local, remote);
             try (Socket s = new Socket(bound.getHostName(), bound.getPort());
                  OutputStream output = s.getOutputStream();
                  InputStream input = s.getInputStream()) {
@@ -417,10 +417,9 @@ public class PortForwardingTest extends BaseTestSupport {
                     String res = new String(buf, 0, n);
                     assertEquals("Mismatched data at iteration #" + i, expected, res);
                 }
+            } finally {
+                session.stopLocalPortForwarding(bound);
             }
-
-            session.stopLocalPortForwarding(bound);
-            session.close(false).await();
         }
     }
 
@@ -448,12 +447,10 @@ public class PortForwardingTest extends BaseTestSupport {
                 }
                 channel.close(false);
             }
-
-            session.close(false).await();
         }
     }
 
-    @Test(timeout = 30000)
+    @Test(timeout = 45000)
     public void testRemoteForwardingWithDisconnect() throws Exception {
         Session session = createSession();
         try {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java b/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
index 1e789f3..0ec059c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/ProxyTest.java
@@ -99,30 +99,32 @@ public class ProxyTest extends BaseTestSupport {
     @Test
     public void testSocksProxy() throws Exception {
         try (ClientSession session = createNativeSession()) {
-            SshdSocketAddress dynamic = session.startDynamicPortForwarding(new SshdSocketAddress(TEST_LOCALHOST, 0));
-
             String expected = getCurrentTestName();
             byte[] bytes = expected.getBytes(StandardCharsets.UTF_8);
             byte[] buf = new byte[bytes.length + Long.SIZE];
-            for (int i = 0; i < 10; i++) {
-                try (Socket s = new Socket(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TEST_LOCALHOST, dynamic.getPort())))) {
-                    s.connect(new InetSocketAddress(TEST_LOCALHOST, echoPort));
-                    s.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L));
+            SshdSocketAddress dynamic = session.startDynamicPortForwarding(new SshdSocketAddress(TEST_LOCALHOST, 0));
+
+            try {
+                for (int i = 0; i < 10; i++) {
+                    try (Socket s = new Socket(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TEST_LOCALHOST, dynamic.getPort())))) {
+                        s.connect(new InetSocketAddress(TEST_LOCALHOST, echoPort));
+                        s.setSoTimeout((int) TimeUnit.SECONDS.toMillis(10L));
 
-                    try (OutputStream sockOut = s.getOutputStream();
-                         InputStream sockIn = s.getInputStream()) {
+                        try (OutputStream sockOut = s.getOutputStream();
+                             InputStream sockIn = s.getInputStream()) {
 
-                        sockOut.write(bytes);
-                        sockOut.flush();
+                            sockOut.write(bytes);
+                            sockOut.flush();
 
-                        int l = sockIn.read(buf);
-                        assertEquals("Mismatched data at iteration " + i, expected, new String(buf, 0, l));
+                            int l = sockIn.read(buf);
+                            assertEquals("Mismatched data at iteration " + i, expected, new String(buf, 0, l));
+                        }
                     }
                 }
+            } finally {
+                session.stopDynamicPortForwarding(dynamic);
             }
 
-            session.stopDynamicPortForwarding(dynamic);
-
             try {
                 try (Socket s = new Socket(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TEST_LOCALHOST, dynamic.getPort())))) {
                     s.connect(new InetSocketAddress(TEST_LOCALHOST, echoPort));
@@ -133,8 +135,6 @@ public class ProxyTest extends BaseTestSupport {
             } catch (IOException e) {
                 // expected
             }
-
-            session.close(false).await();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java b/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
index e87d425..3db63b8 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
@@ -33,8 +33,8 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.server.ServerFactoryManager;
 import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.CachingPublicKeyAuthenticator;
 import org.apache.sshd.server.auth.UserAuthPublicKeyFactory;
+import org.apache.sshd.server.auth.pubkey.CachingPublicKeyAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.apache.sshd.server.session.ServerSession;
@@ -94,8 +94,9 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
             @SuppressWarnings("synthetic-access")
             @Override
             public boolean authenticate(String username, PublicKey key, ServerSession session) {
-                count.putIfAbsent(KeyUtils.getFingerPrint(key), new AtomicInteger());
-                count.get(KeyUtils.getFingerPrint(key)).incrementAndGet();
+                String fp = KeyUtils.getFingerPrint(key);
+                count.putIfAbsent(fp, new AtomicInteger());
+                count.get(fp).incrementAndGet();
                 return key.equals(pairRsa.getPublic());
             }
         });
@@ -109,19 +110,21 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
                 session.addPublicKeyIdentity(pairRsa);
                 session.auth().verify(5L, TimeUnit.SECONDS);
 
-                assertEquals(2, count.size());
-                assertTrue(count.containsKey(KeyUtils.getFingerPrint(pairRsaBad.getPublic())));
-                assertTrue(count.containsKey(KeyUtils.getFingerPrint(pairRsa.getPublic())));
-                assertEquals(1, count.get(KeyUtils.getFingerPrint(pairRsaBad.getPublic())).get());
-                assertEquals(1, count.get(KeyUtils.getFingerPrint(pairRsa.getPublic())).get());
-                client.close(false).await();
+                assertEquals("Mismatched authentication invocations count", 2, count.size());
+
+                String fpBad = KeyUtils.getFingerPrint(pairRsaBad.getPublic());
+                String fpGood = KeyUtils.getFingerPrint(pairRsa.getPublic());
+                assertTrue("Missing bad public key", count.containsKey(fpBad));
+                assertTrue("Missing good public key", count.containsKey(fpGood));
+                assertEquals("Mismatched bad key authentication attempts", 1, count.get(fpBad).get());
+                assertEquals("Mismatched good key authentication attempts", 1, count.get(fpGood).get());
             } finally {
                 client.stop();
             }
         }
 
-        Thread.sleep(100);
-        assertTrue(auth.getCache().isEmpty());
+        Thread.sleep(100L);
+        assertTrue("Cache not empty", auth.getCache().isEmpty());
     }
 
     @Test
@@ -131,8 +134,9 @@ public class SinglePublicKeyAuthTest extends BaseTestSupport {
             @SuppressWarnings("synthetic-access")
             @Override
             public boolean authenticate(String username, PublicKey key, ServerSession session) {
-                count.putIfAbsent(KeyUtils.getFingerPrint(key), new AtomicInteger());
-                count.get(KeyUtils.getFingerPrint(key)).incrementAndGet();
+                String fp = KeyUtils.getFingerPrint(key);
+                count.putIfAbsent(fp, new AtomicInteger());
+                count.get(fp).incrementAndGet();
                 return key.equals(pairRsa.getPublic());
             }
         };

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 451eb31..f861ca1 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -96,6 +96,7 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.channel.ChannelSessionFactory;
@@ -503,7 +504,7 @@ public class ClientTest extends BaseTestSupport {
                 // That's ok, the channel is being closed by the other side
             }
             assertEquals(ClientChannel.CLOSED, channel.waitFor(ClientChannel.CLOSED, 0) & ClientChannel.CLOSED);
-            session.close(false).await();
+            assertTrue("Failed to close session on time", session.close(false).await(7L, TimeUnit.SECONDS));
         } finally {
             client.stop();
         }
@@ -614,7 +615,7 @@ public class ClientTest extends BaseTestSupport {
             channel.setOut(out);
             channel.setErr(err);
             channel.open().verify(5L, TimeUnit.SECONDS);
-            channel.close(false).await();
+            assertTrue("Failed to close channel on time", channel.close(false).await(7L, TimeUnit.SECONDS));
         } finally {
             client.stop();
         }
@@ -938,7 +939,7 @@ public class ClientTest extends BaseTestSupport {
     }
 
     @Test   // see SSHD-504
-    public void testKeyboardInteractivePasswordPromptLocationIndependence() throws Exception {
+    public void testDefaultKeyboardInteractivePasswordPromptLocationIndependence() throws Exception {
         final Collection<String> mismatchedPrompts = new LinkedList<String>();
         client.setUserAuthFactories(Arrays.<NamedFactory<UserAuth>>asList(new UserAuthKeyboardInteractiveFactory() {
             @Override
@@ -991,29 +992,21 @@ public class ClientTest extends BaseTestSupport {
                             }
                         }
                 ));
-        sshd.setUserAuthFactories(Arrays.<NamedFactory<org.apache.sshd.server.auth.UserAuth>>asList(
-                new org.apache.sshd.server.auth.UserAuthKeyboardInteractiveFactory() {
-                    private int xformerIndex;
+        sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
+            private int xformerIndex;
 
-                    @Override
-                    public org.apache.sshd.server.auth.UserAuth create() {
-                        return new org.apache.sshd.server.auth.UserAuthKeyboardInteractive() {
-
-                            @SuppressWarnings("synthetic-access")
-                            @Override
-                            protected String getInteractionPrompt() {
-                                String original = super.getInteractionPrompt();
-                                if (xformerIndex < xformers.size()) {
-                                    Transformer<String, String> x = xformers.get(xformerIndex);
-                                    xformerIndex++;
-                                    return x.transform(original);
-                                } else {
-                                    return original;
-                                }
-                            }
-                        };
-                    }
-                }));
+            @Override
+            protected String getInteractionPrompt(ServerSession session) {
+                String original = super.getInteractionPrompt(session);
+                if (xformerIndex < xformers.size()) {
+                    Transformer<String, String> x = xformers.get(xformerIndex);
+                    xformerIndex++;
+                    return x.transform(original);
+                } else {
+                    return original;
+                }
+            }
+        });
 
         try {
             for (int index = 0; index < xformers.size(); index++) {
@@ -1042,15 +1035,14 @@ public class ClientTest extends BaseTestSupport {
     }
 
     @Test
-    public void testKeyboardInteractiveWithFailures() throws Exception {
-        final AtomicInteger count = new AtomicInteger();
-        final int MAX_PROMPTS = 3;
-        FactoryManagerUtils.updateProperty(client, ClientFactoryManager.PASSWORD_PROMPTS, MAX_PROMPTS);
-
+    public void testDefaultKeyboardInteractiveWithFailures() throws Exception {
         client.setUserAuthFactories(Collections.<NamedFactory<UserAuth>>singletonList(UserAuthKeyboardInteractiveFactory.INSTANCE));
 
+        final AtomicInteger count = new AtomicInteger();
         final AtomicReference<ClientSession> interactionSessionHolder = new AtomicReference<>(null);
         client.setUserInteraction(new UserInteraction() {
+            private final String[] BAD_RESPONSE = { "bad" };
+
             @Override
             public void welcome(ClientSession session, String banner, String lang) {
                 validateSession("welcome", session);
@@ -1060,7 +1052,7 @@ public class ClientTest extends BaseTestSupport {
             public String[] interactive(ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
                 validateSession("interactive", session);
                 count.incrementAndGet();
-                return new String[]{"bad"};
+                return BAD_RESPONSE;
             }
 
             @Override
@@ -1075,13 +1067,17 @@ public class ClientTest extends BaseTestSupport {
                 }
             }
         });
+
+        final int MAX_PROMPTS = 3;
+        FactoryManagerUtils.updateProperty(client, ClientFactoryManager.PASSWORD_PROMPTS, MAX_PROMPTS);
+
         client.start();
 
         try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
             assertNotNull("Client session creation not signalled", clientSessionHolder.get());
 
             AuthFuture future = session.auth();
-            future.await();
+            assertTrue("Failed to complete authentication on time", future.await(15L, TimeUnit.SECONDS));
             assertTrue("Unexpected authentication success", future.isFailure());
             assertEquals("Mismatched authentication retry count", MAX_PROMPTS, count.get());
         } finally {
@@ -1092,7 +1088,7 @@ public class ClientTest extends BaseTestSupport {
     }
 
     @Test
-    public void testKeyboardInteractiveInSessionUserInteractive() throws Exception {
+    public void testDefaultKeyboardInteractiveInSessionUserInteractive() throws Exception {
         final AtomicInteger count = new AtomicInteger();
         final int MAX_PROMPTS = 3;
         FactoryManagerUtils.updateProperty(client, ClientFactoryManager.PASSWORD_PROMPTS, MAX_PROMPTS);
@@ -1122,7 +1118,7 @@ public class ClientTest extends BaseTestSupport {
             });
 
             AuthFuture future = session.auth();
-            future.await();
+            assertTrue("Failed to complete authentication on time", future.await(15L, TimeUnit.SECONDS));
             assertTrue("Authentication not marked as success", future.isSuccess());
             assertFalse("Authentication marked as failure", future.isFailure());
             assertEquals("Mismatched authentication attempts count", 1, count.get());


[3/3] mina-sshd git commit: [SSHD-563] Provide server-side KeyboardInteractiveAuthenticator

Posted by lg...@apache.org.
[SSHD-563] Provide server-side KeyboardInteractiveAuthenticator


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/75643fcb
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/75643fcb
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/75643fcb

Branch: refs/heads/master
Commit: 75643fcb1ce1316ea1b63815731ca5ee885db52e
Parents: d19f5ec
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Thu Sep 17 08:59:46 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Thu Sep 17 08:59:46 2015 +0300

----------------------------------------------------------------------
 pom.xml                                         |   2 -
 .../sshd/client/ClientFactoryManager.java       |   2 +-
 .../sshd/client/auth/AbstractUserAuth.java      |  27 +-
 .../org/apache/sshd/client/auth/UserAuth.java   |   4 +-
 .../auth/UserAuthKeyboardInteractive.java       | 102 +++-
 .../sshd/client/auth/UserAuthPassword.java      |  12 +-
 .../sshd/client/auth/UserAuthPublicKey.java     |  83 +--
 .../client/auth/pubkey/KeyAgentIdentity.java    |  49 ++
 .../client/auth/pubkey/KeyPairIdentity.java     |  60 +++
 .../client/auth/pubkey/PublicKeyIdentity.java   |  42 ++
 .../client/config/hosts/HostConfigEntry.java    |   4 +-
 .../apache/sshd/client/future/AuthFuture.java   |   7 +
 .../sshd/client/future/DefaultAuthFuture.java   |   6 +-
 .../sshd/client/session/ClientSessionImpl.java  |   2 +-
 .../sshd/common/auth/UserAuthInstance.java      |  40 ++
 .../apache/sshd/common/auth/UsernameHolder.java |  31 ++
 .../org/apache/sshd/common/session/Session.java |  14 +-
 .../org/apache/sshd/server/ServerBuilder.java   |  14 +
 .../sshd/server/ServerFactoryManager.java       |  11 +
 .../java/org/apache/sshd/server/SshServer.java  |  11 +
 .../sshd/server/auth/AbstractUserAuth.java      |  24 +-
 .../server/auth/AbstractUserAuthPassword.java   |  91 ----
 .../auth/CachingPublicKeyAuthenticator.java     |  76 ---
 .../org/apache/sshd/server/auth/UserAuth.java   |  14 +-
 .../auth/UserAuthKeyboardInteractive.java       |  93 ++--
 .../apache/sshd/server/auth/UserAuthNone.java   |   4 +-
 .../sshd/server/auth/UserAuthPassword.java      |  83 ++-
 .../sshd/server/auth/UserAuthPublicKey.java     |  33 +-
 .../sshd/server/auth/gss/UserAuthGSS.java       |  39 +-
 ...DefaultKeyboardInteractiveAuthenticator.java | 117 +++++
 .../auth/keyboard/InteractiveChallenge.java     | 130 +++++
 .../KeyboardInteractiveAuthenticator.java       |  58 ++
 .../sshd/server/auth/keyboard/PromptEntry.java  | 101 ++++
 .../PasswordChangeRequiredException.java        |   8 +-
 .../pubkey/CachingPublicKeyAuthenticator.java   |  75 +++
 .../DefaultAuthorizedKeysAuthenticator.java     |   4 +-
 .../server/session/ServerUserAuthService.java   |   6 +-
 .../org/apache/sshd/AuthenticationTest.java     | 377 -------------
 .../org/apache/sshd/PortForwardingTest.java     |  49 +-
 .../test/java/org/apache/sshd/ProxyTest.java    |  32 +-
 .../apache/sshd/SinglePublicKeyAuthTest.java    |  30 +-
 .../java/org/apache/sshd/client/ClientTest.java |  64 ++-
 .../sshd/common/auth/AuthenticationTest.java    | 523 +++++++++++++++++++
 .../apache/sshd/common/cipher/CipherTest.java   |   2 +-
 .../java/org/apache/sshd/server/ServerTest.java |   6 +-
 .../sshd/git/pack/GitPackCommandTest.java       |   2 +-
 .../apache/sshd/git/pgm/GitPgmCommandTest.java  |   2 +-
 47 files changed, 1684 insertions(+), 882 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d48cd3d..055e923 100644
--- a/pom.xml
+++ b/pom.xml
@@ -385,8 +385,6 @@
 							<exclude>src/test/resources/**</exclude>
 							<exclude>**/stty-output-*.txt</exclude>
 							<exclude>**/big-msg.txt</exclude>
-							<exclude>sshd-sftp/**</exclude>
-							<exclude>sshd-pam/**</exclude>
 								<!-- Eclipse files -->
 							<exclude>.metadata/**</exclude>
 							<exclude>.recommenders/**</exclude>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 5fcc068..11000d2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -64,7 +64,7 @@ public interface ClientFactoryManager extends FactoryManager {
     String PREFERRED_AUTHS = "preferred-auths";
 
     /**
-     * Specifies the number of password prompts before giving up.
+     * Specifies the number of interactive prompts before giving up.
      * The argument to this keyword must be an integer.
      */
     String PASSWORD_PROMPTS = "password-prompts";

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/AbstractUserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/AbstractUserAuth.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/AbstractUserAuth.java
index 783c5f2..119310f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/AbstractUserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/AbstractUserAuth.java
@@ -22,21 +22,19 @@ package org.apache.sshd.client.auth;
 import java.util.Collection;
 
 import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.session.ClientSessionHolder;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public abstract class AbstractUserAuth
-                extends AbstractLoggingBean
-                implements UserAuth, ClientSessionHolder {
+public abstract class AbstractUserAuth extends AbstractLoggingBean implements UserAuth {
+    private final String name;
     private ClientSession session;
     private String service;
 
-    protected AbstractUserAuth() {
-        super();
+    protected AbstractUserAuth(String name) {
+        this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No name");
     }
 
     @Override
@@ -44,6 +42,16 @@ public abstract class AbstractUserAuth
         return session;
     }
 
+    @Override
+    public ClientSession getSession() {
+        return getClientSession();
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
     public String getService() {
         return service;
     }
@@ -51,6 +59,11 @@ public abstract class AbstractUserAuth
     @Override
     public void init(ClientSession session, String service, Collection<?> identities) throws Exception {
         this.session = ValidateUtils.checkNotNull(session, "No client session");
-        this.service = service;
+        this.service = ValidateUtils.checkNotNullAndNotEmpty(service, "No service");
+    }
+
+    @Override
+    public String toString() {
+        return getName() + ": " + getSession() + "[" + getService() + "]";
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuth.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuth.java
index c699077..5bf1037 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuth.java
@@ -21,6 +21,8 @@ package org.apache.sshd.client.auth;
 import java.util.Collection;
 
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.common.auth.UserAuthInstance;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
@@ -28,7 +30,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface UserAuth {
+public interface UserAuth extends ClientSessionHolder, UserAuthInstance<ClientSession> {
 
     void init(ClientSession session, String service, Collection<?> identities) throws Exception;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
index 160cdb6..30f4e45 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
@@ -26,8 +26,10 @@ import java.util.List;
 
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.SshConstants;
 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.buffer.BufferUtils;
 
@@ -38,14 +40,47 @@ import org.apache.sshd.common.util.buffer.BufferUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthKeyboardInteractive extends AbstractUserAuth {
+    public static final String NAME = UserAuthKeyboardInteractiveFactory.NAME;
+
+    public static final String INTERACTIVE_LANGUAGE_TAG = "kb-client-interactive-language-tag";
+
+    /*
+     * As per RFC-4256:
+     *
+     *      The language tag is deprecated and SHOULD be the empty string.  It
+     *      may be removed in a future revision of this specification.  Instead,
+     *      the server SHOULD select the language to be used based on the tags
+     *      communicated during key exchange
+     */
+    public static final String DEFAULT_INTERACTIVE_LANGUAGE_TAG = "";
+
+    public static final String INTERACTIVE_SUBMETHODS = "kb-client-interactive-sub-methods";
+
+    /*
+     * As per RFC-4256:
+     *
+     *      The submethods field is included so the user can give a hint of which
+     *      actual methods he wants to use.  It is a comma-separated list of
+     *      authentication submethods (software or hardware) that the user
+     *      prefers.  If the client has knowledge of the submethods preferred by
+     *      the user, presumably through a configuration setting, it MAY use the
+     *      submethods field to pass this information to the server.  Otherwise,
+     *      it MUST send the empty string.
+     *
+     *      The actual names of the submethods is something the user and the
+     *      server need to agree upon.
+     *
+     *      Server interpretation of the submethods field is implementation-
+     *      dependent.
+     */
+    public static final String DEFAULT_INTERACTIVE_SUBMETHODS = "";
 
     private Iterator<String> passwords;
-    private String current;
-    private int nbTrials;
     private int maxTrials;
+    private int nbTrials;
 
     public UserAuthKeyboardInteractive() {
-        super();
+        super(NAME);
     }
 
     @Override
@@ -60,6 +95,7 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
         }
         passwords = pwds.iterator();
         maxTrials = session.getIntProperty(ClientFactoryManager.PASSWORD_PROMPTS, ClientFactoryManager.DEFAULT_PASSWORD_PROMPTS);
+        ValidateUtils.checkTrue(maxTrials > 0, "Non-positive max. trials: %d", maxTrials);
     }
 
     @Override
@@ -69,42 +105,43 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
         String service = getService();
 
         if (buffer == null) {
-            if (passwords.hasNext()) {
-                current = passwords.next();
-            } else if (nbTrials++ < maxTrials) {
-                current = null;
-            } else {
-                return false;
-            }
-
+            String name = getName();
             if (log.isDebugEnabled()) {
-                log.debug("process({}@{})[{}] Send SSH_MSG_USERAUTH_REQUEST for keyboard-interactive",
-                          username, session, service);
+                log.debug("process({}@{})[{}] Send SSH_MSG_USERAUTH_REQUEST for {}",
+                          username, session, service, name);
             }
             buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST,
-                                username.length() + service.length() + UserAuthKeyboardInteractiveFactory.NAME.length() + Integer.SIZE);
+                                username.length() + service.length() + name.length() + Integer.SIZE);
             buffer.putString(username);
             buffer.putString(service);
-            buffer.putString(UserAuthKeyboardInteractiveFactory.NAME);
-            buffer.putString("");
-            buffer.putString("");
+            buffer.putString(name);
+            buffer.putString(getExchangeLanguageTag(session));
+            buffer.putString(getExchangeSubMethods(session));
             session.writePacket(buffer);
             return true;
         }
 
         int cmd = buffer.getUByte();
         if (cmd == SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST) {
+            nbTrials++;
+            if (nbTrials > maxTrials) {
+                if (log.isDebugEnabled()) {
+                    log.debug("process({})[{}] Reject SSH_MSG_USERAUTH_INFO_REQUEST for {} num. trials ({}) exceeds max({})",
+                              session, service, getName(), nbTrials, maxTrials);
+                }
+                return false;
+            }
+
             if (log.isDebugEnabled()) {
-                log.debug("process({}@{})[{}] Received SSH_MSG_USERAUTH_INFO_REQUEST for keyboard-interactive",
-                          username, session, service);
+                log.debug("process({})[{}] Received SSH_MSG_USERAUTH_INFO_REQUEST for {}", session, service, getName());
             }
 
             String name = buffer.getString();
             String instruction = buffer.getString();
             String language_tag = buffer.getString();
             if (log.isTraceEnabled()) {
-                log.trace("process({}@{})[{}] SSH_MSG_USERAUTH_INFO_REQUEST name={} instruction={} language={}",
-                          username, session, service, name, instruction, language_tag);
+                log.trace("process({})[{}] SSH_MSG_USERAUTH_INFO_REQUEST name={} instruction={} language={}",
+                          session, service, name, instruction, language_tag);
             }
 
             int num = buffer.getInt();
@@ -117,8 +154,8 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
             }
 
             if (log.isTraceEnabled()) {
-                log.trace("process({}@{})[{}] Prompt: {}", username, session, service, Arrays.toString(prompt));
-                log.trace("process({}@{})[{}] Echo: {}", username, session, service, Arrays.toString(echo));
+                log.trace("process({})[{}] Prompt: {}", session, service, Arrays.toString(prompt));
+                log.trace("process({})[{}] Echo: {}", session, service, Arrays.toString(echo));
             }
 
             String[] rep = getUserResponses(name, instruction, language_tag, prompt, echo);
@@ -136,8 +173,7 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
              * However it is the server's (!) responsibility to fail, so we only warn...
              */
             if (num != rep.length) {
-                log.warn("process({}@{}) Mismatched prompts ({}) vs. responses count ({})",
-                         username, session, num, rep.length);
+                log.warn("process({}) Mismatched prompts ({}) vs. responses count ({})", session, num, rep.length);
             }
 
             buffer = session.prepareBuffer(SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE, BufferUtils.clear(buffer));
@@ -148,11 +184,23 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
             session.writePacket(buffer);
             return true;
         }
-        throw new IllegalStateException("process(" + username + "@" + session + ")[" + service + ") received unknown packet: cmd=" + cmd);
+        throw new IllegalStateException("process(" + session + ")[" + service + ") received unknown packet: cmd=" + cmd);
+    }
+
+    protected String getExchangeLanguageTag(ClientSession session) {
+        return FactoryManagerUtils.getStringProperty(session, INTERACTIVE_LANGUAGE_TAG, DEFAULT_INTERACTIVE_LANGUAGE_TAG);
+    }
+
+    protected String getExchangeSubMethods(ClientSession session) {
+        return FactoryManagerUtils.getStringProperty(session, INTERACTIVE_SUBMETHODS, DEFAULT_INTERACTIVE_SUBMETHODS);
     }
 
     protected String getCurrentPasswordCandidate() {
-        return current;
+        if ((passwords != null) && passwords.hasNext()) {
+            return passwords.next();
+        } else {
+            return null;
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPassword.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPassword.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPassword.java
index 0214bc6..4924f0c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPassword.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPassword.java
@@ -37,12 +37,13 @@ import org.apache.sshd.common.util.buffer.BufferUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthPassword extends AbstractUserAuth {
+    public static final String NAME = UserAuthPasswordFactory.NAME;
 
     private Iterator<String> passwords;
     private String current;
 
     public UserAuthPassword() {
-        super();
+        super(NAME);
     }
 
     @Override
@@ -69,7 +70,7 @@ public class UserAuthPassword extends AbstractUserAuth {
             if (passwords.hasNext()) {
                 current = passwords.next();
                 buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST,
-                                    username.length() + service.length() + UserAuthPasswordFactory.NAME.length() + current.length() + Integer.SIZE);
+                                    username.length() + service.length() + getName().length() + current.length() + Integer.SIZE);
                 sendPassword(buffer, session, current, current);
                 return true;
             }
@@ -121,16 +122,17 @@ public class UserAuthPassword extends AbstractUserAuth {
     protected void sendPassword(Buffer buffer, ClientSession session, String oldPassword, String newPassword) throws IOException {
         String username = session.getUsername();
         String service = getService();
+        String name = getName();
         boolean modified = !Objects.equals(oldPassword, newPassword);
         if (log.isDebugEnabled()) {
-            log.debug("sendPassword({}@{})[{}] send SSH_MSG_USERAUTH_REQUEST for password - modified={}",
-                      username, session, service, modified);
+            log.debug("sendPassword({}@{})[{}] send SSH_MSG_USERAUTH_REQUEST for {} - modified={}",
+                      username, session, service, name, modified);
         }
 
         buffer = session.prepareBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST, BufferUtils.clear(buffer));
         buffer.putString(username);
         buffer.putString(service);
-        buffer.putString(UserAuthPasswordFactory.NAME);
+        buffer.putString(name);
         buffer.putBoolean(modified);
         // see RFC-4252 section 8
         buffer.putString(oldPassword);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
index 6a6f366..525eb43 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
@@ -28,15 +28,16 @@ import java.util.List;
 
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity;
+import org.apache.sshd.client.auth.pubkey.KeyPairIdentity;
+import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.FactoryManager;
-import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.util.Pair;
-import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -47,13 +48,14 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthPublicKey extends AbstractUserAuth {
+    public static final String NAME = UserAuthPublicKeyFactory.NAME;
 
     private SshAgent agent;
     private Iterator<PublicKeyIdentity> keys;
     private PublicKeyIdentity current;
 
     public UserAuthPublicKey() {
-        super();
+        super(NAME);
     }
 
     @Override
@@ -99,15 +101,16 @@ public class UserAuthPublicKey extends AbstractUserAuth {
                 current = keys.next();
                 PublicKey key = current.getPublicKey();
                 String algo = KeyUtils.getKeyType(key);
+                String name = getName();
                 if (log.isDebugEnabled()) {
-                    log.debug("process({}@{})[{}] Send SSH_MSG_USERAUTH_REQUEST request publickey algo={}",
-                              username, session, service, algo);
+                    log.debug("process({}@{})[{}] Send SSH_MSG_USERAUTH_REQUEST request {} algo={}",
+                              username, session, service, name, algo);
                 }
 
                 buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
                 buffer.putString(username);
                 buffer.putString(service);
-                buffer.putString(UserAuthPublicKeyFactory.NAME);
+                buffer.putString(name);
                 buffer.putBoolean(false);
                 buffer.putString(algo);
                 buffer.putPublicKey(key);
@@ -125,25 +128,27 @@ public class UserAuthPublicKey extends AbstractUserAuth {
         if (cmd == SshConstants.SSH_MSG_USERAUTH_PK_OK) {
             PublicKey key = current.getPublicKey();
             String algo = KeyUtils.getKeyType(key);
+            String name = getName();
             if (log.isDebugEnabled()) {
-                log.debug("process({}@{})[{}] Send SSH_MSG_USERAUTH_REQUEST reply publickey algo={}",
-                          username, session, service, algo);
+                log.debug("process({}@{})[{}] Send SSH_MSG_USERAUTH_REQUEST reply {} algo={}",
+                          username, session, service, name, algo);
             }
 
             buffer = session.prepareBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST, BufferUtils.clear(buffer));
             buffer.putString(username);
             buffer.putString(service);
-            buffer.putString(UserAuthPublicKeyFactory.NAME);
+            buffer.putString(name);
             buffer.putBoolean(true);
             buffer.putString(algo);
             buffer.putPublicKey(key);
 
             Buffer bs = new ByteArrayBuffer();
-            bs.putBytes(session.getKex().getH());
+            KeyExchange kex = session.getKex();
+            bs.putBytes(kex.getH());
             bs.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
             bs.putString(username);
             bs.putString(service);
-            bs.putString(UserAuthPublicKeyFactory.NAME);
+            bs.putString(name);
             bs.putBoolean(true);
             bs.putString(algo);
             bs.putPublicKey(key);
@@ -171,58 +176,4 @@ public class UserAuthPublicKey extends AbstractUserAuth {
             }
         }
     }
-
-    interface PublicKeyIdentity {
-        PublicKey getPublicKey();
-
-        byte[] sign(byte[] data) throws Exception;
-    }
-
-    static class KeyAgentIdentity implements PublicKeyIdentity {
-        private final SshAgent agent;
-        private final PublicKey key;
-
-        KeyAgentIdentity(SshAgent agent, PublicKey key) {
-            this.agent = agent;
-            this.key = key;
-        }
-
-        @Override
-        public PublicKey getPublicKey() {
-            return key;
-        }
-
-        @Override
-        public byte[] sign(byte[] data) throws Exception {
-            return agent.sign(key, data);
-        }
-    }
-
-    static class KeyPairIdentity implements PublicKeyIdentity {
-        private final KeyPair pair;
-        private final FactoryManager manager;
-
-        KeyPairIdentity(FactoryManager manager, KeyPair pair) {
-            this.manager = manager;
-            this.pair = pair;
-        }
-
-        @Override
-        public PublicKey getPublicKey() {
-            return pair.getPublic();
-        }
-
-        @Override
-        public byte[] sign(byte[] data) throws Exception {
-            String keyType = KeyUtils.getKeyType(pair);
-            Signature verif = ValidateUtils.checkNotNull(
-                    NamedFactory.Utils.create(manager.getSignatureFactories(), keyType),
-                    "No signer could be located for key type=%s",
-                    keyType);
-            verif.initSigner(pair.getPrivate());
-            verif.update(data, 0, data.length);
-            return verif.sign();
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java
new file mode 100644
index 0000000..e8f09a3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java
@@ -0,0 +1,49 @@
+/*
+ * 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.client.auth.pubkey;
+
+import java.security.PublicKey;
+
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Uses an {@link SshAgent} to generate the identity signature
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class KeyAgentIdentity implements PublicKeyIdentity {
+    private final SshAgent agent;
+    private final PublicKey key;
+
+    public KeyAgentIdentity(SshAgent agent, PublicKey key) {
+        this.agent = ValidateUtils.checkNotNull(agent, "No signing agent");
+        this.key = ValidateUtils.checkNotNull(key, "No public key");
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return key;
+    }
+
+    @Override
+    public byte[] sign(byte[] data) throws Exception {
+        return agent.sign(key, data);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
new file mode 100644
index 0000000..40b3837
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.client.auth.pubkey;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Uses a {@link KeyPair} to generate the identity signature
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class KeyPairIdentity implements PublicKeyIdentity {
+    private final KeyPair pair;
+    private final FactoryManager manager;
+
+    public KeyPairIdentity(FactoryManager manager, KeyPair pair) {
+        this.manager = ValidateUtils.checkNotNull(manager, "No manager");
+        this.pair = ValidateUtils.checkNotNull(pair, "No key pair");
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return pair.getPublic();
+    }
+
+    @Override
+    public byte[] sign(byte[] data) throws Exception {
+        String keyType = KeyUtils.getKeyType(pair);
+        Signature verifier = ValidateUtils.checkNotNull(
+                NamedFactory.Utils.create(manager.getSignatureFactories(), keyType),
+                "No signer could be located for key type=%s",
+                keyType);
+        verifier.initSigner(pair.getPrivate());
+        verifier.update(data, 0, data.length);
+        return verifier.sign();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java
new file mode 100644
index 0000000..51444ae
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.client.auth.pubkey;
+
+import java.security.PublicKey;
+
+/**
+ * Represents a public key identity
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyIdentity {
+    /**
+     * @return The {@link PublicKey} identity value
+     */
+    PublicKey getPublicKey();
+
+    /**
+     * Proves the public key identity by signing the given data
+     *
+     * @param data Data to sign
+     * @return Signed data - using the identity
+     * @throws Exception If failed to sign the data
+     */
+    byte[] sign(byte[] data) throws Exception;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
index 7272a34..7593ae8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
@@ -49,6 +49,7 @@ import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.apache.sshd.common.auth.UsernameHolder;
 import org.apache.sshd.common.config.SshConfigFileReader;
 import org.apache.sshd.common.config.keys.IdentityUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
@@ -65,7 +66,7 @@ import org.apache.sshd.common.util.io.NoCloseReader;
  * file format</A>
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class HostConfigEntry implements Cloneable {
+public class HostConfigEntry implements Cloneable, UsernameHolder {
 
     /**
      * Used in a host pattern to denote zero or more consecutive characters
@@ -218,6 +219,7 @@ public class HostConfigEntry implements Cloneable {
     /**
      * @return A username override - if not {@code null}/empty
      */
+    @Override
     public String getUsername() {
         return username;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/future/AuthFuture.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/future/AuthFuture.java b/sshd-core/src/main/java/org/apache/sshd/client/future/AuthFuture.java
index 6859966..cf9f802 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/future/AuthFuture.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/future/AuthFuture.java
@@ -38,11 +38,18 @@ public interface AuthFuture extends SshFuture<AuthFuture>, VerifiableFuture<Auth
 
     /**
      * @return <code>true</code> if the authentication operation is finished successfully.
+     * <B>Note:</B> calling this method while the operation is in progress
+     * returns {@code false}. Should check {@link #isDone()} in order to
+     * ensure that the result is valid.
      */
     boolean isSuccess();
 
     /**
      * @return <code>false</code> if the authentication operation failed.
+     * <B>Note:</B> the operation is considered failed if an exception
+     * is received instead of a success/fail response code or the operation
+     * is in progress. Should check {@link #isDone()} in order to
+     * ensure that the result is valid.
      */
     boolean isFailure();
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/future/DefaultAuthFuture.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/future/DefaultAuthFuture.java b/sshd-core/src/main/java/org/apache/sshd/client/future/DefaultAuthFuture.java
index ca83826..f42b94c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/future/DefaultAuthFuture.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/future/DefaultAuthFuture.java
@@ -64,7 +64,11 @@ public class DefaultAuthFuture extends DefaultVerifiableSshFuture<AuthFuture> im
     @Override   // TODO for JDK-8 make this a default method
     public boolean isFailure() {
         Object v = getValue();
-        return (v instanceof Boolean) && (!((Boolean) v).booleanValue());
+        if (v instanceof Boolean) {
+            return !((Boolean) v).booleanValue();
+        } else {
+            return true;
+        }
     }
 
     @Override   // TODO for JDK-8 make this a default method

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index 96dcbd6..29c2879 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -523,7 +523,7 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
             for (;;) {
                 int cond = 0;
                 if (closeFuture.isClosed()) {
-                    cond |= ClientSession.CLOSED;
+                    cond |= CLOSED;
                 }
                 if (authed) { // authFuture.isSuccess()
                     cond |= AUTHED;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/common/auth/UserAuthInstance.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/auth/UserAuthInstance.java b/sshd-core/src/main/java/org/apache/sshd/common/auth/UserAuthInstance.java
new file mode 100644
index 0000000..f7448e6
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/auth/UserAuthInstance.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.session.Session;
+
+/**
+ * Represents an authentication-in-progress tracker for a specific session
+ *
+ * @param <S> The type of session being tracked by the instance
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+//CHECKSTYLE:OFF
+public interface UserAuthInstance<S extends Session> extends NamedResource {
+    /**
+     * @return The current session for which the authentication is being
+     * tracked. <B>Note:</B> may be {@code null} if the instance has not
+     * been initialized yet
+     */
+    S getSession();
+}
+//CHECKSTYLE:ON

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java b/sshd-core/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java
new file mode 100644
index 0000000..b49c84b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/auth/UsernameHolder.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface UsernameHolder {
+    /**
+     * @return The attached username - may be {@code null}/empty if holder
+     * not yet initialized
+     */
+    String getUsername();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
index 04e65ea..bd60783 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
@@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.Service;
+import org.apache.sshd.common.auth.UsernameHolder;
 import org.apache.sshd.common.channel.ChannelListenerManager;
 import org.apache.sshd.common.future.KeyExchangeFuture;
 import org.apache.sshd.common.io.IoSession;
@@ -33,11 +34,12 @@ import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
- * Represents an SSH session
+ * Represents an SSH session. <B>Note:</B> the associated username for the session
+ * may be {@code null}/empty if the session is not yet authenticated
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface Session extends SessionListenerManager, ChannelListenerManager, Closeable {
+public interface Session extends SessionListenerManager, ChannelListenerManager, Closeable, UsernameHolder {
 
     /**
      * Timeout status.
@@ -68,14 +70,6 @@ public interface Session extends SessionListenerManager, ChannelListenerManager,
      */
     <T, E extends T> T setAttribute(AttributeKey<T> key, E value);
 
-    /**
-     * Retrieve the name of the user authenticated on this session
-     * or null if the session has not been authenticated yet.
-     *
-     * @return the user name.
-     */
-    String getUsername();
-
     void setUsername(String username);
 
     /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java b/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
index a932449..d2fd0ab 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java
@@ -31,6 +31,8 @@ import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.channel.ChannelSessionFactory;
 import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
@@ -76,13 +78,20 @@ public class ServerBuilder extends BaseBuilder<SshServer, ServerBuilder> {
         ));
 
     public static final PublickeyAuthenticator DEFAULT_PUBLIC_KEY_AUTHENTICATOR = DefaultAuthorizedKeysAuthenticator.INSTANCE;
+    public static final KeyboardInteractiveAuthenticator DEFAULT_INTERACTIVE_AUTHENTICATOR = DefaultKeyboardInteractiveAuthenticator.INSTANCE;
 
     protected PublickeyAuthenticator pubkeyAuthenticator;
+    protected KeyboardInteractiveAuthenticator interactiveAuthenticator;
 
     public ServerBuilder() {
         super();
     }
 
+    public ServerBuilder interactiveAuthenticator(KeyboardInteractiveAuthenticator auth) {
+        interactiveAuthenticator = auth;
+        return this;
+    }
+
     public ServerBuilder publickeyAuthenticator(PublickeyAuthenticator auth) {
         pubkeyAuthenticator = auth;
         return this;
@@ -108,6 +117,10 @@ public class ServerBuilder extends BaseBuilder<SshServer, ServerBuilder> {
             pubkeyAuthenticator = DEFAULT_PUBLIC_KEY_AUTHENTICATOR;
         }
 
+        if (interactiveAuthenticator == null) {
+            interactiveAuthenticator = DEFAULT_INTERACTIVE_AUTHENTICATOR;
+        }
+
         if (factory == null) {
             factory = SshServer.DEFAULT_SSH_SERVER_FACTORY;
         }
@@ -119,6 +132,7 @@ public class ServerBuilder extends BaseBuilder<SshServer, ServerBuilder> {
     public SshServer build(boolean isFillWithDefaultValues) {
         SshServer server = super.build(isFillWithDefaultValues);
         server.setPublickeyAuthenticator(pubkeyAuthenticator);
+        server.setKeyboardInteractiveAuthenticator(interactiveAuthenticator);
         return server;
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
index 9bcc8bb..29b03ea 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
@@ -26,6 +26,7 @@ import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
 import org.apache.sshd.server.auth.UserAuth;
 import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.auth.password.PasswordAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 
@@ -146,6 +147,16 @@ public interface ServerFactoryManager extends FactoryManager, KeyPairProviderHol
     PasswordAuthenticator getPasswordAuthenticator();
 
     /**
+     * Retrieve the <code>KeyboardInteractiveAuthenticator</code> to be used by
+     * the SSH server. If no authenticator has been configured (i.e. this method returns
+     * {@code null}), then client authentication requests based on this method
+     * will be rejected.
+     *
+     * @return The {@link KeyboardInteractiveAuthenticator} or {@code null}
+     */
+    KeyboardInteractiveAuthenticator getKeyboardInteractiveAuthenticator();
+
+    /**
      * Retrieve the <code>GSSAuthenticator</code> to be used by the SSH server.
      * If no authenticator has been configured (i.e. this method returns
      * {@code null}), then client authentication requests based on gssapi

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index 6baf31b..8d1d152 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -50,6 +50,7 @@ import org.apache.sshd.server.auth.UserAuthPasswordFactory;
 import org.apache.sshd.server.auth.UserAuthPublicKeyFactory;
 import org.apache.sshd.server.auth.gss.GSSAuthenticator;
 import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.auth.password.PasswordAuthenticator;
 import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
@@ -129,6 +130,7 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
     protected List<NamedFactory<Command>> subsystemFactories;
     protected PasswordAuthenticator passwordAuthenticator;
     protected PublickeyAuthenticator publickeyAuthenticator;
+    protected KeyboardInteractiveAuthenticator interactiveAuthenticator;
     protected GSSAuthenticator gssAuthenticator;
 
     public SshServer() {
@@ -219,6 +221,15 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
     }
 
     @Override
+    public KeyboardInteractiveAuthenticator getKeyboardInteractiveAuthenticator() {
+        return interactiveAuthenticator;
+    }
+
+    public void setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator interactiveAuthenticator) {
+        this.interactiveAuthenticator = interactiveAuthenticator;
+    }
+
+    @Override
     public GSSAuthenticator getGSSAuthenticator() {
         return gssAuthenticator;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuth.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuth.java
index b2618ea..578e1d1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuth.java
@@ -22,21 +22,26 @@ import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.server.session.ServerSessionHolder;
 
 /**
  */
-public abstract class AbstractUserAuth extends AbstractLoggingBean implements UserAuth, ServerSessionHolder {
+public abstract class AbstractUserAuth extends AbstractLoggingBean implements UserAuth {
+    private final String name;
     private ServerSession session;
     private String service;
     private String username;
 
-    protected AbstractUserAuth() {
-        super();
+    protected AbstractUserAuth(String name) {
+        this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No name");
     }
 
     @Override
-    public String getUserName() {
+    public final String getName() {
+        return name;
+    }
+
+    @Override
+    public String getUsername() {
         return username;
     }
 
@@ -50,6 +55,11 @@ public abstract class AbstractUserAuth extends AbstractLoggingBean implements Us
     }
 
     @Override
+    public ServerSession getSession() {
+        return getServerSession();
+    }
+
+    @Override
     public Boolean auth(ServerSession session, String username, String service, Buffer buffer) throws Exception {
         this.session = ValidateUtils.checkNotNull(session, "No server session");
         this.username = username;
@@ -69,4 +79,8 @@ public abstract class AbstractUserAuth extends AbstractLoggingBean implements Us
 
     protected abstract Boolean doAuth(Buffer buffer, boolean init) throws Exception;
 
+    @Override
+    public String toString() {
+        return getName() + ": " + getSession() + "[" + getService() + "]";
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuthPassword.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuthPassword.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuthPassword.java
deleted file mode 100644
index b673bf7..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/AbstractUserAuthPassword.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.server.auth;
-
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.auth.password.PasswordAuthenticator;
-import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
-import org.apache.sshd.server.session.ServerSession;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public abstract class AbstractUserAuthPassword extends AbstractUserAuth {
-    protected AbstractUserAuthPassword() {
-        super();
-    }
-
-    /**
-     * Invokes the configured {@link PasswordAuthenticator} and returns the result.
-     * If {@link PasswordChangeRequiredException} thrown by the authenticator then
-     * {@link #handleServerPasswordChangeRequest(Buffer, ServerSession, String, String, PasswordChangeRequiredException)}
-     * is invoked
-     *
-     * @param buffer   The received {@link Buffer} to be re-used if need to send
-     *                 a password change request
-     * @param session  The {@link ServerSession} through which the request was received
-     * @param username The username
-     * @param password The password
-     * @return The authentication result - if {@code null} then exception was handled
-     * internally and authentication is still in progress
-     * @throws Exception If internal error during authentication (exception for
-     * {@link PasswordChangeRequiredException} which is handled internally)
-     * @see #handleServerPasswordChangeRequest(Buffer, ServerSession, String, String, PasswordChangeRequiredException)
-     */
-    protected Boolean checkPassword(Buffer buffer, ServerSession session, String username, String password) throws Exception {
-        ServerFactoryManager manager = ValidateUtils.checkNotNull(
-                session.getFactoryManager(),
-                "No ServerFactoryManager configured");
-        PasswordAuthenticator auth = ValidateUtils.checkNotNull(
-                manager.getPasswordAuthenticator(),
-                "No PasswordAuthenticator configured");
-        try {
-            return auth.authenticate(username, password, session);
-        } catch (PasswordChangeRequiredException e) {
-            if (log.isDebugEnabled()) {
-                log.debug("checkPassword({}@{}) password change required: {}",
-                          username, session, e.getMessage());
-            }
-            return handleServerPasswordChangeRequest(buffer, session, username, password, e);
-        }
-    }
-
-    /**
-     * Invoked by {@link #checkPassword(Buffer, ServerSession, String, String)}
-     * when a {@link PasswordChangeRequiredException} was thrown by the authenticator.
-     * By default it re-throws the original exception.
-     *
-     * @param buffer   The received {@link Buffer} to be re-used if need to send
-     *                 a password change request
-     * @param session  The {@link ServerSession} through which the request was received
-     * @param username The username
-     * @param password The (rejected) password
-     * @param e        The original thrown exception
-     * @return {@code null} by default to indicate incomplete authentication
-     * @throws Exception If failed to dispatch the message
-     */
-    protected Boolean handleServerPasswordChangeRequest(
-            Buffer buffer, ServerSession session, String username, String password, PasswordChangeRequiredException e)
-                throws Exception {
-        throw e;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
deleted file mode 100644
index 998813c..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.server.auth;
-
-import java.security.PublicKey;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.session.SessionListener;
-import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
-
-/**
- * Caches the result per session
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener {
-
-    protected final PublickeyAuthenticator authenticator;
-    protected final Map<ServerSession, Map<PublicKey, Boolean>> cache = new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>();
-
-    public CachingPublicKeyAuthenticator(PublickeyAuthenticator authenticator) {
-        this.authenticator = authenticator;
-    }
-
-    @Override
-    public boolean authenticate(String username, PublicKey key, ServerSession session) {
-        Map<PublicKey, Boolean> map = cache.get(session);
-        if (map == null) {
-            map = new ConcurrentHashMap<>();
-            cache.put(session, map);
-            session.addSessionListener(this);
-        }
-
-        Boolean result = map.get(key);
-        if (result == null) {
-            result = authenticator.authenticate(username, key, session);
-            map.put(key, result);
-        }
-
-        return result;
-    }
-
-    @Override
-    public void sessionCreated(Session session) {
-        // ignored
-    }
-
-    @Override
-    public void sessionEvent(Session session, Event event) {
-        // ignored
-    }
-
-    @Override
-    public void sessionClosed(Session session) {
-        cache.remove(session);
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
index 96730bb..9892324 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuth.java
@@ -18,15 +18,18 @@
  */
 package org.apache.sshd.server.auth;
 
+import org.apache.sshd.common.auth.UserAuthInstance;
+import org.apache.sshd.common.auth.UsernameHolder;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionHolder;
 
 /**
  * Server side authentication mechanism.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface UserAuth {
+public interface UserAuth extends ServerSessionHolder, UserAuthInstance<ServerSession>, UsernameHolder {
 
     /**
      * Try to authenticate the user.
@@ -56,15 +59,6 @@ public interface UserAuth {
     Boolean next(Buffer buffer) throws Exception;
 
     /**
-     * Get a user name which has been derived from the handshaking process, or the initial name if
-     * nothing has been found.
-     *
-     * @return The user name
-     * @throws Exception if the request fails
-     */
-    String getUserName() throws Exception;
-
-    /**
      * Free any system resources used by the module.
      */
     void destroy();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
index 22ceed8..f9b8737 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
@@ -18,11 +18,18 @@
  */
 package org.apache.sshd.server.auth;
 
-import org.apache.sshd.common.FactoryManagerUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.auth.keyboard.InteractiveChallenge;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
@@ -30,35 +37,39 @@ import org.apache.sshd.server.session.ServerSession;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class UserAuthKeyboardInteractive extends AbstractUserAuthPassword {
-    // configuration parameters on the FactoryManager to configure the message values
-    public static final String KB_INTERACTIVE_NAME_PROP = "kb-interactive-name";
-    public static final String DEFAULT_KB_INTERACTIVE_NAME = "Password authentication";
-    public static final String KB_INTERACTIVE_INSTRUCTION_PROP = "kb-interactive-instruction";
-    public static final String DEFAULT_KB_INTERACTIVE_INSTRUCTION = "";
-    public static final String KB_INTERACTIVE_LANG_PROP = "kb-interactive-language";
-    public static final String DEFAULT_KB_INTERACTIVE_LANG = "en-US";
-    public static final String KB_INTERACTIVE_PROMPT_PROP = "kb-interactive-prompt";
-    public static final String DEFAULT_KB_INTERACTIVE_PROMPT = "Password: ";
-    public static final String KB_INTERACTIVE_ECHO_PROMPT_PROP = "kb-interactive-echo-prompt";
-    public static final boolean DEFAULT_KB_INTERACTIVE_ECHO_PROMPT = false;
+public class UserAuthKeyboardInteractive extends AbstractUserAuth {
+    public static final String NAME = UserAuthKeyboardInteractiveFactory.NAME;
 
     public UserAuthKeyboardInteractive() {
-        super();
+        super(NAME);
     }
 
     @Override
     protected Boolean doAuth(Buffer buffer, boolean init) throws Exception {
         ServerSession session = getServerSession();
+        ServerFactoryManager manager = ValidateUtils.checkNotNull(session.getFactoryManager(), "No factory manager");
+        KeyboardInteractiveAuthenticator auth = manager.getKeyboardInteractiveAuthenticator();
         if (init) {
+            String lang = buffer.getString();
+            String subMethods = buffer.getString();
+            if (auth == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("doAuth({}) no interactive authenticator to generate challenge", session);
+                }
+                return false;
+            }
+
+            InteractiveChallenge challenge = auth.generateChallenge(session, getUsername(), lang, subMethods);
+            if (challenge == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("doAuth({}) no interactive challenge generated", session);
+                }
+                return false;
+            }
+
             // Prompt for password
             buffer = session.prepareBuffer(SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST, BufferUtils.clear(buffer));
-            buffer.putString(getInteractionName());
-            buffer.putString(getInteractionInstruction());
-            buffer.putString(getInteractionLanguage());
-            buffer.putInt(1);
-            buffer.putString(getInteractionPrompt());
-            buffer.putBoolean(isInteractionPromptEchoEnabled());
+            challenge.append(buffer);
             session.writePacket(buffer);
             return null;
         } else {
@@ -66,39 +77,21 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuthPassword {
             if (cmd != SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE) {
                 throw new SshException("Received unexpected message: " + cmd);
             }
+
             int num = buffer.getInt();
-            /*
-             * According to RFC4256:
-             *
-             *      If the num-responses field does not match the num-prompts
-             *      field in the request message, the server MUST send a failure
-             *      message.
-             */
-            if (num != 1) {
-                throw new SshException("Expected 1 response from user but received " + num);
+            List<String> responses = (num <= 0) ? Collections.<String>emptyList() : new ArrayList<String>(num);
+            for (int index = 0; index < num; index++) {
+                responses.add(buffer.getString());
             }
-            String password = buffer.getString();
-            return checkPassword(buffer, session, getUserName(), password);
-        }
-    }
 
-    protected String getInteractionName() {
-        return FactoryManagerUtils.getStringProperty(getServerSession(), KB_INTERACTIVE_NAME_PROP, DEFAULT_KB_INTERACTIVE_NAME);
-    }
-
-    protected String getInteractionInstruction() {
-        return FactoryManagerUtils.getStringProperty(getServerSession(), KB_INTERACTIVE_INSTRUCTION_PROP, DEFAULT_KB_INTERACTIVE_INSTRUCTION);
-    }
-
-    protected String getInteractionLanguage() {
-        return FactoryManagerUtils.getStringProperty(getServerSession(), KB_INTERACTIVE_LANG_PROP, DEFAULT_KB_INTERACTIVE_LANG);
-    }
-
-    protected String getInteractionPrompt() {
-        return FactoryManagerUtils.getStringProperty(getServerSession(), KB_INTERACTIVE_PROMPT_PROP, DEFAULT_KB_INTERACTIVE_PROMPT);
-    }
+            if (auth == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("doAuth({}) no interactive authenticator to validate responses", session);
+                }
+                return false;
+            }
 
-    protected boolean isInteractionPromptEchoEnabled() {
-        return FactoryManagerUtils.getBooleanProperty(getServerSession(), KB_INTERACTIVE_ECHO_PROMPT_PROP, DEFAULT_KB_INTERACTIVE_ECHO_PROMPT);
+            return auth.authenticate(session, getUsername(), responses);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
index a3a62e0..8cc708e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
@@ -26,14 +26,14 @@ import org.apache.sshd.common.util.buffer.Buffer;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthNone extends AbstractUserAuth {
+    public static final String NAME = UserAuthNoneFactory.NAME;
 
     public UserAuthNone() {
-        super();
+        super(NAME);
     }
 
     @Override
     public Boolean doAuth(Buffer buffer, boolean init) {
         return Boolean.TRUE;
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
index 9d37581..3dc7fd5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
@@ -19,7 +19,10 @@
 package org.apache.sshd.server.auth;
 
 import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
 import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
 import org.apache.sshd.server.session.ServerSession;
 
@@ -28,23 +31,62 @@ import org.apache.sshd.server.session.ServerSession;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class UserAuthPassword extends AbstractUserAuthPassword {
+public class UserAuthPassword extends AbstractUserAuth {
+    public static final String NAME = UserAuthPasswordFactory.NAME;
+
     public UserAuthPassword() {
-        super();
+        super(NAME);
     }
 
     @Override
     public Boolean doAuth(Buffer buffer, boolean init) throws Exception {
-        if (!init) {
-            throw new IllegalStateException("Incomplete initialization");
-        }
+        ValidateUtils.checkTrue(init, "Instance not initialized");
 
         boolean newPassword = buffer.getBoolean();
         String password = buffer.getString();
         if (newPassword) {
-            return handleClientPasswordChangeRequest(buffer, getServerSession(), getUserName(), password, buffer.getString());
+            return handleClientPasswordChangeRequest(buffer, getServerSession(), getUsername(), password, buffer.getString());
         } else {
-            return checkPassword(buffer, getServerSession(), getUserName(), password);
+            return checkPassword(buffer, getServerSession(), getUsername(), password);
+        }
+    }
+
+    /**
+     * Invokes the configured {@link PasswordAuthenticator} and returns the result.
+     * If {@link PasswordChangeRequiredException} thrown by the authenticator then
+     * {@link #handleServerPasswordChangeRequest(Buffer, ServerSession, String, String, PasswordChangeRequiredException)}
+     * is invoked
+     *
+     * @param buffer   The received {@link Buffer} to be re-used if need to send
+     *                 a password change request
+     * @param session  The {@link ServerSession} through which the request was received
+     * @param username The username
+     * @param password The password
+     * @return The authentication result - if {@code null} then exception was handled
+     * internally and authentication is still in progress
+     * @throws Exception If internal error during authentication (exception for
+     * {@link PasswordChangeRequiredException} which is handled internally)
+     * @see #handleServerPasswordChangeRequest(Buffer, ServerSession, String, String, PasswordChangeRequiredException)
+     */
+    protected Boolean checkPassword(Buffer buffer, ServerSession session, String username, String password) throws Exception {
+        ServerFactoryManager manager = ValidateUtils.checkNotNull(
+                session.getFactoryManager(),
+                "No ServerFactoryManager configured");
+        PasswordAuthenticator auth = manager.getPasswordAuthenticator();
+        if (auth == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("checkPassword({}) no password authenticator", session);
+            }
+            return false;
+        }
+
+        try {
+            return auth.authenticate(username, password, session);
+        } catch (PasswordChangeRequiredException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("checkPassword({}) password change required: {}", session, e.getMessage());
+            }
+            return handleServerPasswordChangeRequest(buffer, session, username, password, e);
         }
     }
 
@@ -67,23 +109,34 @@ public class UserAuthPassword extends AbstractUserAuthPassword {
         throw new UnsupportedOperationException("Password change not supported");
     }
 
-    @Override
+    /**
+     * Invoked by {@link #checkPassword(Buffer, ServerSession, String, String)}
+     * when a {@link PasswordChangeRequiredException} was thrown by the authenticator.
+     * By default it re-throws the original exception.
+     *
+     * @param buffer   The received {@link Buffer} to be re-used if need to send
+     *                 a password change request
+     * @param session  The {@link ServerSession} through which the request was received
+     * @param username The username
+     * @param password The (rejected) password
+     * @param e        The original thrown exception
+     * @return {@code null} by default to indicate incomplete authentication
+     * @throws Exception If failed to dispatch the message
+     */
     protected Boolean handleServerPasswordChangeRequest(
             Buffer buffer, ServerSession session, String username, String password, PasswordChangeRequiredException e)
-                    throws Exception {
+                throws Exception {
         String prompt = e.getPrompt();
         String lang = e.getLanguage();
         if (log.isDebugEnabled()) {
-            log.debug("handlePasswordChangeRequest({}@{}) password change required - prompt={}, lang={}",
-                      username, session, prompt, lang);
+            log.debug("handlePasswordChangeRequest({}) password change required - prompt={}, lang={}",
+                      session, prompt, lang);
         }
 
         buffer = session.prepareBuffer(SshConstants.SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, buffer);
-        buffer.putString((prompt == null) ? "" : prompt);
-        buffer.putString((lang == null) ? "" : lang);
+        buffer.putString(prompt);
+        buffer.putString(lang);
         session.writePacket(buffer);
         return null;    // authentication incomplete
-
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
index fa56c9d..5285e40 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
@@ -37,36 +37,43 @@ import org.apache.sshd.server.session.ServerSession;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthPublicKey extends AbstractUserAuth {
+    public static final String NAME = UserAuthPublicKeyFactory.NAME;
 
     public UserAuthPublicKey() {
-        super();
+        super(NAME);
     }
 
     @Override
     public Boolean doAuth(Buffer buffer, boolean init) throws Exception {
         ValidateUtils.checkTrue(init, "Instance not initialized");
+
         boolean hasSig = buffer.getBoolean();
         String alg = buffer.getString();
-
         int oldLim = buffer.wpos();
         int oldPos = buffer.rpos();
         int len = buffer.getInt();
         buffer.wpos(buffer.rpos() + len);
-        PublicKey key = buffer.getRawPublicKey();
+
         ServerSession session = getServerSession();
-        ServerFactoryManager manager = session.getFactoryManager();
-        Signature verif = ValidateUtils.checkNotNull(
+        ServerFactoryManager manager = ValidateUtils.checkNotNull(session.getFactoryManager(), "No factory manager");
+        PublicKey key = buffer.getRawPublicKey();
+        Signature verifier = ValidateUtils.checkNotNull(
                 NamedFactory.Utils.create(manager.getSignatureFactories(), alg),
                 "No verifier located for algorithm=%s",
                 alg);
-        verif.initVerifier(key);
+        verifier.initVerifier(key);
         buffer.wpos(oldLim);
 
         byte[] sig = hasSig ? buffer.getBytes() : null;
+        PublickeyAuthenticator authenticator = manager.getPublickeyAuthenticator();
+        if (authenticator == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("doAuth({}) no authenticator", session);
+            }
+            return false;
+        }
 
-        PublickeyAuthenticator authenticator =
-                ValidateUtils.checkNotNull(manager.getPublickeyAuthenticator(), "No PublickeyAuthenticator configured");
-        if (!authenticator.authenticate(getUserName(), key, session)) {
+        if (!authenticator.authenticate(getUsername(), key, session)) {
             return Boolean.FALSE;
         }
 
@@ -80,16 +87,16 @@ public class UserAuthPublicKey extends AbstractUserAuth {
             Buffer buf = new ByteArrayBuffer();
             buf.putBytes(session.getKex().getH());
             buf.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
-            buf.putString(getUserName());
+            buf.putString(getUsername());
             buf.putString(getService());
-            buf.putString(UserAuthPublicKeyFactory.NAME);
+            buf.putString(getName());
             buf.putBoolean(true);
             buf.putString(alg);
             buffer.rpos(oldPos);
             buffer.wpos(oldPos + 4 + len);
             buf.putBuffer(buffer);
-            verif.update(buf.array(), buf.rpos(), buf.available());
-            if (!verif.verify(sig)) {
+            verifier.update(buf.array(), buf.rpos(), buf.available());
+            if (!verifier.verify(sig)) {
                 throw new Exception("Key verification failed");
             }
             return Boolean.TRUE;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/75643fcb/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
index 9f55d4a..b9d6a22 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
@@ -41,7 +41,7 @@ import org.ietf.jgss.Oid;
  * <p>Several methods are available for overriding in specific circumstances.</p>
  */
 public class UserAuthGSS extends AbstractUserAuth {
-
+    public static final String NAME = UserAuthGSSFactory.NAME;
     // Oids for the Kerberos 5 mechanism and principal
 
     public static final Oid KRB5_MECH = createOID("1.2.840.113554.1.2.2");
@@ -53,6 +53,10 @@ public class UserAuthGSS extends AbstractUserAuth {
     // Identity from context
     private String identity;
 
+    public UserAuthGSS() {
+        super(NAME);
+    }
+
     /**
      * Handle the first authentication step.
      */
@@ -74,7 +78,7 @@ public class UserAuthGSS extends AbstractUserAuth {
 
                     // Validate initial user before proceeding
 
-                    if (!auth.validateInitialUser(session, getUserName())) {
+                    if (!auth.validateInitialUser(session, getUsername())) {
                         return Boolean.FALSE;
                     }
 
@@ -109,7 +113,9 @@ public class UserAuthGSS extends AbstractUserAuth {
                         "Packet not supported by user authentication method: " + msg);
             }
 
-            log.debug("In krb5.next: msg = " + msg);
+            if (log.isDebugEnabled()) {
+                log.debug("doAuth({}@{}) In krb5.next: msg = {}", getUsername(), session, msg);
+            }
 
             // If the context is established, this must be a MIC message
 
@@ -125,22 +131,23 @@ public class UserAuthGSS extends AbstractUserAuth {
 
                 msgbuf.putBytes(ValidateUtils.checkNotNullAndNotEmpty(session.getSessionId(), "No current session ID"));
                 msgbuf.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
-                msgbuf.putString(getUserName());
+                msgbuf.putString(getUsername());
                 msgbuf.putString(getService());
-                msgbuf.putString(UserAuthGSSFactory.NAME);
+                msgbuf.putString(getName());
 
                 byte[] msgbytes = msgbuf.getCompactData();
                 byte[] inmic = buffer.getBytes();
 
                 try {
                     context.verifyMIC(inmic, 0, inmic.length, msgbytes, 0, msgbytes.length, new MessageProp(false));
-                    log.debug("MIC verified");
+                    if (log.isDebugEnabled()) {
+                        log.debug("doAuth({}@{}) MIC verified", getUsername(), session);
+                    }
                     return Boolean.TRUE;
                 } catch (GSSException e) {
-                    log.info("GSS verification error: {}", e.toString());
+                    log.info("doAuth({}@{}) GSS verification error: {}", getUsername(), session, e.toString());
                     return Boolean.FALSE;
                 }
-
             } else {
 
                 // Not established - new token to process
@@ -151,7 +158,7 @@ public class UserAuthGSS extends AbstractUserAuth {
 
                 // Validate identity if context is now established
 
-                if (established && identity == null) {
+                if (established && (identity == null)) {
                     identity = context.getSrcName().toString();
                     log.info("GSS identity is {}", identity);
 
@@ -175,15 +182,9 @@ public class UserAuthGSS extends AbstractUserAuth {
         }
     }
 
-    /**
-     * Get a user name which has been derived from the handshaking process, or the initial name if
-     * nothing has been found.
-     *
-     * @return The user name
-     */
     @Override
-    public String getUserName() {
-        return identity != null ? identity : super.getUserName();
+    public String getUsername() {
+        return identity != null ? identity : super.getUsername();
     }
 
     /**
@@ -195,7 +196,9 @@ public class UserAuthGSS extends AbstractUserAuth {
             try {
                 context.dispose();
             } catch (GSSException e) {
-                // ignore
+                if (log.isDebugEnabled()) {
+                    log.debug("Failed ({}) to dispose of context: {}", e.getClass().getSimpleName(), e.getMessage());
+                }
             } finally {
                 context = null;
             }