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/12/14 10:14:05 UTC

[1/4] mina-sshd git commit: [SSHD-613] Support a 'callback' method for session password identities

Repository: mina-sshd
Updated Branches:
  refs/heads/master 1539faa20 -> a26ef1549


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/test/java/org/apache/sshd/client/auth/PasswordIdentityProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/auth/PasswordIdentityProviderTest.java b/sshd-core/src/test/java/org/apache/sshd/client/auth/PasswordIdentityProviderTest.java
new file mode 100644
index 0000000..1a760c3
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/auth/PasswordIdentityProviderTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class PasswordIdentityProviderTest extends BaseTestSupport {
+    public PasswordIdentityProviderTest() {
+        super();
+    }
+
+    @Test
+    public void testMultiProvider() {
+        String[][] values = {
+                { getClass().getSimpleName(), getCurrentTestName() },
+                { new Date(System.currentTimeMillis()).toString() },
+                { getClass().getPackage().getName() }
+        };
+        List<String> expected = new ArrayList<>();
+        Collection<PasswordIdentityProvider> providers = new LinkedList<>();
+        for (String[] va : values) {
+            Collection<String> passwords = Arrays.asList(va);
+            expected.addAll(passwords);
+
+            PasswordIdentityProvider p = PasswordIdentityProvider.Utils.wrap(passwords);
+            assertProviderContents("Wrapped", p, passwords);
+            providers.add(p);
+        }
+
+        PasswordIdentityProvider p = PasswordIdentityProvider.Utils.multiProvider(providers);
+        assertProviderContents("Multi", p, expected);
+    }
+
+    private static void assertProviderContents(String message, PasswordIdentityProvider p, Iterable<String> expected) {
+        assertNotNull(message + ": no provider", p);
+        assertEquals(message, expected, p.loadPasswords());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
index 1289f91..1a55b6e 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
@@ -37,6 +37,7 @@ import java.util.Objects;
 import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -139,7 +140,7 @@ public class BuiltinClientIdentitiesWatcherTest extends BaseTestSupport {
         }
     }
 
-    private static void testMultipleFilesWatch(String phase, KeyPairProvider watcher, Collection<? extends KeyPair> expected) {
+    private static void testMultipleFilesWatch(String phase, KeyIdentityProvider watcher, Collection<? extends KeyPair> expected) {
         Collection<? extends KeyPair> actual = (Collection<? extends KeyPair>) watcher.loadKeys();
         assertEquals(phase + ": mismatched sizes", GenericUtils.size(expected), GenericUtils.size(actual));
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
index b4941de..e392e86 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
@@ -30,7 +30,7 @@ import java.util.Map;
 
 import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.IdentityUtils;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -88,7 +88,7 @@ public class ClientIdentityTest extends BaseTestSupport {
             }
         }
 
-        KeyPairProvider provider = IdentityUtils.createKeyPairProvider(ids, true /* supported only */);
+        KeyIdentityProvider provider = IdentityUtils.createKeyPairProvider(ids, true /* supported only */);
         assertNotNull("No provider generated", provider);
 
         Iterable<KeyPair> keys = provider.loadKeys();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionImplTest.java b/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionImplTest.java
deleted file mode 100644
index 8dd5d66..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionImplTest.java
+++ /dev/null
@@ -1,144 +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.client.session;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.sshd.client.ClientFactoryManager;
-import org.apache.sshd.common.Factory;
-import org.apache.sshd.common.ServiceFactory;
-import org.apache.sshd.common.channel.Channel;
-import org.apache.sshd.common.channel.ChannelListener;
-import org.apache.sshd.common.forward.DefaultTcpipForwarderFactory;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.random.JceRandomFactory;
-import org.apache.sshd.common.random.Random;
-import org.apache.sshd.common.random.SingletonRandomFactory;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.session.SessionListener;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.util.test.BaseTestSupport;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.mockito.Mockito;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class ClientSessionImplTest extends BaseTestSupport {
-    public ClientSessionImplTest() {
-        super();
-    }
-
-    @Test
-    public void testAddRemoveIdentities() throws Exception {
-        ClientFactoryManager client = Mockito.mock(ClientFactoryManager.class);
-        Mockito.when(client.getTcpipForwarderFactory()).thenReturn(DefaultTcpipForwarderFactory.INSTANCE);
-        Mockito.when(client.getSessionListenerProxy()).thenReturn(new SessionListener() {
-            @Override
-            public void sessionEvent(Session session, Event event) {
-                // ignored
-            }
-
-            @Override
-            public void sessionCreated(Session session) {
-                // ignored
-            }
-
-            @Override
-            public void sessionClosed(Session session) {
-                // ignored
-            }
-        });
-        Mockito.when(client.getChannelListenerProxy()).thenReturn(new ChannelListener() {
-            @Override
-            public void channelOpenSuccess(Channel channel) {
-                // ignored
-            }
-
-            @Override
-            public void channelOpenFailure(Channel channel, Throwable reason) {
-                // ignored
-            }
-
-            @Override
-            public void channelInitialized(Channel channel) {
-                // ignored
-            }
-
-            @Override
-            public void channelClosed(Channel channel) {
-                // ignored
-            }
-        });
-        Factory<Random> randomFactory = new SingletonRandomFactory(JceRandomFactory.INSTANCE);
-        Mockito.when(client.getRandomFactory()).thenReturn(randomFactory);
-
-        List<ServiceFactory> serviceFactories = Arrays.asList(
-                ClientUserAuthServiceFactory.INSTANCE,
-                ClientConnectionServiceFactory.INSTANCE
-        );
-        Mockito.when(client.getServiceFactories()).thenReturn(serviceFactories);
-
-        try (ClientSession session = new ClientSessionImpl(client, Mockito.mock(IoSession.class)) {
-            @Override
-            protected void sendClientIdentification() {
-                // ignored
-            }
-
-            @Override
-            protected byte[] sendKexInit() throws IOException {
-                return GenericUtils.EMPTY_BYTE_ARRAY;
-            }
-
-            @Override
-            public void close() throws IOException {
-                // ignored
-            }
-        }) {
-            {
-                String expected = getCurrentTestName();
-                assertNull("Unexpected initial password identity", session.removePasswordIdentity(expected));
-                session.addPasswordIdentity(expected);
-
-                String actual = session.removePasswordIdentity(expected);
-                assertSame("Mismatched removed password identity", expected, actual);
-                assertNull("Password identity not removed", session.removePasswordIdentity(expected));
-            }
-
-            {
-                KeyPair expected = new KeyPair(Mockito.mock(PublicKey.class), Mockito.mock(PrivateKey.class));
-                assertNull("Unexpected initial pubket identity", session.removePublicKeyIdentity(expected));
-                session.addPublicKeyIdentity(expected);
-
-                KeyPair actual = session.removePublicKeyIdentity(expected);
-                assertSame("Mismatched removed pubkey identity", expected, actual);
-                assertNull("Pubkey identity not removed", session.removePublicKeyIdentity(expected));
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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
index 3aec0e7..88fc25c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
@@ -20,7 +20,6 @@ package org.apache.sshd.common.auth;
 
 import java.io.IOException;
 import java.security.KeyPair;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
@@ -32,12 +31,12 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.PasswordIdentityProvider;
 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.PropertyResolverUtils;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.Session;
@@ -45,10 +44,6 @@ import org.apache.sshd.common.session.SessionListener;
 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;
@@ -59,6 +54,8 @@ 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.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.server.session.ServerSessionImpl;
 import org.apache.sshd.server.session.SessionFactory;
@@ -71,9 +68,6 @@ 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;
 
@@ -84,8 +78,6 @@ public class AuthenticationTest extends BaseTestSupport {
     @Before
     public void setUp() throws Exception {
         sshd = setupTestServer();
-        PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.WELCOME_BANNER, WELCOME);
-        PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.AUTH_METHODS, "publickey,password publickey,keyboard-interactive");
         sshd.setSessionFactory(new SessionFactory(sshd) {
             @Override
             protected ServerSessionImpl doCreateSession(IoSession ioSession) throws Exception {
@@ -117,11 +109,6 @@ public class AuthenticationTest extends BaseTestSupport {
     @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()) {
@@ -130,8 +117,13 @@ public class AuthenticationTest extends BaseTestSupport {
                 Collection<ClientSession.ClientSessionEvent> result = s.waitFor(mask, TimeUnit.SECONDS.toMillis(11L));
                 assertFalse("Timeout while waiting on session events", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
 
+                String password = "the-password";
                 for (String username : new String[]{"user1", "user2"}) {
-                    assertAuthenticationResult(username, authPassword(s, username, "the-password"), false);
+                    try {
+                        assertAuthenticationResult(username, authPassword(s, username, password), false);
+                    } finally {
+                        s.removePasswordIdentity(password);
+                    }
                 }
 
                 // Note that WAIT_AUTH flag should be false, but since the internal
@@ -249,18 +241,21 @@ public class AuthenticationTest extends BaseTestSupport {
     @Test
     public void testAuthPasswordOnly() throws Exception {
         try (SshClient client = setupTestClient()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
-            client.start();
+            sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
 
+            client.start();
             try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
                 Collection<ClientSession.ClientSessionEvent> result =
                         s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
                         TimeUnit.SECONDS.toMillis(11L));
                 assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-                assertAuthenticationResult(getCurrentTestName(), authPassword(s, getCurrentTestName(), getCurrentTestName()), false);
+
+                String password = getCurrentTestName();
+                try {
+                    assertAuthenticationResult(getCurrentTestName(), authPassword(s, getCurrentTestName(), password), false);
+                } finally {
+                    s.removePasswordIdentity(password);
+                }
             } finally {
                 client.stop();
             }
@@ -270,10 +265,9 @@ public class AuthenticationTest extends BaseTestSupport {
     @Test
     public void testAuthKeyPassword() throws Exception {
         try (SshClient client = setupTestClient()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
+            sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+            sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+
             client.start();
 
             try (ClientSession s = client.connect(null, TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
@@ -283,22 +277,28 @@ public class AuthenticationTest extends BaseTestSupport {
                 assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
 
                 KeyPair pair = createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
-                assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
-                assertAuthenticationResult(UserAuthMethodFactory.PASSWORD, authPassword(s, getCurrentTestName(), getCurrentTestName()), true);
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
+                } finally {
+                    s.removePublicKeyIdentity(pair);
+                }
+
+                String password = getCurrentTestName();
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.PASSWORD, authPassword(s, getCurrentTestName(), password), true);
+                } finally {
+                    s.removePasswordIdentity(password);
+                }
             } finally {
                 client.stop();
             }
         }
     }
 
-    @Test
+    @Test // see SSHD-612
     public void testAuthDefaultKeyInteractive() throws Exception {
         try (SshClient client = setupTestClient()) {
-            client.setServiceFactories(Arrays.asList(
-                    new ClientUserAuthServiceOld.Factory(),
-                    ClientConnectionServiceFactory.INSTANCE
-            ));
-
+            sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
             sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
                 @Override
                 public InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods) {
@@ -345,8 +345,17 @@ public class AuthenticationTest extends BaseTestSupport {
                 assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
 
                 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);
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY, authPublicKey(s, getCurrentTestName(), pair), false);
+                } finally {
+                    s.removePublicKeyIdentity(pair);
+                }
+
+                try {
+                    assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE, authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
+                } finally {
+                    s.setUserInteraction(null);
+                }
             } finally {
                 client.stop();
             }
@@ -554,27 +563,77 @@ public class AuthenticationTest extends BaseTestSupport {
         }
     }
 
+    @Test
+    public void testPasswordIdentityProviderPropagation() throws Exception {
+        try (SshClient client = setupTestClient()) {
+            final List<String> passwords = Collections.singletonList(getCurrentTestName());
+            final AtomicInteger loadCount = new AtomicInteger(0);
+            PasswordIdentityProvider provider = new PasswordIdentityProvider() {
+                @Override
+                public Iterable<String> loadPasswords() {
+                    loadCount.incrementAndGet();
+                    outputDebugMessage("loadPasswords - count=%s", loadCount);
+                    return passwords;
+                }
+            };
+            client.setPasswordIdentityProvider(provider);
+
+            client.start();
+            try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                s.auth().verify(11L, TimeUnit.SECONDS);
+                assertEquals("Mismatched load passwords count", 1, loadCount.get());
+                assertSame("Mismatched passwords identity provider", provider, s.getPasswordIdentityProvider());
+            } 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 {
+    private static 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));
+        s.addPasswordIdentity(pswd);
+        return s.auth();
     }
 
-    private AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
+    private static AuthFuture authInteractive(final ClientSession s, String user, String pswd) throws IOException {
         s.setUsername(user);
-        return s.getService(ClientUserAuthServiceOld.class)
-                .auth(new UserAuthKeyboardInteractive(s, "ssh-connection", pswd));
+        final String[] response = {pswd};
+        s.setUserInteraction(new UserInteraction() {
+            @Override
+            public void welcome(ClientSession session, String banner, String lang) {
+                // ignored
+            }
+
+            @Override
+            public boolean isInteractionAllowed(ClientSession session) {
+                return true;
+            }
+
+            @Override
+            public String[] interactive(ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
+                assertSame("Mismatched session instance", s, session);
+                assertEquals("Mismatched prompt size", 1, GenericUtils.length(prompt));
+                assertTrue("Mismatched prompt: " + prompt[0], prompt[0].toLowerCase().contains("password"));
+                return response;
+            }
+
+            @Override
+            public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+                throw new UnsupportedOperationException("Unexpected password update request");
+            }
+        });
+        return s.auth();
     }
 
-    private AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
+    private static 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));
+        s.addPublicKeyIdentity(pair);
+        return s.auth();
     }
 
     public static class TestSession extends ServerSessionImpl {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/test/java/org/apache/sshd/util/test/Utils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/Utils.java b/sshd-core/src/test/java/org/apache/sshd/util/test/Utils.java
index 033be14..1dec6e4 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/Utils.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/Utils.java
@@ -50,6 +50,7 @@ import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
 import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.keyprovider.AbstractFileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
 import org.apache.sshd.common.random.Random;
@@ -99,7 +100,7 @@ public class Utils {
         return getFirstKeyPair(ValidateUtils.checkNotNull(holder, "No holder").getKeyPairProvider());
     }
 
-    public static KeyPair getFirstKeyPair(KeyPairProvider provider) {
+    public static KeyPair getFirstKeyPair(KeyIdentityProvider provider) {
         ValidateUtils.checkNotNull(provider, "No key pair provider");
         Iterable<? extends KeyPair> pairs = ValidateUtils.checkNotNull(provider.loadKeys(), "No loaded keys");
         Iterator<? extends KeyPair> iter = ValidateUtils.checkNotNull(pairs.iterator(), "No keys iterator");
@@ -130,7 +131,7 @@ public class Utils {
         }
     }
 
-    private static <P extends KeyPairProvider> P validateKeyPairProvider(P provider) {
+    private static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
         ValidateUtils.checkNotNull(provider, "No provider");
 
         // get the I/O out of the way


[3/4] mina-sshd git commit: Use more conservative buffer size allocations

Posted by lg...@apache.org.
Use more conservative buffer size allocations


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

Branch: refs/heads/master
Commit: fa724c2f1406b5f834cf802f7a41b6fe5461e96e
Parents: 13818de
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Dec 14 10:48:05 2015 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Dec 14 10:48:05 2015 +0200

----------------------------------------------------------------------
 .../client/channel/AbstractClientChannel.java   |  2 +-
 .../sshd/client/channel/ChannelDirectTcpip.java | 11 +--
 .../apache/sshd/client/channel/ChannelExec.java |  2 +-
 .../sshd/client/channel/ChannelShell.java       |  2 +-
 .../sshd/client/channel/ChannelSubsystem.java   |  5 +-
 .../channel/PtyCapableChannelSession.java       |  6 +-
 .../org/apache/sshd/client/kex/DHGClient.java   |  2 +-
 .../org/apache/sshd/client/kex/DHGEXClient.java |  2 +-
 .../client/session/AbstractClientSession.java   | 74 +++++++-------------
 .../client/session/ClientConnectionService.java |  2 +-
 .../common/forward/DefaultTcpipForwarder.java   | 17 +++--
 .../sshd/common/forward/TcpipClientChannel.java | 12 +++-
 .../sshd/server/x11/X11ForwardSupport.java      |  8 ++-
 13 files changed, 68 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
index 78003c9..0cc419c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
@@ -285,7 +285,7 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
         log.debug("Send SSH_MSG_CHANNEL_OPEN on channel {}", this);
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN);
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN, type.length() + Integer.SIZE);
         buffer.putString(type);
         buffer.putInt(getId());
         buffer.putInt(localWindow.getSize());

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelDirectTcpip.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelDirectTcpip.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelDirectTcpip.java
index 921b419..4f1aa99 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelDirectTcpip.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelDirectTcpip.java
@@ -70,18 +70,21 @@ public class ChannelDirectTcpip extends AbstractClientChannel {
 
         openFuture = new DefaultOpenFuture(lock);
         if (log.isDebugEnabled()) {
-            log.debug("Send SSH_MSG_CHANNEL_OPEN on channel {}", getId());
+            log.debug("open({}) SSH_MSG_CHANNEL_OPEN", this);
         }
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN);
+        String remoteName = remote.getHostName();
+        String localName = local.getHostName();
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN,
+                            type.length() + remoteName.length() + localName.length() + Long.SIZE);
         buffer.putString(type);
         buffer.putInt(getId());
         buffer.putInt(localWindow.getSize());
         buffer.putInt(localWindow.getPacketSize());
-        buffer.putString(remote.getHostName());
+        buffer.putString(remoteName);
         buffer.putInt(remote.getPort());
-        buffer.putString(local.getHostName());
+        buffer.putString(localName);
         buffer.putInt(local.getPort());
         writePacket(buffer);
         return openFuture;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
index 813b292..4079cb6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
@@ -48,7 +48,7 @@ public class ChannelExec extends PtyCapableChannelSession {
         }
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST);
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, command.length() + Integer.SIZE);
         buffer.putInt(getRecipient());
         buffer.putString("exec");
         buffer.putBoolean(false);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
index fb7b94e..d67ac60 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
@@ -44,7 +44,7 @@ public class ChannelShell extends PtyCapableChannelSession {
         }
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST);
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Integer.SIZE);
         buffer.putInt(getRecipient());
         buffer.putString("shell");
         buffer.putBoolean(false);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSubsystem.java
index 1880990..37434da 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSubsystem.java
@@ -59,11 +59,12 @@ public class ChannelSubsystem extends ChannelSession {
         }
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST);
+        String systemName = getSubsystem();
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, systemName.length() + Integer.SIZE);
         buffer.putInt(getRecipient());
         buffer.putString("subsystem");
         buffer.putBoolean(false);
-        buffer.putString(getSubsystem());
+        buffer.putString(systemName);
         writePacket(buffer);
 
         super.doOpen();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
index 75ce2a4..cce09af 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
@@ -211,7 +211,7 @@ public class PtyCapableChannelSession extends ChannelSession {
         ptyWidth = width;
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST);
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Long.SIZE);
         buffer.putInt(getRecipient());
         buffer.putString("window-change");
         buffer.putBoolean(false);
@@ -229,7 +229,7 @@ public class PtyCapableChannelSession extends ChannelSession {
                 log.debug("doOpenPty({}) Send agent forwarding request", this);
             }
 
-            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST);
+            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Long.SIZE);
             buffer.putInt(getRecipient());
             buffer.putString("auth-agent-req@openssh.com");
             buffer.putBoolean(false);
@@ -242,7 +242,7 @@ public class PtyCapableChannelSession extends ChannelSession {
                           this, ptyType, ptyColumns, ptyLines, ptyHeight, ptyWidth, ptyModes);
             }
 
-            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST);
+            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Byte.MAX_VALUE);
             buffer.putInt(getRecipient());
             buffer.putString("pty-req");
             buffer.putBoolean(false);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index c676c35..1d16973 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -87,7 +87,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
         if (log.isDebugEnabled()) {
             log.debug("init({})[{}] Send SSH_MSG_KEXDH_INIT", this, s);
         }
-        Buffer buffer = s.createBuffer(SshConstants.SSH_MSG_KEXDH_INIT);
+        Buffer buffer = s.createBuffer(SshConstants.SSH_MSG_KEXDH_INIT, e.length + Integer.SIZE);
         buffer.putMPInt(e);
 
         s.writePacket(buffer);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
index c521749..36feac4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
@@ -92,7 +92,7 @@ public class DHGEXClient extends AbstractDHClientKeyExchange {
         if (log.isDebugEnabled()) {
             log.debug("init({}) Send SSH_MSG_KEX_DH_GEX_REQUEST", s);
         }
-        Buffer buffer = s.createBuffer(SshConstants.SSH_MSG_KEX_DH_GEX_REQUEST);
+        Buffer buffer = s.createBuffer(SshConstants.SSH_MSG_KEX_DH_GEX_REQUEST, Integer.SIZE);
         buffer.putInt(min);
         buffer.putInt(prf);
         buffer.putInt(max);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index 76d042b..d5b6066 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -22,11 +22,12 @@ package org.apache.sshd.client.session;
 import java.io.IOException;
 import java.nio.file.FileSystem;
 import java.security.KeyPair;
-import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.sshd.client.ClientFactoryManager;
+import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
+import org.apache.sshd.client.auth.PasswordIdentityProvider;
 import org.apache.sshd.client.auth.UserAuth;
 import org.apache.sshd.client.auth.UserInteraction;
 import org.apache.sshd.client.channel.ChannelDirectTcpip;
@@ -58,46 +59,17 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public abstract class AbstractClientSession extends AbstractSession implements ClientSession {
-    /**
-     * Compares 2 password identities - returns zero ONLY if <U>both</U> compared
-     * objects are {@link String}s and equal to each other
-     */
-    public static final Comparator<Object> PASSWORD_IDENTITY_COMPARATOR = new Comparator<Object>() {
-        @Override
-        public int compare(Object o1, Object o2) {
-            if (!(o1 instanceof String) || !(o2 instanceof String)) {
-                return -1;
-            } else {
-                return ((String) o1).compareTo((String) o2);
-            }
-        }
-    };
-
-    /**
-     * Compares 2 {@link KeyPair} identities - returns zero ONLY if <U>both</U> compared
-     * objects are {@link KeyPair}s and equal to each other
-     */
-    public static final Comparator<Object> KEYPAIR_IDENTITY_COMPARATOR = new Comparator<Object>() {
-        @Override
-        public int compare(Object o1, Object o2) {
-            if ((!(o1 instanceof KeyPair)) || (!(o2 instanceof KeyPair))) {
-                return -1;
-            } else if (KeyUtils.compareKeyPairs((KeyPair) o1, (KeyPair) o2)) {
-                return 0;
-            } else {
-                return 1;
-            }
-        }
-    };
-
-    private final List<Object> identities = new ArrayList<>();
+    private final List<Object> identities = new CopyOnWriteArrayList<>();
+    private final AuthenticationIdentitiesProvider identitiesProvider;
     private ServerKeyVerifier serverKeyVerifier;
     private UserInteraction userInteraction;
+    private PasswordIdentityProvider passwordIdentityProvider;
     private List<NamedFactory<UserAuth>> userAuthFactories;
     private ScpTransferEventListener scpListener;
 
     protected AbstractClientSession(ClientFactoryManager factoryManager, IoSession ioSession) {
         super(false, factoryManager, ioSession);
+        identitiesProvider = AuthenticationIdentitiesProvider.Utils.wrap(identities);
     }
 
     @Override
@@ -135,8 +107,19 @@ public abstract class AbstractClientSession extends AbstractSession implements C
         this.userAuthFactories = userAuthFactories; // OK if null/empty - inherit from parent
     }
 
-    protected List<Object> getRegisteredIdentities() {
-        return identities;
+    @Override
+    public AuthenticationIdentitiesProvider getRegisteredIdentities() {
+        return identitiesProvider;
+    }
+
+    @Override
+    public PasswordIdentityProvider getPasswordIdentityProvider() {
+        return resolveEffectiveProvider(PasswordIdentityProvider.class, passwordIdentityProvider, getFactoryManager().getPasswordIdentityProvider());
+    }
+
+    @Override
+    public void setPasswordIdentityProvider(PasswordIdentityProvider provider) {
+        passwordIdentityProvider = provider;
     }
 
     @Override
@@ -153,7 +136,8 @@ public abstract class AbstractClientSession extends AbstractSession implements C
             return null;
         }
 
-        int index = findIdentityIndex(PASSWORD_IDENTITY_COMPARATOR, password);
+        int index = AuthenticationIdentitiesProvider.Utils.findIdentityIndex(
+                identities, AuthenticationIdentitiesProvider.Utils.PASSWORD_IDENTITY_COMPARATOR, password);
         if (index >= 0) {
             return (String) identities.remove(index);
         } else {
@@ -180,7 +164,8 @@ public abstract class AbstractClientSession extends AbstractSession implements C
             return null;
         }
 
-        int index = findIdentityIndex(KEYPAIR_IDENTITY_COMPARATOR, kp);
+        int index = AuthenticationIdentitiesProvider.Utils.findIdentityIndex(
+                identities, AuthenticationIdentitiesProvider.Utils.KEYPAIR_IDENTITY_COMPARATOR, kp);
         if (index >= 0) {
             return (KeyPair) identities.remove(index);
         } else {
@@ -188,17 +173,6 @@ public abstract class AbstractClientSession extends AbstractSession implements C
         }
     }
 
-    protected int findIdentityIndex(Comparator<? super Object> comp, Object target) {
-        for (int index = 0; index < identities.size(); index++) {
-            Object value = identities.get(index);
-            if (comp.compare(value, target) == 0) {
-                return index;
-            }
-        }
-
-        return -1;
-    }
-
     @Override
     public ClientChannel createChannel(String type) throws IOException {
         return createChannel(type, null);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
index db400aa..356efbf 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
@@ -72,7 +72,7 @@ public class ClientConnectionService extends AbstractConnectionService {
     protected void sendHeartBeat() {
         String request = PropertyResolverUtils.getStringProperty(session, ClientFactoryManager.HEARTBEAT_REQUEST, ClientFactoryManager.DEFAULT_KEEP_ALIVE_HEARTBEAT_STRING);
         try {
-            Buffer buf = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST);
+            Buffer buf = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST, request.length() + Byte.SIZE);
             buf.putString(request);
             buf.putBoolean(false);
             session.writePacket(buf);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
index f84523e..803bf1b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
@@ -149,16 +149,18 @@ public class DefaultTcpipForwarder extends AbstractInnerCloseable implements Tcp
         ValidateUtils.checkNotNull(local, "Local address is null");
         ValidateUtils.checkNotNull(remote, "Remote address is null");
 
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST);
+        String remoteHost = remote.getHostName();
+        int remotePort = remote.getPort();
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST, remoteHost.length() + Long.SIZE);
         buffer.putString("tcpip-forward");
         buffer.putBoolean(true);
-        buffer.putString(remote.getHostName());
-        buffer.putInt(remote.getPort());
+        buffer.putString(remoteHost);
+        buffer.putInt(remotePort);
         Buffer result = session.request(buffer);
         if (result == null) {
             throw new SshException("Tcpip forwarding request denied by server");
         }
-        int port = (remote.getPort() == 0) ? result.getInt() : remote.getPort();
+        int port = (remotePort == 0) ? result.getInt() : remote.getPort();
         // TODO: Is it really safe to only store the local address after the request ?
         SshdSocketAddress prev;
         synchronized (remoteToLocal) {
@@ -169,7 +171,7 @@ public class DefaultTcpipForwarder extends AbstractInnerCloseable implements Tcp
             throw new IOException("Multiple remote port forwarding bindings on port=" + port + ": current=" + remote + ", previous=" + prev);
         }
 
-        SshdSocketAddress bound = new SshdSocketAddress(remote.getHostName(), port);
+        SshdSocketAddress bound = new SshdSocketAddress(remoteHost, port);
         if (log.isDebugEnabled()) {
             log.debug("startRemotePortForwarding(" + remote + " -> " + local + "): " + bound);
         }
@@ -189,10 +191,11 @@ public class DefaultTcpipForwarder extends AbstractInnerCloseable implements Tcp
                 log.debug("stopRemotePortForwarding(" + remote + ") cancel forwarding to " + bound);
             }
 
-            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST);
+            String remoteHost = remote.getHostName();
+            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST, remoteHost.length() + Long.SIZE);
             buffer.putString("cancel-tcpip-forward");
             buffer.putBoolean(false);
-            buffer.putString(remote.getHostName());
+            buffer.putString(remoteHost);
             buffer.putInt(remote.getPort());
             session.writePacket(buffer);
         } else {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
index a1a8f78..a5b4281 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
@@ -19,6 +19,7 @@
 package org.apache.sshd.common.forward;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 
 import org.apache.sshd.client.channel.AbstractClientChannel;
@@ -90,14 +91,19 @@ public class TcpipClientChannel extends AbstractClientChannel {
         }
 
         Session session = getSession();
-        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN);
+        InetAddress srcAddress = src.getAddress();
+        String srcHost = srcAddress.getHostAddress();
+        InetAddress dstAddress = dst.getAddress();
+        String dstHost = dstAddress.getHostAddress();
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN,
+                type.length() + srcHost.length() + dstHost.length() + Long.SIZE);
         buffer.putString(type);
         buffer.putInt(getId());
         buffer.putInt(localWindow.getSize());
         buffer.putInt(localWindow.getPacketSize());
-        buffer.putString(dst.getAddress().getHostAddress());
+        buffer.putString(dstHost);
         buffer.putInt(dst.getPort());
-        buffer.putString(src.getAddress().getHostAddress());
+        buffer.putString(srcHost);
         buffer.putInt(src.getPort());
         writePacket(buffer);
         return openFuture;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/fa724c2f/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
index afa8735..96f0a43 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
@@ -21,6 +21,7 @@ package org.apache.sshd.server.x11;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.BindException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.concurrent.TimeUnit;
 
@@ -201,12 +202,15 @@ public class X11ForwardSupport extends AbstractInnerCloseable implements IoHandl
                 log.debug("open({}) SSH_MSG_CHANNEL_OPEN", this);
             }
 
-            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN);
+            InetAddress remoteAddress = remote.getAddress();
+            String remoteHost = remoteAddress.getHostAddress();
+            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_OPEN,
+                    remoteHost.length() + type.length() + Integer.SIZE);
             buffer.putString(type);
             buffer.putInt(getId());
             buffer.putInt(localWindow.getSize());
             buffer.putInt(localWindow.getPacketSize());
-            buffer.putString(remote.getAddress().getHostAddress());
+            buffer.putString(remoteHost);
             buffer.putInt(remote.getPort());
             writePacket(buffer);
             return openFuture;


[2/4] mina-sshd git commit: [SSHD-613] Support a 'callback' method for session password identities

Posted by lg...@apache.org.
[SSHD-613] Support a 'callback' method for session password identities


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

Branch: refs/heads/master
Commit: 13818de0c58decf8bf7e04f47517d35f9e6eb1c9
Parents: 1539faa
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Dec 14 10:47:46 2015 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Dec 14 10:47:46 2015 +0200

----------------------------------------------------------------------
 pom.xml                                         |   2 +-
 .../client/ClientAuthenticationManager.java     |  41 +++-
 .../java/org/apache/sshd/client/SshClient.java  | 144 +++++++++++--
 .../sshd/client/auth/AbstractUserAuth.java      |   4 +-
 .../auth/AuthenticationIdentitiesProvider.java  | 141 +++++++++++++
 .../client/auth/PasswordIdentityProvider.java   | 146 ++++++++++++++
 .../org/apache/sshd/client/auth/UserAuth.java   |   5 +-
 .../auth/UserAuthKeyboardInteractive.java       |  16 +-
 .../sshd/client/auth/UserAuthPassword.java      |  41 ++--
 .../sshd/client/auth/UserAuthPublicKey.java     | 100 +++------
 .../client/auth/pubkey/KeyAgentIdentity.java    |  19 +-
 .../client/auth/pubkey/KeyPairIdentity.java     |  10 +-
 .../auth/pubkey/SessionKeyPairIterator.java     |  60 ++++++
 .../auth/pubkey/SshAgentPublicKeyIterator.java  |  73 +++++++
 .../auth/pubkey/UserAuthPublicKeyIterator.java  | 147 ++++++++++++++
 .../sshd/client/session/ClientSession.java      |  30 +--
 .../sshd/client/session/ClientSessionImpl.java  |  16 +-
 .../client/session/ClientUserAuthService.java   |   9 +-
 .../common/keyprovider/KeyIdentityProvider.java | 149 ++++++++++++++
 .../common/keyprovider/KeyPairProvider.java     |   9 +-
 .../apache/sshd/common/util/GenericUtils.java   |  73 +++++++
 .../server/session/ServerUserAuthService.java   |   3 +-
 .../client/ClientAuthenticationManagerTest.java | 201 +++++++++++++++++++
 .../java/org/apache/sshd/client/ClientTest.java |   4 +-
 .../auth/PasswordIdentityProviderTest.java      |  69 +++++++
 .../BuiltinClientIdentitiesWatcherTest.java     |   3 +-
 .../client/config/keys/ClientIdentityTest.java  |   4 +-
 .../client/session/ClientSessionImplTest.java   | 144 -------------
 .../sshd/common/auth/AuthenticationTest.java    | 153 +++++++++-----
 .../java/org/apache/sshd/util/test/Utils.java   |   5 +-
 30 files changed, 1435 insertions(+), 386 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 18697cf..d1fe10e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -530,7 +530,7 @@
 									<!-- Checks for Size Violations.                    -->
 									<!-- See http://checkstyle.sf.net/config_sizes.html -->
 								<module name="AnonInnerLength">
-									<property name="max" value="40" />
+									<property name="max" value="50" />
 								</module>
 								<module name="ExecutableStatementCount">
 									<property name="max" value="100" />

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
index de41b9a..ab1f154 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
@@ -19,18 +19,22 @@
 
 package org.apache.sshd.client;
 
+import java.security.KeyPair;
 import java.util.List;
 
+import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
+import org.apache.sshd.client.auth.PasswordIdentityProvider;
 import org.apache.sshd.client.auth.UserAuth;
 import org.apache.sshd.client.auth.UserInteraction;
 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
 
 /**
  * Holds information required for the client to perform authentication with the server
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface ClientAuthenticationManager {
+public interface ClientAuthenticationManager extends KeyPairProviderHolder {
 
     /**
      * Ordered comma separated list of authentications methods.
@@ -51,6 +55,41 @@ public interface ClientAuthenticationManager {
      */
     int DEFAULT_PASSWORD_PROMPTS = 3;
 
+    AuthenticationIdentitiesProvider getRegisteredIdentities();
+
+    PasswordIdentityProvider getPasswordIdentityProvider();
+    void setPasswordIdentityProvider(PasswordIdentityProvider provider);
+
+    /**
+     * @param password Password to be added - may not be {@code null}/empty.
+     * <B>Note:</B> this password is <U>in addition</U> to whatever passwords
+     * are available via the {@link PasswordIdentityProvider} (if any)
+     */
+    void addPasswordIdentity(String password);
+
+    /**
+     * @param password The password to remove - ignored if {@code null}/empty
+     * @return The removed password - same one that was added via
+     * {@link #addPasswordIdentity(String)} - or {@code null} if no
+     * match found
+     */
+    String removePasswordIdentity(String password);
+
+    /**
+     * @param key The {@link KeyPair} to add - may not be {@code null}
+     * <B>Note:</B> this key is <U>in addition</U> to whatever keys
+     * are available via the {@link org.apache.sshd.common.keyprovider.KeyIdentityProvider} (if any)
+     */
+    void addPublicKeyIdentity(KeyPair key);
+
+    /**
+     * @param kp The {@link KeyPair} to remove - ignored if {@code null}
+     * @return The removed {@link KeyPair} - same one that was added via
+     * {@link #addPublicKeyIdentity(KeyPair)} - or {@code null} if no
+     * match found
+     */
+    KeyPair removePublicKeyIdentity(KeyPair kp);
+
     /**
      * Retrieve the server key verifier to be used to check the key when connecting
      * to an SSH server.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 9081d82..e64b4cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -38,9 +38,11 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Formatter;
 import java.util.logging.Handler;
@@ -49,6 +51,8 @@ import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
+import org.apache.sshd.client.auth.PasswordIdentityProvider;
 import org.apache.sshd.client.auth.UserAuth;
 import org.apache.sshd.client.auth.UserAuthKeyboardInteractiveFactory;
 import org.apache.sshd.client.auth.UserAuthPasswordFactory;
@@ -178,9 +182,12 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     private HostConfigEntryResolver hostConfigEntryResolver;
     private ClientIdentityLoader clientIdentityLoader;
     private FilePasswordProvider filePasswordProvider;
+    private PasswordIdentityProvider passwordIdentityProvider;
+    private final List<Object> identities = new CopyOnWriteArrayList<>();
+    private final AuthenticationIdentitiesProvider identitiesProvider;
 
     public SshClient() {
-        super();
+        identitiesProvider = AuthenticationIdentitiesProvider.Utils.wrap(identities);
     }
 
     public SessionFactory getSessionFactory() {
@@ -252,6 +259,72 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     }
 
     @Override
+    public AuthenticationIdentitiesProvider getRegisteredIdentities() {
+        return identitiesProvider;
+    }
+
+    @Override
+    public PasswordIdentityProvider getPasswordIdentityProvider() {
+        return passwordIdentityProvider;
+    }
+
+    @Override
+    public void setPasswordIdentityProvider(PasswordIdentityProvider provider) {
+        passwordIdentityProvider = provider;
+    }
+
+    @Override
+    public void addPasswordIdentity(String password) {
+        identities.add(ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided"));
+        if (log.isDebugEnabled()) { // don't show the password in the log
+            log.debug("addPasswordIdentity({}) {}", this, KeyUtils.getFingerPrint(password));
+        }
+    }
+
+    @Override
+    public String removePasswordIdentity(String password) {
+        if (GenericUtils.isEmpty(password)) {
+            return null;
+        }
+
+        int index = AuthenticationIdentitiesProvider.Utils.findIdentityIndex(
+                identities, AuthenticationIdentitiesProvider.Utils.PASSWORD_IDENTITY_COMPARATOR, password);
+        if (index >= 0) {
+            return (String) identities.remove(index);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void addPublicKeyIdentity(KeyPair kp) {
+        ValidateUtils.checkNotNull(kp, "No key-pair to add");
+        ValidateUtils.checkNotNull(kp.getPublic(), "No public key");
+        ValidateUtils.checkNotNull(kp.getPrivate(), "No private key");
+
+        identities.add(kp);
+
+        if (log.isDebugEnabled()) {
+            log.debug("addPublicKeyIdentity({}) {}", this, KeyUtils.getFingerPrint(kp.getPublic()));
+        }
+    }
+
+    @Override
+    public KeyPair removePublicKeyIdentity(KeyPair kp) {
+        if (kp == null) {
+            return null;
+        }
+
+        int index = AuthenticationIdentitiesProvider.Utils.findIdentityIndex(
+                identities, AuthenticationIdentitiesProvider.Utils.KEYPAIR_IDENTITY_COMPARATOR, kp);
+        if (index >= 0) {
+            return (KeyPair) identities.remove(index);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
     protected void checkConfig() {
         super.checkConfig();
 
@@ -501,29 +574,18 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         session.setUsername(username);
 
         if (useDefaultIdentities) {
-            // check if session listener intervened
-            KeyPairProvider kpSession = session.getKeyPairProvider();
-            KeyPairProvider kpClient = ValidateUtils.checkNotNull(getKeyPairProvider(), "No default key-pair provider");
-            if (kpSession == null) {
-                session.setKeyPairProvider(kpClient);
-            } else {
-                if (kpSession != kpClient) {
-                    if (log.isDebugEnabled()) {
-                        log.debug("onConnectOperationComplete({}) key-pair provider override", session);
-                    }
-                }
-            }
+            setupDefaultSessionIdentities(session);
         }
 
         int numIds = GenericUtils.size(identities);
         if (numIds > 0) {
             if (log.isDebugEnabled()) {
-                log.debug("onConnectOperationComplete({}@{}) adding {} identities", username, address, numIds);
+                log.debug("onConnectOperationComplete({}) adding {} identities", session, numIds);
             }
             for (KeyPair kp : identities) {
                 if (log.isTraceEnabled()) {
-                    log.trace("onConnectOperationComplete({}@{}) add identity type={}, fingerprint={}",
-                              username, address, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
+                    log.trace("onConnectOperationComplete({}) add identity type={}, fingerprint={}",
+                              session, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
                 }
                 session.addPublicKeyIdentity(kp);
             }
@@ -532,6 +594,56 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         connectFuture.setSession(session);
     }
 
+    protected void setupDefaultSessionIdentities(ClientSession session) {
+        // check if session listener intervened
+        KeyPairProvider kpSession = session.getKeyPairProvider();
+        KeyPairProvider kpClient = getKeyPairProvider();
+        if (kpSession == null) {
+            session.setKeyPairProvider(kpClient);
+        } else {
+            if (kpSession != kpClient) {
+                if (log.isDebugEnabled()) {
+                    log.debug("setupDefaultSessionIdentities({}) key-pair provider override", session);
+                }
+            }
+        }
+
+        PasswordIdentityProvider passSession = session.getPasswordIdentityProvider();
+        PasswordIdentityProvider passClient = getPasswordIdentityProvider();
+        if (passSession == null) {
+            session.setPasswordIdentityProvider(passClient);
+        } else {
+            if (passSession != passClient) {
+                if (log.isDebugEnabled()) {
+                    log.debug("setupDefaultSessionIdentities({}) password provider override", session);
+                }
+            }
+        }
+
+        AuthenticationIdentitiesProvider idsClient = getRegisteredIdentities();
+        for (Iterator<?> iter = GenericUtils.iteratorOf((idsClient == null) ? null : idsClient.loadIdentities()); iter.hasNext();) {
+            Object id = iter.next();
+            if (id instanceof String) {
+                if (log.isTraceEnabled()) {
+                    log.trace("setupDefaultSessionIdentities({}) add password fingerprint={}",
+                              session, KeyUtils.getFingerPrint(id.toString()));
+                }
+                session.addPasswordIdentity((String) id);
+            } else if (id instanceof KeyPair) {
+                KeyPair kp = (KeyPair) id;
+                if (log.isTraceEnabled()) {
+                    log.trace("setupDefaultSessionIdentities({}) add identity type={}, fingerprint={}",
+                              session, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
+                }
+                session.addPublicKeyIdentity(kp);
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("setupDefaultSessionIdentities({}) ignored identity={}", session, id);
+                }
+            }
+        }
+    }
+
     protected IoConnector createConnector() {
         return getIoServiceFactory().createConnector(getSessionFactory());
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 7cc3348..9b22eb7 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
@@ -19,8 +19,6 @@
 
 package org.apache.sshd.client.auth;
 
-import java.util.Collection;
-
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -58,7 +56,7 @@ public abstract class AbstractUserAuth extends AbstractLoggingBean implements Us
     }
 
     @Override
-    public void init(ClientSession session, String service, Collection<?> identities) throws Exception {
+    public void init(ClientSession session, String service) throws Exception {
         this.clientSession = ValidateUtils.checkNotNull(session, "No client session");
         this.service = ValidateUtils.checkNotNullAndNotEmpty(service, "No service");
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
new file mode 100644
index 0000000..2d0637b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface AuthenticationIdentitiesProvider extends KeyIdentityProvider, PasswordIdentityProvider {
+    /**
+     * @return All the currently available identities - passwords, keys, etc...
+     */
+    Iterable<?> loadIdentities();
+
+    /**
+     * A helper class for identity provider related operations
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     */
+    // CHECKSTYLE:OFF
+    final class Utils {
+    // CHECKSTYLE:ON
+        /**
+         * Compares 2 password identities - returns zero ONLY if <U>both</U> compared
+         * objects are {@link String}s and equal to each other
+         */
+        public static final Comparator<Object> PASSWORD_IDENTITY_COMPARATOR = new Comparator<Object>() {
+            @Override
+            public int compare(Object o1, Object o2) {
+                if (!(o1 instanceof String) || !(o2 instanceof String)) {
+                    return -1;
+                } else {
+                    return ((String) o1).compareTo((String) o2);
+                }
+            }
+        };
+
+        /**
+         * Compares 2 {@link KeyPair} identities - returns zero ONLY if <U>both</U> compared
+         * objects are {@link KeyPair}s and equal to each other
+         */
+        public static final Comparator<Object> KEYPAIR_IDENTITY_COMPARATOR = new Comparator<Object>() {
+            @Override
+            public int compare(Object o1, Object o2) {
+                if ((!(o1 instanceof KeyPair)) || (!(o2 instanceof KeyPair))) {
+                    return -1;
+                } else if (KeyUtils.compareKeyPairs((KeyPair) o1, (KeyPair) o2)) {
+                    return 0;
+                } else {
+                    return 1;
+                }
+            }
+        };
+
+        private Utils() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+
+        public static int findIdentityIndex(List<?> identities, Comparator<? super Object> comp, Object target) {
+            for (int index = 0; index < identities.size(); index++) {
+                Object value = identities.get(index);
+                if (comp.compare(value, target) == 0) {
+                    return index;
+                }
+            }
+
+            return -1;
+        }
+
+        /**
+         * @param identities The {@link Iterable} identities - OK if {@code null}/empty
+         * @return An {@link AuthenticationIdentitiesProvider} wrapping the identities
+         */
+        public static AuthenticationIdentitiesProvider wrap(final Iterable<?> identities) {
+            return new AuthenticationIdentitiesProvider() {
+                @Override
+                public Iterable<KeyPair> loadKeys() {
+                    return selectIdentities(KeyPair.class);
+                }
+
+                @Override
+                public Iterable<String> loadPasswords() {
+                    return selectIdentities(String.class);
+                }
+
+                @Override
+                public Iterable<?> loadIdentities() {
+                    return selectIdentities(Object.class);
+                }
+
+                // NOTE: returns a NEW Collection on every call so that the original
+                //      identities remain unchanged
+                private <T> Collection<T> selectIdentities(Class<T> type) {
+                    Collection<T> matches = null;
+                    for (Iterator<?> iter = GenericUtils.iteratorOf(identities); iter.hasNext();) {
+                        Object o = iter.next();
+                        Class<?> t = o.getClass();
+                        if (!type.isAssignableFrom(t)) {
+                            continue;
+                        }
+
+                        if (matches == null) {
+                            matches = new LinkedList<T>();
+                        }
+
+                        matches.add(type.cast(o));
+                    }
+
+                    return (matches == null) ? Collections.<T>emptyList() : matches;
+                }
+            };
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/auth/PasswordIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/PasswordIdentityProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/PasswordIdentityProvider.java
new file mode 100644
index 0000000..26811db
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/PasswordIdentityProvider.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Supplier;
+import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PasswordIdentityProvider {
+    PasswordIdentityProvider EMPTY_PASSWORDS_PROVIDER = new PasswordIdentityProvider() {
+        @Override
+        public Iterable<String> loadPasswords() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
+     * @return The currently available passwords - never {@code null}
+     */
+    Iterable<String> loadPasswords();
+
+    /**
+     * A helper class for password identity provider related operations
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     */
+    // CHECKSTYLE:OFF
+    final class Utils {
+    // CHECKSTYLE:ON
+        public static final Transformer<PasswordIdentityProvider, Iterable<String>> LOADER =
+            new Transformer<PasswordIdentityProvider, Iterable<String>>() {
+                @Override
+                public Iterable<String> transform(PasswordIdentityProvider p) {
+                    return (p == null) ? null : p.loadPasswords();
+                }
+            };
+
+        private Utils() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+
+        public static Iterator<String> iteratorOf(ClientSession session) {
+            ValidateUtils.checkNotNull(session, "No session");
+            return iteratorOf(session.getRegisteredIdentities(), session.getPasswordIdentityProvider());
+        }
+
+        public static Iterator<String> iteratorOf(PasswordIdentityProvider identities, PasswordIdentityProvider passwords) {
+            return iteratorOf(resolvePasswordIdentityProvider(identities, passwords));
+        }
+
+        /**
+         * Resolves a non-{@code null} iterator of the available passwords
+         *
+         * @param provider The {@link PasswordIdentityProvider} - ignored if {@code null}
+         * @return A non-{@code null} iterator - which may be empty if no provider or no passwords
+         */
+        public static Iterator<String> iteratorOf(PasswordIdentityProvider provider) {
+            return GenericUtils.iteratorOf((provider == null) ? null : provider.loadPasswords());
+        }
+
+        public static PasswordIdentityProvider resolvePasswordIdentityProvider(PasswordIdentityProvider identities, PasswordIdentityProvider passwords) {
+            if ((passwords == null) || (identities == passwords)) {
+                return identities;
+            } else if (identities == null) {
+                return passwords;
+            } else {
+                return multiProvider(identities, passwords);
+            }
+        }
+
+        public static PasswordIdentityProvider multiProvider(PasswordIdentityProvider ... providers) {
+            return multiProvider(GenericUtils.isEmpty(providers) ? Collections.<PasswordIdentityProvider>emptyList() : Arrays.asList(providers));
+        }
+
+        public static PasswordIdentityProvider multiProvider(Collection<? extends PasswordIdentityProvider> providers) {
+            return wrap(iterableOf(providers));
+        }
+
+        public static Iterable<String> iterableOf(Collection<? extends PasswordIdentityProvider> providers) {
+            if (GenericUtils.isEmpty(providers)) {
+                return Collections.emptyList();
+            }
+
+            Collection<Supplier<Iterable<String>>> suppliers = new ArrayList<Supplier<Iterable<String>>>(providers.size());
+            for (final PasswordIdentityProvider p : providers) {
+                if (p == null) {
+                    continue;
+                }
+
+                suppliers.add(new Supplier<Iterable<String>>() {
+                    @Override
+                    public Iterable<String> get() {
+                        return p.loadPasswords();
+                    }
+                });
+            }
+
+            if (GenericUtils.isEmpty(suppliers)) {
+                return Collections.emptyList();
+            }
+
+            return GenericUtils.multiIterableSuppliers(suppliers);
+        }
+
+        public static PasswordIdentityProvider wrap(final Iterable<String> passwords) {
+            return new PasswordIdentityProvider() {
+                @Override
+                public Iterable<String> loadPasswords() {
+                    return passwords;
+                }
+            };
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 44a111a..633e761 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
@@ -18,8 +18,6 @@
  */
 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;
@@ -34,10 +32,9 @@ public interface UserAuth extends ClientSessionHolder, UserAuthInstance<ClientSe
     /**
      * @param session The {@link ClientSession}
      * @param service The requesting service name
-     * @param identities The currently available identities - e.g., password, keys, etc.
      * @throws Exception If failed to initialize the mechanism
      */
-    void init(ClientSession session, String service, Collection<?> identities) throws Exception;
+    void init(ClientSession session, String service) throws Exception;
 
     /**
      * @param buffer The {@link Buffer} to process - {@code null} if not a response buffer,

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 1863379..9327236 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
@@ -18,11 +18,8 @@
  */
 package org.apache.sshd.client.auth;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Iterator;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -87,16 +84,9 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
     }
 
     @Override
-    public void init(ClientSession session, String service, Collection<?> identities) throws Exception {
-        super.init(session, service, identities);
-
-        List<String> pwds = new ArrayList<>();
-        for (Object o : identities) {
-            if (o instanceof String) {
-                pwds.add((String) o);
-            }
-        }
-        passwords = pwds.iterator();
+    public void init(ClientSession session, String service) throws Exception {
+        super.init(session, service);
+        passwords = PasswordIdentityProvider.Utils.iteratorOf(session);
         maxTrials = PropertyResolverUtils.getIntProperty(session, ClientAuthenticationManager.PASSWORD_PROMPTS, ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS);
         ValidateUtils.checkTrue(maxTrials > 0, "Non-positive max. trials: %d", maxTrials);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 449d10a..d4ed6f7 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
@@ -19,10 +19,7 @@
 package org.apache.sshd.client.auth;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Objects;
 
 import org.apache.sshd.client.session.ClientSession;
@@ -32,8 +29,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 
 /**
- * TODO Add javadoc
- *
+ * Implements the &quot;password&quot; authentication mechanism
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthPassword extends AbstractUserAuth {
@@ -47,34 +43,27 @@ public class UserAuthPassword extends AbstractUserAuth {
     }
 
     @Override
-    public void init(ClientSession session, String service, Collection<?> identities) throws Exception {
-        super.init(session, service, identities);
-
-        List<String> pwds = new ArrayList<>();
-        for (Object o : identities) {
-            if (o instanceof String) {
-                pwds.add((String) o);
-            }
-        }
-        this.passwords = pwds.iterator();
+    public void init(ClientSession session, String service) throws Exception {
+        super.init(session, service);
+        passwords = PasswordIdentityProvider.Utils.iteratorOf(session);
     }
 
     @Override
     protected boolean sendAuthDataRequest(ClientSession session, String service) throws Exception {
-        if (passwords.hasNext()) {
-            current = passwords.next();
-            String username = session.getUsername();
-            Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST,
-                                username.length() + service.length() + getName().length() + current.length() + Integer.SIZE);
-            sendPassword(buffer, session, current, current);
-            return true;
-        }
+        if ((passwords == null) || (!passwords.hasNext())) {
+            if (log.isDebugEnabled()) {
+                log.debug("sendAuthDataRequest({})[{}] no more passwords to send", session, service);
+            }
 
-        if (log.isDebugEnabled()) {
-            log.debug("sendAuthDataRequest({})[{}] no more passwords to send", session, service);
+            return false;
         }
 
-        return false;
+        current = passwords.next();
+        String username = session.getUsername();
+        Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST,
+                            username.length() + service.length() + getName().length() + current.length() + Integer.SIZE);
+        sendPassword(buffer, session, current, current);
+        return true;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 8cbfddf..991c0d7 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
@@ -18,41 +18,27 @@
  */
 package org.apache.sshd.client.auth;
 
+import java.io.Closeable;
 import java.io.IOException;
-import java.security.KeyPair;
 import java.security.PublicKey;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
-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.auth.pubkey.UserAuthPublicKeyIterator;
 import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.FactoryManager;
 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.util.GenericUtils;
-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;
 
 /**
- * TODO Add javadoc
- *
+ * Implements the &quot;publickey&quot; authentication mechanism
  * @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;
 
@@ -61,51 +47,15 @@ public class UserAuthPublicKey extends AbstractUserAuth {
     }
 
     @Override
-    public void init(ClientSession session, String service, Collection<?> identities) throws Exception {
-        super.init(session, service, identities);
-
-        List<PublicKeyIdentity> ids = new ArrayList<>();
-        for (Object o : identities) {
-            if (o instanceof KeyPair) {
-                ids.add(new KeyPairIdentity(session, (KeyPair) o));
-            }
-        }
-
-        FactoryManager manager = ValidateUtils.checkNotNull(session.getFactoryManager(), "No session factory manager");
-        SshAgentFactory factory = manager.getAgentFactory();
-        if (factory != null) {
-            this.agent = ValidateUtils.checkNotNull(factory.createClient(manager), "No agent created");
-            Collection<Pair<PublicKey, String>> agentKeys = agent.getIdentities();
-            if (GenericUtils.size(agentKeys) > 0) {
-                for (Pair<PublicKey, String> pair : agentKeys) {
-                    PublicKey key = pair.getFirst();
-                    if (log.isDebugEnabled()) {
-                        log.debug("init({}) add agent public key type={}: comment={}, fingerprint={}",
-                                  session, pair.getSecond(), KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
-                    }
-                    ids.add(new KeyAgentIdentity(agent, key));
-                }
-            }
-        } else {
-            this.agent = null;
-        }
-
-        KeyPairProvider provider = session.getKeyPairProvider();
-        if (provider != null) {
-            for (KeyPair kp : provider.loadKeys()) {
-                if (log.isDebugEnabled()) {
-                    log.debug("init({}) add provider public key type={}: {}",
-                              session, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
-                }
-                ids.add(new KeyPairIdentity(session, kp));
-            }
-        }
-        this.keys = ids.iterator();
+    public void init(ClientSession session, String service) throws Exception {
+        super.init(session, service);
+        releaseKeys();  // just making sure in case multiple calls to the method
+        keys = new UserAuthPublicKeyIterator(session);
     }
 
     @Override
     protected boolean sendAuthDataRequest(ClientSession session, String service) throws Exception {
-        if (!keys.hasNext()) {
+        if ((keys == null) || (!keys.hasNext())) {
             if (log.isDebugEnabled()) {
                 log.debug("sendAuthDataRequest({})[{}] no more keys to send", session, service);
             }
@@ -114,6 +64,10 @@ public class UserAuthPublicKey extends AbstractUserAuth {
         }
 
         current = keys.next();
+        if (log.isTraceEnabled()) {
+            log.trace("sendAuthDataRequest({})[{}] current key details: {}", session, service, current);
+        }
+
         PublicKey key = current.getPublicKey();
         String algo = KeyUtils.getKeyType(key);
         String name = getName();
@@ -121,7 +75,6 @@ public class UserAuthPublicKey extends AbstractUserAuth {
             log.debug("sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}",
                       session, service, name, algo, KeyUtils.getFingerPrint(key));
         }
-
         Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
         buffer.putString(session.getUsername());
         buffer.putString(service);
@@ -145,7 +98,7 @@ public class UserAuthPublicKey extends AbstractUserAuth {
         String algo = KeyUtils.getKeyType(key);
         String name = getName();
         if (log.isDebugEnabled()) {
-            log.debug("processAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_PK_OK reply for {}: type={}, fingerprint={}",
+            log.debug("processAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_PK_OK reply {} type={} - fingerprint={}",
                       session, service, name, algo, KeyUtils.getFingerPrint(key));
         }
 
@@ -181,16 +134,25 @@ public class UserAuthPublicKey extends AbstractUserAuth {
 
     @Override
     public void destroy() {
-        if (agent != null) {
-            try {
-                agent.close();
-            } catch (IOException e) {
-                throw new RuntimeException("Failed (" + e.getClass().getSimpleName() + ") to close agent: " + e.getMessage(), e);
-            } finally {
-                agent = null;
-            }
+        try {
+            releaseKeys();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed (" + e.getClass().getSimpleName() + ") to close agent: " + e.getMessage(), e);
+        }
+
+        super.destroy(); // for logging
+    }
 
-            super.destroy(); // for logging
+    protected void releaseKeys() throws IOException {
+        try {
+            if (keys instanceof Closeable) {
+                if (log.isTraceEnabled()) {
+                    log.trace("releaseKeys({}) closing {}", getClientSession(), keys);
+                }
+                ((Closeable) keys).close();
+            }
+        } finally {
+            keys = null;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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
index e8f09a3..a583e39 100644
--- 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
@@ -21,6 +21,7 @@ package org.apache.sshd.client.auth.pubkey;
 import java.security.PublicKey;
 
 import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -31,10 +32,12 @@ import org.apache.sshd.common.util.ValidateUtils;
 public class KeyAgentIdentity implements PublicKeyIdentity {
     private final SshAgent agent;
     private final PublicKey key;
+    private final String comment;
 
-    public KeyAgentIdentity(SshAgent agent, PublicKey key) {
+    public KeyAgentIdentity(SshAgent agent, PublicKey key, String comment) {
         this.agent = ValidateUtils.checkNotNull(agent, "No signing agent");
         this.key = ValidateUtils.checkNotNull(key, "No public key");
+        this.comment = comment;
     }
 
     @Override
@@ -42,8 +45,20 @@ public class KeyAgentIdentity implements PublicKeyIdentity {
         return key;
     }
 
+    public String getComment() {
+        return comment;
+    }
+
     @Override
     public byte[] sign(byte[] data) throws Exception {
-        return agent.sign(key, data);
+        return agent.sign(getPublicKey(), data);
+    }
+
+    @Override
+    public String toString() {
+        PublicKey pubKey = getPublicKey();
+        return getClass().getSimpleName() + "[" + KeyUtils.getKeyType(pubKey) + "]"
+             + " fingerprint=" + KeyUtils.getFingerPrint(pubKey)
+             + ", comment=" + getComment();
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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
index e84195f..f24b65f 100644
--- 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
@@ -48,7 +48,7 @@ public class KeyPairIdentity implements PublicKeyIdentity {
 
     @Override
     public byte[] sign(byte[] data) throws Exception {
-        String keyType = KeyUtils.getKeyType(pair);
+        String keyType = KeyUtils.getKeyType(getPublicKey());
         Signature verifier = ValidateUtils.checkNotNull(
                 NamedFactory.Utils.create(manager.getSignatureFactories(), keyType),
                 "No signer could be located for key type=%s",
@@ -57,4 +57,12 @@ public class KeyPairIdentity implements PublicKeyIdentity {
         verifier.update(data, 0, data.length);
         return verifier.sign();
     }
+
+    @Override
+    public String toString() {
+        PublicKey pubKey = getPublicKey();
+        return getClass().getSimpleName() + "[" + manager + "]"
+             + " type=" + KeyUtils.getKeyType(pubKey)
+             + ", fingerprint=" + KeyUtils.getFingerPrint(pubKey);
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SessionKeyPairIterator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SessionKeyPairIterator.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SessionKeyPairIterator.java
new file mode 100644
index 0000000..5ff50b4
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SessionKeyPairIterator.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.util.Iterator;
+
+import org.apache.sshd.common.kex.KexFactoryManager;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SessionKeyPairIterator implements Iterator<KeyPairIdentity> {
+
+    private final KexFactoryManager manager;
+    private final Iterator<KeyPair> keys;
+
+    public SessionKeyPairIterator(KexFactoryManager manager, Iterator<KeyPair> keys) {
+        this.manager = ValidateUtils.checkNotNull(manager, "No KEX factory manager");
+        this.keys = keys;   // OK if null
+    }
+
+    @Override
+    public boolean hasNext() {
+        return (keys != null) && keys.hasNext();
+    }
+
+    @Override
+    public KeyPairIdentity next() {
+        return new KeyPairIdentity(manager, keys.next());
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("No removal allowed");
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + manager + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SshAgentPublicKeyIterator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SshAgentPublicKeyIterator.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SshAgentPublicKeyIterator.java
new file mode 100644
index 0000000..8becc4b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/SshAgentPublicKeyIterator.java
@@ -0,0 +1,73 @@
+/*
+ * 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.io.IOException;
+import java.security.PublicKey;
+import java.util.Iterator;
+
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Pair;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SshAgentPublicKeyIterator implements Iterator<KeyAgentIdentity>, ClientSessionHolder {
+    private final ClientSession clientSession;
+    private final SshAgent agent;
+    private final Iterator<Pair<PublicKey, String>> keys;
+
+    public SshAgentPublicKeyIterator(ClientSession session, SshAgent agent) throws IOException {
+        this.clientSession = ValidateUtils.checkNotNull(session, "No session");
+        this.agent = ValidateUtils.checkNotNull(agent, "No agent");
+        keys = GenericUtils.iteratorOf(agent.getIdentities());
+    }
+
+    @Override
+    public ClientSession getClientSession() {
+        return clientSession;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return (keys != null) && keys.hasNext();
+    }
+
+    @Override
+    public KeyAgentIdentity next() {
+        Pair<PublicKey, String> kp = keys.next();
+        return new KeyAgentIdentity(agent, kp.getFirst(), kp.getSecond());
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("No removal allowed");
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + getClientSession() + "]";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
new file mode 100644
index 0000000..83ed94c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
@@ -0,0 +1,147 @@
+/*
+ * 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.io.IOException;
+import java.nio.channels.Channel;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class UserAuthPublicKeyIterator implements Iterator<PublicKeyIdentity>, ClientSessionHolder, Channel {
+
+    private final AtomicBoolean open = new AtomicBoolean(true);
+    private final ClientSession clientSession;
+    private final Iterator<Iterator<? extends PublicKeyIdentity>> iterators;
+    private Iterator<? extends PublicKeyIdentity> current;
+    private SshAgent agent;
+
+    public UserAuthPublicKeyIterator(ClientSession session) throws Exception {
+        clientSession = ValidateUtils.checkNotNull(session, "No session");
+
+        Collection<Iterator<? extends PublicKeyIdentity>> identities = new LinkedList<>();
+        identities.add(new SessionKeyPairIterator(session, KeyIdentityProvider.Utils.iteratorOf(session)));
+
+        FactoryManager manager = ValidateUtils.checkNotNull(session.getFactoryManager(), "No session factory manager");
+        SshAgentFactory factory = manager.getAgentFactory();
+        if (factory != null) {
+            try {
+                agent = ValidateUtils.checkNotNull(factory.createClient(manager), "No agent created");
+                identities.add(new SshAgentPublicKeyIterator(session, agent));
+            } catch (Exception e) {
+                try {
+                    closeAgent();
+                } catch (Exception err) {
+                    e.addSuppressed(err);
+                }
+
+                throw e;
+            }
+        }
+
+        iterators = GenericUtils.iteratorOf(identities);
+        current = nextIterator(iterators);
+    }
+
+    @Override
+    public ClientSession getClientSession() {
+        return clientSession;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (!isOpen()) {
+            return false;
+        }
+
+        return current != null;
+    }
+
+    @Override
+    public PublicKeyIdentity next() {
+        if (!isOpen()) {
+            throw new NoSuchElementException("Iterator is closed");
+        }
+
+        PublicKeyIdentity pki = current.next();
+        if (!current.hasNext()) {
+            current = nextIterator(iterators);
+        }
+        return pki;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("No removal allowed");
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (open.getAndSet(false)) {
+            closeAgent();
+        }
+    }
+
+    protected void closeAgent() throws IOException {
+        if (agent != null) {
+            try {
+                agent.close();
+            } finally {
+                agent = null;
+            }
+        }
+    }
+
+    protected Iterator<? extends PublicKeyIdentity> nextIterator(
+            Iterator<? extends Iterator<? extends PublicKeyIdentity>> available) {
+        while ((available != null) && available.hasNext()) {
+            Iterator<? extends PublicKeyIdentity> iter = available.next();
+            if (iter.hasNext()) {
+                return iter;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + getClientSession() + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index 6ffb863..9506ee7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -20,7 +20,6 @@ package org.apache.sshd.client.session;
 
 import java.io.IOException;
 import java.nio.file.FileSystem;
-import java.security.KeyPair;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
@@ -38,7 +37,6 @@ import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
 import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.common.future.KeyExchangeFuture;
-import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.session.Session;
 
@@ -70,7 +68,7 @@ import org.apache.sshd.common.session.Session;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface ClientSession extends Session, KeyPairProviderHolder, ClientAuthenticationManager {
+public interface ClientSession extends Session, ClientAuthenticationManager {
     enum ClientSessionEvent {
         TIMEOUT,
         CLOSED,
@@ -79,32 +77,6 @@ public interface ClientSession extends Session, KeyPairProviderHolder, ClientAut
     }
 
     /**
-     * @param password Password to be added - may not be {@code null}/empty
-     */
-    void addPasswordIdentity(String password);
-
-    /**
-     * @param password The password to remove - ignored if {@code null}/empty
-     * @return The removed password - same one that was added via
-     * {@link #addPasswordIdentity(String)} - or {@code null} if no
-     * match found
-     */
-    String removePasswordIdentity(String password);
-
-    /**
-     * @param key The {@link KeyPair} to add - may not be {@code null}
-     */
-    void addPublicKeyIdentity(KeyPair key);
-
-    /**
-     * @param kp The {@link KeyPair} to remove - ignored if {@code null}
-     * @return The removed {@link KeyPair} - same one that was added via
-     * {@link #addPublicKeyIdentity(KeyPair)} - or {@code null} if no
-     * match found
-     */
-    KeyPair removePublicKeyIdentity(KeyPair kp);
-
-    /**
      * Starts the authentication process.
      * User identities will be tried until the server successfully authenticate the user.
      * User identities must be provided before calling this method using

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 5a0a0f5..8166f2b 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
@@ -127,10 +127,7 @@ public class ClientSessionImpl extends AbstractClientSession {
         ClientUserAuthService authService = getUserAuthService();
         synchronized (lock) {
             String serviceName = nextServiceName();
-            authFuture = ValidateUtils.checkNotNull(
-                    authService.auth(getRegisteredIdentities(), serviceName),
-                    "No auth future generated by service=%s",
-                    serviceName);
+            authFuture = ValidateUtils.checkNotNull(authService.auth(serviceName), "No auth future generated by service=%s", serviceName);
             return authFuture;
         }
     }
@@ -381,7 +378,7 @@ public class ClientSessionImpl extends AbstractClientSession {
 
     @Override
     protected void sendSessionEvent(SessionListener.Event event) throws IOException {
-        if (event == SessionListener.Event.KeyEstablished) {
+        if (SessionListener.Event.KeyEstablished.equals(event)) {
             sendInitialServiceRequest();
         }
         synchronized (lock) {
@@ -395,12 +392,13 @@ public class ClientSessionImpl extends AbstractClientSession {
             return;
         }
         initialServiceRequestSent = true;
+        String serviceName = currentServiceFactory.getName();
         if (log.isDebugEnabled()) {
-            log.debug("sendInitialServiceRequest({}) Send SSH_MSG_SERVICE_REQUEST for {}",
-                      this, currentServiceFactory.getName());
+            log.debug("sendInitialServiceRequest({}) Send SSH_MSG_SERVICE_REQUEST for {}", this, serviceName);
         }
-        Buffer request = createBuffer(SshConstants.SSH_MSG_SERVICE_REQUEST);
-        request.putString(currentServiceFactory.getName());
+
+        Buffer request = createBuffer(SshConstants.SSH_MSG_SERVICE_REQUEST, serviceName.length() + Byte.SIZE);
+        request.putString(serviceName);
         writePacket(request);
         // Assuming that MINA-SSHD only implements "explicit server authentication" it is permissible
         // for the client's service to start sending data before the service-accept has been received.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
index 34c6cf5..ae5bf7a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
@@ -36,6 +36,7 @@ import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.session.Session;
 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.closeable.AbstractCloseable;
 
@@ -54,7 +55,6 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
 
     private final ClientSessionImpl clientSession;
 
-    private List<Object> identities;
     private String service;
 
     private List<NamedFactory<UserAuth>> authFactories;
@@ -115,9 +115,8 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
         // ignored
     }
 
-    public AuthFuture auth(List<Object> identities, String service) throws IOException {
-        this.identities = new ArrayList<>(identities);
-        this.service = service;
+    public AuthFuture auth(String service) throws IOException {
+        this.service = ValidateUtils.checkNotNullAndNotEmpty(service, "No service");
 
         ClientSession session = getClientSession();
         String username = session.getUsername();
@@ -284,7 +283,7 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
                 log.debug("tryNext({}) attempting method={}", session, method);
             }
 
-            userAuth.init(session, service, identities);
+            userAuth.init(session, service);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
new file mode 100644
index 0000000..9a153aa
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
@@ -0,0 +1,149 @@
+/*
+ * 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.keyprovider;
+
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Supplier;
+import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyIdentityProvider {
+    KeyIdentityProvider EMPTY_KEYS_PROVIDER = new KeyIdentityProvider() {
+        @Override
+        public Iterable<KeyPair> loadKeys() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
+     * Load available keys.
+     *
+     * @return an {@link Iterable} instance of available keys - ignored if {@code null}
+     */
+    Iterable<KeyPair> loadKeys();
+
+    /**
+     * A helper class for key identity provider related operations
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     */
+    // CHECKSTYLE:OFF
+    final class Utils {
+    // CHECKSTYLE:ON
+        public static final Transformer<KeyIdentityProvider, Iterable<KeyPair>> LOADER =
+            new Transformer<KeyIdentityProvider, Iterable<KeyPair>>() {
+                @Override
+                public Iterable<KeyPair> transform(KeyIdentityProvider p) {
+                    return (p == null) ? null : p.loadKeys();
+                }
+            };
+
+        private Utils() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+
+        public static Iterator<KeyPair> iteratorOf(ClientSession session) {
+            ValidateUtils.checkNotNull(session, "No session");
+            return iteratorOf(session.getRegisteredIdentities(), session.getKeyPairProvider());
+        }
+
+        public static Iterator<KeyPair> iteratorOf(KeyIdentityProvider identities, KeyIdentityProvider keys) {
+            return iteratorOf(resolveKeyIdentityProvider(identities, keys));
+        }
+
+        /**
+         * Resolves a non-{@code null} iterator of the available keys
+         *
+         * @param provider The {@link KeyIdentityProvider} - ignored if {@code null}
+         * @return A non-{@code null} iterator - which may be empty if no provider or no keys
+         */
+        public static Iterator<KeyPair> iteratorOf(KeyIdentityProvider provider) {
+            return GenericUtils.iteratorOf((provider == null) ? null : provider.loadKeys());
+        }
+
+        public static KeyIdentityProvider resolveKeyIdentityProvider(KeyIdentityProvider identities, KeyIdentityProvider keys) {
+            if ((keys == null) || (identities == keys)) {
+                return identities;
+            } else if (identities == null) {
+                return keys;
+            } else {
+                return multiProvider(identities, keys);
+            }
+        }
+
+        public static KeyIdentityProvider multiProvider(KeyIdentityProvider ... providers) {
+            return multiProvider(GenericUtils.isEmpty(providers) ? Collections.<KeyIdentityProvider>emptyList() : Arrays.asList(providers));
+        }
+
+        public static KeyIdentityProvider multiProvider(Collection<? extends KeyIdentityProvider> providers) {
+            return wrap(iterableOf(providers));
+        }
+
+        public static Iterable<KeyPair> iterableOf(Collection<? extends KeyIdentityProvider> providers) {
+            if (GenericUtils.isEmpty(providers)) {
+                return Collections.emptyList();
+            }
+
+            Collection<Supplier<Iterable<KeyPair>>> suppliers = new ArrayList<Supplier<Iterable<KeyPair>>>(providers.size());
+            for (final KeyIdentityProvider p : providers) {
+                if (p == null) {
+                    continue;
+                }
+
+                suppliers.add(new Supplier<Iterable<KeyPair>>() {
+                    @Override
+                    public Iterable<KeyPair> get() {
+                        return p.loadKeys();
+                    }
+                });
+            }
+
+            if (GenericUtils.isEmpty(suppliers)) {
+                return Collections.emptyList();
+            }
+
+            return GenericUtils.multiIterableSuppliers(suppliers);
+        }
+
+        public static KeyIdentityProvider wrap(final Iterable<KeyPair> keys) {
+            return new KeyIdentityProvider() {
+                @Override
+                public Iterable<KeyPair> loadKeys() {
+                    return keys;
+                }
+            };
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
index a768a86..b4e7256 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
@@ -29,7 +29,7 @@ import org.apache.sshd.common.cipher.ECCurves;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface KeyPairProvider {
+public interface KeyPairProvider extends KeyIdentityProvider {
 
     /**
      * SSH identifier for RSA keys
@@ -83,13 +83,6 @@ public interface KeyPairProvider {
         };
 
     /**
-     * Load available keys.
-     *
-     * @return an {@link Iterable} instance of available keys, never {@code null}
-     */
-    Iterable<KeyPair> loadKeys();
-
-    /**
      * Load a key of the specified type which can be "ssh-rsa", "ssh-dss", or
      * "ecdsa-sha2-nistp{256,384,521}". If there is no key of this type, return
      * {@code null}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index ed5593e..ef06c54 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -31,6 +31,7 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
@@ -565,4 +566,76 @@ public final class GenericUtils {
             }
         };
     }
+
+    /**
+     * Resolves to an always non-{@code null} iterator
+     *
+     * @param iterable The {@link Iterable} instance
+     * @return A non-{@code null} iterator which may be empty if no iterable
+     * instance or no iterator returned from it
+     * @see #iteratorOf(Iterator)
+     */
+    public static <T> Iterator<T> iteratorOf(Iterable<T> iterable) {
+        return iteratorOf((iterable == null) ? null : iterable.iterator());
+    }
+
+    /**
+     * Resolves to an always non-{@code null} iterator
+     *
+     * @param iter The {@link Iterator} instance
+     * @return  A non-{@code null} iterator which may be empty if no iterator instance
+     * @see Collections#emptyIterator()
+     */
+    public static <T> Iterator<T> iteratorOf(Iterator<T> iter) {
+        return (iter == null) ? Collections.<T>emptyIterator() : iter;
+    }
+
+    public static <T> Iterable<T> multiIterableSuppliers(final Iterable<? extends Supplier<? extends Iterable<? extends T>>> providers) {
+        return new Iterable<T>() {
+            @Override
+            public Iterator<T> iterator() {
+                return new Iterator<T>() {
+                    private final Iterator<? extends Supplier<? extends Iterable<? extends T>>> iter = iteratorOf(providers);
+                    private Iterator<? extends T> current = nextIterator();
+
+                    @Override
+                    public boolean hasNext() {
+                        return current != null;
+                    }
+
+                    @Override
+                    public T next() {
+                        if (current == null) {
+                            throw new NoSuchElementException("No more elements");
+                        }
+
+                        T value = current.next();
+                        if (!current.hasNext()) {
+                            current = nextIterator();
+                        }
+
+                        return value;
+                    }
+
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException("remove");
+                    }
+
+                    private Iterator<? extends T> nextIterator() {
+                        while (iter.hasNext()) {
+                            Supplier<? extends Iterable<? extends T>> supplier = iter.next();
+                            Iterator<? extends T> values = iteratorOf((supplier == null) ? null : supplier.get());
+                            if (values.hasNext()) {
+                                return values;
+                            }
+                        }
+
+                        return null;
+                    }
+                };
+            }
+
+        };
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 9d17c4d..518b319 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
@@ -65,7 +65,8 @@ public class ServerUserAuthService extends AbstractCloseable implements Service,
         serverSession = (ServerSession) s;
         maxAuthRequests = PropertyResolverUtils.getIntProperty(s, ServerAuthenticationManager.MAX_AUTH_REQUESTS, ServerAuthenticationManager.DEFAULT_MAX_AUTH_REQUESTS);
 
-        List<NamedFactory<UserAuth>> factories = serverSession.getUserAuthFactories();
+        List<NamedFactory<UserAuth>> factories = ValidateUtils.checkNotNullAndNotEmpty(
+                serverSession.getUserAuthFactories(), "No user auth factories for %s", s);
         userAuthFactories = new ArrayList<>(factories);
         // Get authentication methods
         authMethods = new ArrayList<>();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
new file mode 100644
index 0000000..2b6246d
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import org.apache.sshd.client.auth.PasswordIdentityProvider;
+import org.apache.sshd.client.auth.UserInteraction;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.ChannelListener;
+import org.apache.sshd.common.forward.DefaultTcpipForwarderFactory;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.random.JceRandomFactory;
+import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.random.SingletonRandomFactory;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ClientAuthenticationManagerTest extends BaseTestSupport {
+    public ClientAuthenticationManagerTest() {
+        super();
+    }
+
+    @Test
+    public void testAddRemoveClientSessionIdentities() throws Exception {
+        try (ClientSession session = createMockClientSession()) {
+            testClientAuthenticationManager(session);
+        }
+    }
+
+    @Test
+    public void testAddRemoveSshClientIdentities() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            testClientAuthenticationManager(client);
+        }
+    }
+
+    @Test
+    public void testClientProvidersPropagation() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.setServiceFactories(SshClient.DEFAULT_SERVICE_FACTORIES);
+            client.setUserAuthFactories(SshClient.DEFAULT_USER_AUTH_FACTORIES);
+
+            try (ClientSession session = createMockClientSession(client)) {
+                for (Class<?> provider : new Class<?>[] {
+                    PasswordIdentityProvider.class,
+                    ServerKeyVerifier.class,
+                    UserInteraction.class,
+                    KeyPairProvider.class
+                }) {
+                    testClientProvidersPropagation(provider, client, session);
+                }
+            }
+        }
+    }
+
+    private void testClientProvidersPropagation(Class<?> type, ClientAuthenticationManager client, ClientAuthenticationManager session) throws Exception {
+        String baseName = type.getSimpleName();
+        outputDebugMessage("testClientProvidersPropagation(%s)", baseName);
+        assertTrue(baseName + ": not an interface", type.isInterface());
+
+        Method getter = ClientAuthenticationManager.class.getMethod("get" + baseName);
+        Method setter = ClientAuthenticationManager.class.getMethod("set" + baseName, type);
+        Object clientProvider = Mockito.mock(type);
+        setter.invoke(client, clientProvider);
+        assertSame(baseName + ": mismatched client-only provider", clientProvider, getter.invoke(session));
+
+        Object sessionProvider = Mockito.mock(type);
+        setter.invoke(session, sessionProvider);
+        assertSame(baseName + ": mismatched session override provider", sessionProvider, getter.invoke(session));
+
+        setter.invoke(session, new Object[] { null });
+        assertSame(baseName + ": mismatched nullified session provider", clientProvider, getter.invoke(session));
+    }
+
+    private <M extends ClientAuthenticationManager> M testClientAuthenticationManager(M manager) {
+        {
+            String expected = getCurrentTestName();
+            assertNull("Unexpected initial password identity", manager.removePasswordIdentity(expected));
+            manager.addPasswordIdentity(expected);
+
+            String actual = manager.removePasswordIdentity(expected);
+            assertSame("Mismatched removed password identity", expected, actual);
+            assertNull("Password identity not removed", manager.removePasswordIdentity(expected));
+        }
+
+        {
+            KeyPair expected = new KeyPair(Mockito.mock(PublicKey.class), Mockito.mock(PrivateKey.class));
+            assertNull("Unexpected initial pubket identity", manager.removePublicKeyIdentity(expected));
+            manager.addPublicKeyIdentity(expected);
+
+            KeyPair actual = manager.removePublicKeyIdentity(expected);
+            assertSame("Mismatched removed pubkey identity", expected, actual);
+            assertNull("Pubkey identity not removed", manager.removePublicKeyIdentity(expected));
+        }
+
+        return manager;
+    }
+
+    private ClientSession createMockClientSession() throws Exception {
+        ClientFactoryManager client = Mockito.mock(ClientFactoryManager.class);
+        Mockito.when(client.getTcpipForwarderFactory()).thenReturn(DefaultTcpipForwarderFactory.INSTANCE);
+        Mockito.when(client.getSessionListenerProxy()).thenReturn(new SessionListener() {
+            @Override
+            public void sessionEvent(Session session, Event event) {
+                // ignored
+            }
+
+            @Override
+            public void sessionCreated(Session session) {
+                // ignored
+            }
+
+            @Override
+            public void sessionClosed(Session session) {
+                // ignored
+            }
+        });
+        Mockito.when(client.getChannelListenerProxy()).thenReturn(new ChannelListener() {
+            @Override
+            public void channelOpenSuccess(Channel channel) {
+                // ignored
+            }
+
+            @Override
+            public void channelOpenFailure(Channel channel, Throwable reason) {
+                // ignored
+            }
+
+            @Override
+            public void channelInitialized(Channel channel) {
+                // ignored
+            }
+
+            @Override
+            public void channelClosed(Channel channel) {
+                // ignored
+            }
+        });
+        Factory<Random> randomFactory = new SingletonRandomFactory(JceRandomFactory.INSTANCE);
+        Mockito.when(client.getRandomFactory()).thenReturn(randomFactory);
+
+        Mockito.when(client.getServiceFactories()).thenReturn(SshClient.DEFAULT_SERVICE_FACTORIES);
+        Mockito.when(client.getUserAuthFactories()).thenReturn(SshClient.DEFAULT_USER_AUTH_FACTORIES);
+        return createMockClientSession(client);
+    }
+
+    private ClientSession createMockClientSession(ClientFactoryManager client) throws Exception {
+        return new ClientSessionImpl(client, Mockito.mock(IoSession.class)) {
+            @Override
+            protected void sendClientIdentification() {
+                // ignored
+            }
+
+            @Override
+            protected byte[] sendKexInit() throws IOException {
+                return GenericUtils.EMPTY_BYTE_ARRAY;
+            }
+
+            @Override
+            public void close() throws IOException {
+                // ignored
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/13818de0/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 26fb505..4d1b9a7 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
@@ -363,7 +363,7 @@ public class ClientTest extends BaseTestSupport {
                         channel.setIn(inPipe);
                         channel.setOut(out);
                         channel.setErr(err);
-                        channel.open().verify(3L, TimeUnit.SECONDS);
+                        channel.open().verify(6L, TimeUnit.SECONDS);
                         break;  // 1st success means all methods have been invoked
                     }
                 } catch (IOException e) {
@@ -1295,7 +1295,7 @@ public class ClientTest extends BaseTestSupport {
                 channel.open().verify(9L, TimeUnit.SECONDS);
 
                 AbstractSession cs = (AbstractSession) session;
-                Buffer buffer = cs.createBuffer(SshConstants.SSH_MSG_DISCONNECT);
+                Buffer buffer = cs.createBuffer(SshConstants.SSH_MSG_DISCONNECT, Integer.SIZE);
                 buffer.putInt(SshConstants.SSH2_DISCONNECT_BY_APPLICATION);
                 buffer.putString("Cancel");
                 buffer.putString("");   // TODO add language tag


[4/4] mina-sshd git commit: [SSHD-612] Cannot attempt new authentication on same session if current one fails

Posted by lg...@apache.org.
[SSHD-612] Cannot attempt new authentication on same session if current one fails


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

Branch: refs/heads/master
Commit: a26ef1549dde1df7e5cee53b46961c1a49ba74b0
Parents: fa724c2
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Dec 14 11:13:51 2015 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Dec 14 11:13:51 2015 +0200

----------------------------------------------------------------------
 .../client/session/ClientUserAuthService.java   | 53 +++++++++++++++-----
 1 file changed, 41 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a26ef154/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
index ae5bf7a..23c1b4b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
@@ -19,9 +19,11 @@
 package org.apache.sshd.client.session;
 
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.client.ClientAuthenticationManager;
 import org.apache.sshd.client.auth.UserAuth;
@@ -51,17 +53,15 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
      * The AuthFuture that is being used by the current auth request.  This encodes the state.
      * isSuccess -> authenticated, else if isDone -> server waiting for user auth, else authenticating.
      */
-    private final AuthFuture authFuture;
+    private final AtomicReference<AuthFuture> authFutureHolder = new AtomicReference<>();
 
     private final ClientSessionImpl clientSession;
+    private final List<String> clientMethods;
+    private final List<NamedFactory<UserAuth>> authFactories;
 
     private String service;
-
-    private List<NamedFactory<UserAuth>> authFactories;
-    private List<String> clientMethods;
     private List<String> serverMethods;
     private UserAuth userAuth;
-
     private int currentMethod;
 
     public ClientUserAuthService(Session s) {
@@ -69,8 +69,8 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
             throw new IllegalStateException("Client side service used on server side");
         }
         clientSession = (ClientSessionImpl) s;
-        authFuture = new DefaultAuthFuture(clientSession.getLock());
-        authFactories = clientSession.getUserAuthFactories();
+        authFactories = ValidateUtils.checkNotNullAndNotEmpty(
+                clientSession.getUserAuthFactories(), "No user auth factories for %s", s);
         clientMethods = new ArrayList<>();
 
         String prefs = PropertyResolverUtils.getString(s, ClientAuthenticationManager.PREFERRED_AUTHS);
@@ -116,14 +116,38 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
     }
 
     public AuthFuture auth(String service) throws IOException {
-        this.service = ValidateUtils.checkNotNullAndNotEmpty(service, "No service");
+        this.service = ValidateUtils.checkNotNullAndNotEmpty(service, "No service name");
 
         ClientSession session = getClientSession();
-        String username = session.getUsername();
+        // check if any previous future in use
+        AuthFuture authFuture = new DefaultAuthFuture(clientSession.getLock());
+        AuthFuture currentFuture = authFutureHolder.getAndSet(authFuture);
+        if (currentFuture != null) {
+            if (currentFuture.isDone()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("auth({})[{}] request new authentication", session, service);
+                }
+            } else {
+                currentFuture.setException(new InterruptedIOException("New authentication started before previous completed"));
+            }
+        }
+
+        // start from scratch
+        serverMethods = null;
+        currentMethod = 0;
+        if (userAuth != null) {
+            try {
+                userAuth.destroy();
+            } finally {
+                userAuth = null;
+            }
+        }
+
         if (log.isDebugEnabled()) {
             log.debug("auth({})[{}] send SSH_MSG_USERAUTH_REQUEST for 'none'", session, service);
         }
 
+        String username = session.getUsername();
         Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST, username.length() + service.length() + Integer.SIZE);
         buffer.putString(username);
         buffer.putString(service);
@@ -136,9 +160,10 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
     @Override
     public void process(int cmd, Buffer buffer) throws Exception {
         ClientSession session = getClientSession();
-        if (this.authFuture.isSuccess()) {
+        AuthFuture authFuture = ValidateUtils.checkNotNull(authFutureHolder.get(), "No current future");
+        if (authFuture.isSuccess()) {
             throw new IllegalStateException("UserAuth message delivered to authenticated client");
-        } else if (this.authFuture.isDone()) {
+        } else if (authFuture.isDone()) {
             if (log.isDebugEnabled()) {
                 log.debug("process({}) Ignoring random message - cmd={}",
                           session, SshConstants.getCommandMessageName(cmd));
@@ -184,6 +209,8 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
             }
             session.setAuthenticated();
             ((ClientSessionImpl) session).switchToNextService();
+
+            AuthFuture authFuture = ValidateUtils.checkNotNull(authFutureHolder.get(), "No current future");
             // Will wake up anyone sitting in waitFor
             authFuture.setAuthed(true);
             return;
@@ -270,6 +297,7 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
                 }
 
                 // also wake up anyone sitting in waitFor
+                AuthFuture authFuture = ValidateUtils.checkNotNull(authFutureHolder.get(), "No current future");
                 authFuture.setException(new SshException(SshConstants.SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, "No more authentication methods available"));
                 return;
             }
@@ -289,7 +317,8 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
 
     @Override
     protected void preClose() {
-        if (!authFuture.isDone()) {
+        AuthFuture authFuture = authFutureHolder.get();
+        if ((authFuture != null) && (!authFuture.isDone())) {
             authFuture.setException(new SshException("Session is closed"));
         }