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:06 UTC

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

[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