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/06/22 11:28:23 UTC

[2/2] mina-sshd git commit: [SSHD-498] Add support for loading client/server identity files

[SSHD-498] Add support for loading client/server identity files


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

Branch: refs/heads/master
Commit: 612a2f47a310abefd00ab08ffd54ef55f990afda
Parents: 66698ca
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jun 22 12:28:07 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jun 22 12:28:07 2015 +0300

----------------------------------------------------------------------
 .../java/org/apache/sshd/client/SshClient.java  | 189 +++++-----
 .../java/org/apache/sshd/client/SshKeyScan.java |  18 +-
 .../sshd/client/config/keys/ClientIdentity.java | 343 +++++++++++++++++++
 .../sshd/common/config/SshConfigFileReader.java |  11 +-
 .../common/config/keys/BuiltinIdentities.java   | 197 +++++++++++
 .../sshd/common/config/keys/Identity.java       |  40 +++
 .../sshd/common/config/keys/IdentityUtils.java  | 143 ++++++++
 .../sshd/common/config/keys/KeyUtils.java       |  98 +++++-
 .../org/apache/sshd/common/scp/ScpHelper.java   |   8 +-
 .../apache/sshd/common/util/SecurityUtils.java  |  23 ++
 .../org/apache/sshd/common/util/io/IoUtils.java |  25 +-
 .../DefaultAuthorizedKeysAuthenticator.java     |  38 +-
 .../sshd/server/config/keys/ServerIdentity.java | 200 +++++++++++
 .../sshd/server/session/ServerSession.java      |   2 +-
 .../client/config/keys/ClientIdentityTest.java  | 101 ++++++
 .../config/keys/BuiltinIdentitiesTest.java      | 106 ++++++
 .../server/config/keys/ServerIdentityTest.java  | 103 ++++++
 .../org/apache/sshd/client/config/keys/id_dsa   |  12 +
 .../org/apache/sshd/client/config/keys/id_ecdsa |   5 +
 .../apache/sshd/client/config/keys/id_rsa_key   |  27 ++
 .../sshd/server/config/keys/ssh_host_dsa_key    |  12 +
 .../sshd/server/config/keys/ssh_host_ecdsa_key  |   5 +
 .../sshd/server/config/keys/ssh_host_rsa_key    |  27 ++
 23 files changed, 1580 insertions(+), 153 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/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 8bea7ed..f50eede 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
@@ -19,7 +19,6 @@
 package org.apache.sshd.client;
 
 import java.io.BufferedReader;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
@@ -44,6 +43,7 @@ import org.apache.sshd.client.auth.UserAuthPassword;
 import org.apache.sshd.client.auth.UserAuthPublicKey;
 import org.apache.sshd.client.channel.ChannelShell;
 import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.config.keys.ClientIdentity;
 import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.future.DefaultConnectFuture;
 import org.apache.sshd.client.session.ClientConnectionService;
@@ -61,8 +61,6 @@ import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoConnectFuture;
 import org.apache.sshd.common.io.IoConnector;
-import org.apache.sshd.common.keyprovider.AbstractFileKeyPairProvider;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SecurityUtils;
@@ -435,112 +433,99 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
             root.setLevel(Level.FINEST);
         }
 
-        KeyPairProvider provider = null;
-        final List<File> files = new ArrayList<File>();
-        File f = new File(System.getProperty("user.home"), ".ssh/id_dsa");
-        if (f.exists() && f.isFile() && f.canRead()) {
-            files.add(f);
-        }
-        f = new File(System.getProperty("user.home"), ".ssh/id_rsa");
-        if (f.exists() && f.isFile() && f.canRead()) {
-            files.add(f);
-        }
-        f = new File(System.getProperty("user.home"), ".ssh/id_ecdsa");
-        if (f.exists() && f.isFile() && f.canRead()) {
-            files.add(f);
-        }
-        if (files.size() > 0) {
-            // SSHD-292: we need to use a different class to load the FileKeyPairProvider
-            //  in order to break the link between SshClient and BouncyCastle
-            try {
-                if (SecurityUtils.isBouncyCastleRegistered()) {
-                    AbstractFileKeyPairProvider filesProvider=SecurityUtils.createFileKeyPairProvider();
-                    filesProvider.setFiles(files);
-                    filesProvider.setPasswordFinder(new FilePasswordProvider() {
-                        private final BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
-                        @Override
-                        public String getPassword(String file) throws IOException {
-                            System.out.print("Enter password for private key file=" + file + ": ");
-                            return r.readLine();
-                        }
-                    });
-                    provider = filesProvider;
+        try(SshClient client = SshClient.setUpDefaultClient();
+            BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
+            if (SecurityUtils.isBouncyCastleRegistered()) {
+                try {
+                    ClientIdentity.setKeyPairProvider(client,
+                            false,  // not strict - even though we should...
+                            true,   // supportedOnly
+                            new FilePasswordProvider() {
+                                @Override
+                                public String getPassword(String file) throws IOException {
+                                    System.out.print("Enter password for private key file=" + file + ": ");
+                                    return stdin.readLine();
+                                }
+                            });
+                } catch (Throwable t) {
+                    System.out.println("Error loading user keys: " + t.getMessage());
                 }
-            } catch (Throwable t) {
-                System.out.println("Error loading user keys: " + t.getMessage());
             }
-        }
-
-        SshClient client = SshClient.setUpDefaultClient();
-        Map<String,Object> props = client.getProperties();
-        props.putAll(options);
-
-        client.start();
-        client.setKeyPairProvider(provider);
-        client.setUserInteraction(new UserInteraction() {
-            @Override
-            public void welcome(String banner) {
-                System.out.println(banner);
-            }
-
-            @Override
-            public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
-                String[] answers = new String[prompt.length];
-                try {
-                    for (int i = 0; i < prompt.length; i++) {
-                        BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
-                        System.out.print(prompt[i] + " ");
-                        answers[i] = r.readLine();
+    
+            Map<String,Object> props = client.getProperties();
+            props.putAll(options);
+    
+            client.start();
+            client.setUserInteraction(new UserInteraction() {
+                    @Override
+                    public void welcome(String banner) {
+                        System.out.println(banner);
+                    }
+        
+                    @Override
+                    public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
+                        String[] answers = new String[prompt.length];
+                        try {
+                            for (int i = 0; i < prompt.length; i++) {
+                                System.out.print(prompt[i] + " ");
+                                answers[i] = stdin.readLine();
+                            }
+                        } catch (IOException e) {
+                            // ignored
+                        }
+                        return answers;
                     }
-                } catch (IOException e) {
-                    // ignored
+                });
+
+            /*
+            String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
+            if (authSock == null && provider != null) {
+                Iterable<KeyPair> keys = provider.loadKeys();
+                AgentServer server = new AgentServer();
+                authSock = server.start();
+                SshAgent agent = new AgentClient(authSock);
+                for (KeyPair key : keys) {
+                    agent.addIdentity(key, "");
                 }
-                return answers;
-            }
-        });
-
-        /*
-        String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
-        if (authSock == null && provider != null) {
-            Iterable<KeyPair> keys = provider.loadKeys();
-            AgentServer server = new AgentServer();
-            authSock = server.start();
-            SshAgent agent = new AgentClient(authSock);
-            for (KeyPair key : keys) {
-                agent.addIdentity(key, "");
+                agent.close();
+                props.put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
             }
-            agent.close();
-            props.put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
-        }
-        */
-
-        ClientSession session = client.connect(login, host, port).await().getSession();
-        session.auth().verify();
-
-        if (socksPort >= 0) {
-            session.startDynamicPortForwarding(new SshdSocketAddress("localhost", socksPort));
-            Thread.sleep(Long.MAX_VALUE);
-        } else {
-            ClientChannel channel;
-            if (command == null) {
-                channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
-                ((ChannelShell) channel).setAgentForwarding(agentForward);
-                channel.setIn(new NoCloseInputStream(System.in));
-            } else {
-                StringWriter w = new StringWriter();
-                for (String cmd : command) {
-                    w.append(cmd).append(" ");
+            */
+    
+            try(ClientSession session = client.connect(login, host, port).await().getSession()) {
+                session.auth().verify();
+        
+                if (socksPort >= 0) {
+                    session.startDynamicPortForwarding(new SshdSocketAddress("localhost", socksPort));
+                    Thread.sleep(Long.MAX_VALUE);
+                } else {
+                    ClientChannel channel;
+                    if (command == null) {
+                        channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
+                        ((ChannelShell) channel).setAgentForwarding(agentForward);
+                        channel.setIn(new NoCloseInputStream(System.in));
+                    } else {
+                        StringWriter w = new StringWriter();
+                        for (String cmd : command) {
+                            w.append(cmd).append(" ");
+                        }
+                        w.close();
+                        channel = session.createChannel(ClientChannel.CHANNEL_EXEC, w.toString());
+                    }
+                    
+                    try {
+                        channel.setOut(new NoCloseOutputStream(System.out));
+                        channel.setErr(new NoCloseOutputStream(System.err));
+                        channel.open().await();
+                        channel.waitFor(ClientChannel.CLOSED, 0);
+                    } finally {
+                        channel.close();
+                    }
+                    session.close(false);
                 }
-                w.close();
-                channel = session.createChannel(ClientChannel.CHANNEL_EXEC, w.toString());
+            } finally {
+                client.stop();
             }
-            channel.setOut(new NoCloseOutputStream(System.out));
-            channel.setErr(new NoCloseOutputStream(System.err));
-            channel.open().await();
-            channel.waitFor(ClientChannel.CLOSED, 0);
-            session.close(false);
-            client.stop();
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
index 2d0d278..eaec00e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
@@ -60,6 +60,7 @@ import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.io.IoSession;
@@ -82,12 +83,11 @@ import org.apache.sshd.common.util.logging.LoggingUtils;
  */
 public class SshKeyScan extends AbstractSimplifiedLog
                         implements Channel, Callable<Void>, ServerKeyVerifier, SessionListener {
-    public static final String RSA_KEY_TYPE = "rsa", DSS_KEY_TYPE = "dsa", EC_KEY_TYPE = "ecdsa";
     /**
      * Default key types if not overridden from the command line
      */
-    public static final List<String> DEFAULT_KEY_TYPES =
-            Collections.unmodifiableList(Arrays.asList(RSA_KEY_TYPE, EC_KEY_TYPE));
+    public static final List<String> DEFAULT_KEY_TYPES = 
+            Collections.unmodifiableList(Arrays.asList(BuiltinIdentities.Constants.RSA, BuiltinIdentities.Constants.ECDSA));
     public static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
     public static final Level DEFAULT_LEVEL = Level.INFO;
 
@@ -413,11 +413,11 @@ public class SshKeyScan extends AbstractSimplifiedLog
             log(Level.FINE, "Resolve signature factories for " + keyType);
         }
 
-        if (RSA_KEY_TYPE.equalsIgnoreCase(keyType)) {
+        if (BuiltinIdentities.Constants.RSA.equalsIgnoreCase(keyType)) {
             return Collections.singletonList((NamedFactory<Signature>) BuiltinSignatures.rsa);
-        } else if (DSS_KEY_TYPE.equalsIgnoreCase(keyType)) {
+        } else if (BuiltinIdentities.Constants.DSA.equalsIgnoreCase(keyType)) {
             return Collections.singletonList((NamedFactory<Signature>) BuiltinSignatures.dsa);
-        } else if (EC_KEY_TYPE.equalsIgnoreCase(keyType)) {
+        } else if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyType)) {
             List<NamedFactory<Signature>> factories = new ArrayList<NamedFactory<Signature>>(ECCurves.NAMES.size());
             for (String n : ECCurves.NAMES) {
                 if (isEnabled(Level.FINER)) {
@@ -464,11 +464,11 @@ public class SshKeyScan extends AbstractSimplifiedLog
             log(Level.FINE, "Generate key pairs for " + keyType);
         }
 
-        if (RSA_KEY_TYPE.equalsIgnoreCase(keyType)) {
+        if (BuiltinIdentities.Constants.RSA.equalsIgnoreCase(keyType)) {
             return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_RSA, 1024));
-        } else if (DSS_KEY_TYPE.equalsIgnoreCase(keyType)) {
+        } else if (BuiltinIdentities.Constants.DSA.equalsIgnoreCase(keyType)) {
             return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_DSS, 512));
-        } else if (EC_KEY_TYPE.equalsIgnoreCase(keyType)) {
+        } else if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyType)) {
             if (!SecurityUtils.hasEcc()) {
                 throw new InvalidKeySpecException("ECC not supported");
             }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
new file mode 100644
index 0000000..a92ca9a
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/keys/ClientIdentity.java
@@ -0,0 +1,343 @@
+/*
+ * 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.config.keys;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.IdentityUtils;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * Provides keys loading capability from the user's keys folder - e.g., {@code id_rsa}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see SecurityUtils#isBouncyCastleRegistered()
+ */
+public final class ClientIdentity {
+    public static final String ID_FILE_PREFIX = "id_", ID_FILE_SUFFIX = "";
+
+    private ClientIdentity() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static final Transformer<String,String> ID_GENERATOR =
+            new Transformer<String,String>() {
+                @Override
+                public String transform(String input) {
+                    return getIdentityFileName(input);
+                }
+        };
+
+    /**
+     * @param name The file name - ignored if {@code null}/empty
+     * @return The identity type - {@code null} if cannot determine it - e.g.,
+     * does not start with the {@link #ID_FILE_PREFIX}
+     */
+    public static String getIdentityType(String name) {
+        if (GenericUtils.isEmpty(name)
+        || (name.length() <= ID_FILE_PREFIX.length())
+        || (!name.startsWith(ID_FILE_PREFIX))) {
+            return null;
+        } else {
+            return name.substring(ID_FILE_PREFIX.length());
+        }
+    }
+
+    public static String getIdentityFileName(NamedResource r) {
+        return getIdentityFileName((r == null) ? null : r.getName());
+    }
+
+    /**
+     * @param type The identity type - e.g., {@code rsa} - ignored
+     * if {@code null}/empty
+     * @return The matching file name for the identity - {@code null}
+     * if no name
+     * @see #ID_FILE_PREFIX
+     * @see #ID_FILE_SUFFIX
+     * @see IdentityUtils#getIdentityFileName(String, String, String)
+     */
+    public static String getIdentityFileName(String type) {
+        return IdentityUtils.getIdentityFileName(ID_FILE_PREFIX, type, ID_FILE_SUFFIX);
+    }
+
+    /**
+     * @param client The {@link SshClient} to updated
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return The updated <tt>client</tt> instance - provided a non-{@code null}
+     * {@link KeyPairProvider} was generated
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #getDefaultUserIdentitiesFolder()
+     * @see #setKeyPairProvider(SshClient, Path, boolean, boolean, FilePasswordProvider, LinkOption...)
+     */
+    public static <C extends SshClient> C setKeyPairProvider(
+            C client, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption ... options)
+                    throws IOException, GeneralSecurityException {
+        return setKeyPairProvider(client, getDefaultUserIdentitiesFolder(), strict, supportedOnly, provider, options);
+    }
+
+    /**
+     * @param client The {@link SshClient} to updated
+     * @param dir The folder to scan for the built-in identities
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return The updated <tt>client</tt> instance - provided a non-{@code null}
+     * {@link KeyPairProvider} was generated
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #loadDefaultKeyPairProvider(Path, boolean, boolean, FilePasswordProvider, LinkOption...)
+     */
+    public static <C extends SshClient> C setKeyPairProvider(
+            C client, Path dir, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption ... options)
+                    throws IOException, GeneralSecurityException {
+        KeyPairProvider kpp = loadDefaultKeyPairProvider(dir, strict, supportedOnly, provider, options);
+        if (kpp != null) {
+            client.setKeyPairProvider(kpp);
+        }
+        
+        return client;
+    }
+
+    /**
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return A {@link KeyPair} for the identities - {@code null} if no identities
+     * available (e.g., after filtering unsupported ones or strict permissions)
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...)
+     * @see #getDefaultUserIdentitiesFolder()
+     */
+    public static KeyPairProvider loadDefaultKeyPairProvider(
+            boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption ... options)
+                    throws IOException, GeneralSecurityException {
+        return loadDefaultKeyPairProvider(getDefaultUserIdentitiesFolder(), strict, supportedOnly, provider, options);
+    }
+
+    /**
+     * @param dir The folder to scan for the built-in identities
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return A {@link KeyPair} for the identities - {@code null} if no identities
+     * available (e.g., after filtering unsupported ones or strict permissions)
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...)
+     * @see IdentityUtils#createKeyPairProvider(Map, boolean)
+     */
+    public static KeyPairProvider loadDefaultKeyPairProvider(
+            Path dir, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption ... options)
+                    throws IOException, GeneralSecurityException {
+        Map<String,KeyPair> ids = loadDefaultIdentities(dir, strict, provider, options);
+        return IdentityUtils.createKeyPairProvider(ids, supportedOnly);
+    }
+
+    /**
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return A {@link Map} of the found files where key=identity type (case
+     * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #getDefaultUserIdentitiesFolder()
+     * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...)
+     */
+    public static Map<String,KeyPair> loadDefaultIdentities(boolean strict, FilePasswordProvider provider, LinkOption ... options)
+            throws IOException, GeneralSecurityException {
+        return loadDefaultIdentities(getDefaultUserIdentitiesFolder(), strict, provider, options);
+    }
+
+    /**
+     * @param dir The folder to scan for the built-in identities
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return A {@link Map} of the found files where key=identity type (case
+     * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #loadIdentities(Path, boolean, Collection, Transformer, FilePasswordProvider, LinkOption...)
+     * @see BuiltinIdentities
+     */
+    public static Map<String,KeyPair> loadDefaultIdentities(Path dir, boolean strict, FilePasswordProvider provider, LinkOption ... options)
+            throws IOException, GeneralSecurityException {
+        return loadIdentities(dir, strict, BuiltinIdentities.NAMES, ID_GENERATOR, provider, options);
+    }
+
+    /**
+     * @return The {@link Path} of the default folder where client identity
+     * files are kept
+     * @throws IOException If not value for {@code user.home} system property
+     */
+    public static Path getDefaultUserIdentitiesFolder() throws IOException {
+        String userHome = System.getProperty("user.home");
+        if (GenericUtils.isEmpty(userHome)) {
+            throw new FileNotFoundException("No user home value");
+        }
+        
+        Path homeDir = new File(userHome).toPath();
+        return homeDir.resolve(PublicKeyEntry.STD_KEYFILE_FOLDER_NAME);
+    }
+
+    /**
+     * Scans a folder and loads all available identity files
+     * @param dir The {@link Path} of the folder to scan - ignored if not exists
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param types The identity types - ignored if {@code null}/empty
+     * @param idGenerator A {@link Transformer} to derive the file name
+     * holding the specified type
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return A {@link Map} of the found files where key=identity type (case
+     * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #scanIdentitiesFolder(Path, Collection, Transformer, LinkOption...)
+     * @see IdentityUtils#loadIdentities(Map, FilePasswordProvider, OpenOption...)
+     */
+    public static Map<String,KeyPair> loadIdentities(
+            Path dir, boolean strict, Collection<String> types, Transformer<String,String> idGenerator, FilePasswordProvider provider, LinkOption ... options)
+                    throws IOException, GeneralSecurityException {
+        Map<String,Path> paths = scanIdentitiesFolder(dir, strict, types, idGenerator, options);
+        return IdentityUtils.loadIdentities(paths, provider, IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+    
+    /**
+     * Scans a folder for possible identity files
+     * @param dir The {@link Path} of the folder to scan - ignored if not exists
+     * @param strict If {@code true} then files that do not have the required
+     * access rights are excluded from consideration
+     * @param types The identity types - ignored if {@code null}/empty
+     * @param idGenerator A {@link Transformer} to derive the file name
+     * holding the specified type
+     * @param options The {@link LinkOption}s to apply when checking
+     * for existence
+     * @return A {@link Map} of the found files where key=identity type (case
+     * <U>insensitive</U>), value=the {@link Path} of the file holding the key
+     * @throws IOException If failed to access the file system
+     * @see KeyUtils#validateStrictKeyFilePermissions(Path, LinkOption...)
+     */
+    public static Map<String,Path> scanIdentitiesFolder(
+            Path dir, boolean strict, Collection<String> types, Transformer<String,String> idGenerator, LinkOption ... options)
+                    throws IOException {
+        if (GenericUtils.isEmpty(types)) {
+            return Collections.emptyMap();
+        }
+    
+        if (!Files.exists(dir, options)) {
+            return Collections.emptyMap();
+        }
+        
+        ValidateUtils.checkTrue(Files.isDirectory(dir, options), "Not a directory: %s", dir);
+        
+        Map<String,Path> paths = new TreeMap<String, Path>(String.CASE_INSENSITIVE_ORDER);
+        for (String t : types) {
+            String fileName = idGenerator.transform(t);
+            Path p = dir.resolve(fileName);
+            if (!Files.exists(p, options)) {
+                continue;
+            }
+            
+            if (strict) {
+                PosixFilePermission perm = KeyUtils.validateStrictKeyFilePermissions(p, options);
+                if (perm != null) {
+                    continue;
+                }
+            }
+
+            Path prev = paths.put(t, p);
+            ValidateUtils.checkTrue(prev == null, "Multiple mappings for type=%s", t);
+        }
+        
+        return paths;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java b/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
index 92587a3..95641fa 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/SshConfigFileReader.java
@@ -29,6 +29,9 @@ import java.io.Reader;
 import java.io.StreamCorruptedException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -58,6 +61,7 @@ import org.apache.sshd.common.signature.SignatureFactory;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Transformer;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.NoCloseReader;
 import org.apache.sshd.server.ServerBuilder;
@@ -87,7 +91,6 @@ public class SshConfigFileReader {
         public static final boolean DEFAULT_X11_FORWARDING_VALUE=parseBooleanValue(DEFAULT_X11_FORWARDING);
     public static final String  MAX_SESSIONS_CONFIG_PROP="MaxSessions";
         public static final int DEFAULT_MAX_SESSIONS=10;
-    public static final String  HOST_KEY_CONFIG_PROP="HostKey";
     public static final String  PASSWORD_AUTH_CONFIG_PROP="PasswordAuthentication";
         public static final String  DEFAULT_PASSWORD_AUTH="no";
         public static final boolean DEFAULT_PASSWORD_AUTH_VALUE=parseBooleanValue(DEFAULT_PASSWORD_AUTH);
@@ -143,7 +146,11 @@ public class SshConfigFileReader {
     public static final String  SUBSYSTEM_CONFIG_PROP="Subsystem";
 
     public static final Properties readConfigFile(File file) throws IOException {
-        try(InputStream input=new FileInputStream(file)) {
+        return readConfigFile(file.toPath(), IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+
+    public static final Properties readConfigFile(Path path, OpenOption ... options) throws IOException {
+        try(InputStream input = Files.newInputStream(path, options)) {
             return readConfigFile(input, true);
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
new file mode 100644
index 0000000..1755f31
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
@@ -0,0 +1,197 @@
+/*
+ * 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.config.keys;
+
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinIdentities implements Identity {
+    RSA(Constants.RSA, RSAPublicKey.class, RSAPrivateKey.class),
+    DSA(Constants.DSA, DSAPublicKey.class, DSAPrivateKey.class),
+    ECDSA(Constants.ECDSA, "EC", ECPublicKey.class, ECPrivateKey.class) {
+        @Override
+        public boolean isSupported() {
+            return SecurityUtils.hasEcc();
+        }
+    };
+
+    private final String name, algorithm;
+    private final Class<? extends PublicKey> pubType;
+    private final Class<? extends PrivateKey> prvType;
+    
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    @Override
+    public final Class<? extends PublicKey> getPublicKeyType() {
+        return pubType;
+    }
+
+    @Override
+    public final Class<? extends PrivateKey> getPrivateKeyType() {
+        return prvType;
+    }
+
+    BuiltinIdentities(String type, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) {
+        this(type, type, pubType, prvType);
+    }
+
+    BuiltinIdentities(String name, String algorithm, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) {
+        this.name = name.toLowerCase();
+        this.algorithm = algorithm.toUpperCase();
+        this.pubType = pubType;
+        this.prvType = prvType;
+    }
+
+    public static final Set<BuiltinIdentities> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(BuiltinIdentities.class));
+
+    public static final Set<String> NAMES =
+            Collections.unmodifiableSet(new TreeSet<String>(String.CASE_INSENSITIVE_ORDER) {
+                private static final long serialVersionUID = 1L;    // we're not serializing it
+                
+                {
+                    addAll(NamedResource.Utils.getNameList(VALUES));
+                }
+            });
+
+    /**
+     * @param name The identity name - ignored if {@code null}/empty
+     * @return The matching {@link BuiltinIdentities} whose {@link #getName()}
+     * value matches case <U>insensitive</U> or {@code null} if no match found
+     */
+    public static final BuiltinIdentities fromName(String name) {
+        return NamedResource.Utils.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
+    }
+
+    /**
+     * @param algorithm The algorithm  - ignored if {@code null}/empty
+     * @return The matching {@link BuiltinIdentities} whose {@link #getAlgorithm()}
+     * value matches case <U>insensitive</U> or {@code null} if no match found
+     */
+    public static final BuiltinIdentities fromAlgorithm(String algorithm) {
+        if (GenericUtils.isEmpty(algorithm)) {
+            return null;
+        }
+        
+        for (BuiltinIdentities id : VALUES) {
+            if (algorithm.equalsIgnoreCase(id.getAlgorithm())) {
+                return id;
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * @param kp The {@link KeyPair} - ignored if {@code null}
+     * @return The matching {@link BuiltinIdentities} provided <U>both</U>
+     * public and public keys are of the same type - {@code null} if no
+     * match could be found
+     * @see #fromKey(Key)
+     */
+    public static final BuiltinIdentities fromKeyPair(KeyPair kp) {
+        if (kp == null) {
+            return null;
+        }
+
+        BuiltinIdentities   i1 = fromKey(kp.getPublic());
+        BuiltinIdentities   i2 = fromKey(kp.getPrivate());
+        if (Objects.equals(i1, i2)) {
+            return i1;
+        } else {
+            return null;    // some kind of mixed keys...
+        }
+    }
+
+    /**
+     * @param key The {@link Key} instance - ignored if {@code null}
+     * @return The matching {@link BuiltinIdentities} whose either public or
+     * private key type matches the requested one or {@code null} if no match found
+     * @see #fromKeyType(Class)
+     */
+    public static final BuiltinIdentities fromKey(Key key) {
+        return fromKeyType((key == null) ? null : key.getClass());
+    }
+
+    /**
+     * @param clazz The key type - ignored if {@code null} or not
+     * a {@link Key} class
+     * @return The matching {@link BuiltinIdentities} whose either public or
+     * private key type matches the requested one or {@code null} if no match found
+     * @see #getPublicKeyType()
+     * @see #getPrivateKeyType() 
+     */
+    public static final BuiltinIdentities fromKeyType(Class<?> clazz) {
+        if ((clazz == null) || (!Key.class.isAssignableFrom(clazz))) {
+            return null;
+        }
+
+        for (BuiltinIdentities id : VALUES) {
+            Class<?> pubType = id.getPublicKeyType(), prvType = id.getPrivateKeyType();
+            if (pubType.isAssignableFrom(clazz) || prvType.isAssignableFrom(clazz)) {
+                return id;
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Contains the names of the identities
+     */
+    public static final class Constants {
+        public static final String RSA = "RSA";
+        public static final String DSA = "DSA";
+        public static final String ECDSA = "ECDSA";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/config/keys/Identity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/Identity.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/Identity.java
new file mode 100644
index 0000000..75b68be
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/Identity.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.OptionalFeature;
+
+/**
+ * Represents an SSH key type
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface Identity extends NamedResource, OptionalFeature {
+    /**
+     * @return The key algorithm - e.g., RSA, DSA, EC
+     */
+    String getAlgorithm();
+    
+    Class<? extends PublicKey> getPublicKeyType();
+    Class<? extends PrivateKey> getPrivateKeyType();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
new file mode 100644
index 0000000..15e8c2f
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.config.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.MappedKeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class IdentityUtils {
+    private IdentityUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    /**
+     * @param prefix The file name prefix - ignored if {@code null}/empty
+     * @param type The identity type - ignored if {@code null}/empty
+     * @param suffix The file name suffix - ignored if {@code null}/empty
+     * @return The identity file name or {@code null} if no name
+     */
+    public static String getIdentityFileName(String prefix, String type, String suffix) {
+        if (GenericUtils.isEmpty(type)) {
+            return null;
+        } else {
+            return new StringBuilder(GenericUtils.length(prefix) + type.length() + GenericUtils.length(suffix))
+                            .append(GenericUtils.trimToEmpty(prefix))
+                            .append(type.toLowerCase())
+                            .append(GenericUtils.trimToEmpty(suffix))
+                            .toString();
+        }
+    }
+
+    /**
+     * @param ids A {@link Map} of the loaded identities where key=the identity type,
+     * value=the matching {@link KeyPair} - ignored if {@code null}/empty
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @return A {@link KeyPair} for the identities - {@code null} if no identities
+     * available (e.g., after filtering unsupported ones)
+     * @see BuiltinIdentities
+     */
+    public static KeyPairProvider createKeyPairProvider(Map<String,KeyPair> ids, boolean supportedOnly) {
+        if (GenericUtils.isEmpty(ids)) {
+            return null;
+        }
+        
+        Map<String,KeyPair> pairsMap = new TreeMap<String, KeyPair>(String.CASE_INSENSITIVE_ORDER);
+        for (Map.Entry<String, KeyPair> ide : ids.entrySet()) {
+            String type = ide.getKey();
+            KeyPair kp = ide.getValue();
+            BuiltinIdentities id = BuiltinIdentities.fromName(type);
+            if (id == null) {
+                id = BuiltinIdentities.fromKeyPair(kp);
+            }
+            
+            if (supportedOnly && ((id == null) || (!id.isSupported()))) {
+                continue;
+            }
+            
+            String keyType = KeyUtils.getKeyType(kp);
+            if (GenericUtils.isEmpty(keyType)) {
+                continue;
+            }
+            
+            KeyPair prev = pairsMap.put(keyType, kp);
+            if (prev != null) {
+                continue;   // less of an offense if 2 pairs mapped to same key type
+            }
+        }
+        
+        if (GenericUtils.isEmpty(pairsMap)) {
+            return null;
+        } else {
+            return new MappedKeyPairProvider(pairsMap);
+        }
+    }
+
+    /**
+     * @param paths A {@link Map} of the identities where key=identity type (case
+     * <U>insensitive</U>), value=the {@link Path} of file with the identity key 
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument
+     * to {@link FilePasswordProvider#getPassword(String)} is the path of the
+     * file whose key is to be loaded
+     * @param options The {@link OpenOption}s to use when reading the key data
+     * @return A {@link Map} of the identities where key=identity type (case
+     * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see SecurityUtils#loadKeyPairIdentity(String, InputStream, FilePasswordProvider)
+     */
+    public static Map<String,KeyPair> loadIdentities(Map<String,? extends Path> paths, FilePasswordProvider provider, OpenOption ... options)
+        throws IOException, GeneralSecurityException {
+        if (GenericUtils.isEmpty(paths)) {
+            return Collections.emptyMap();
+        }
+        
+        Map<String,KeyPair> ids = new TreeMap<String, KeyPair>(String.CASE_INSENSITIVE_ORDER);
+        for (Map.Entry<String,? extends Path> pe : paths.entrySet()) {
+            String type = pe.getKey();
+            Path path = pe.getValue();
+            try(InputStream inputStream = Files.newInputStream(path, options)) {
+                KeyPair kp = SecurityUtils.loadKeyPairIdentity(path.toString(), inputStream, provider);
+                KeyPair prev = ids.put(type, kp);
+                ValidateUtils.checkTrue(prev == null, "Multiple keys for type=%s", type);
+            }
+        }
+        
+        return ids;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 2c37278..b285476 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -18,8 +18,13 @@
  */
 package org.apache.sshd.common.config.keys;
 
+import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
 import java.security.GeneralSecurityException;
 import java.security.Key;
 import java.security.KeyPair;
@@ -39,9 +44,12 @@ import java.security.spec.ECParameterSpec;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -52,9 +60,11 @@ import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.digest.DigestUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.IoUtils;
 
 /**
  * Utility class for keys
@@ -73,11 +83,76 @@ public final class KeyUtils {
         registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
     }
 
+    /**
+     * The {@link Set} of {@link PosixFilePermission} <U>not</U> allowed if strict
+     * permissions are enforced on key files
+     */
+    public static final Set<PosixFilePermission> STRICTLY_PROHIBITED_FILE_PERMISSION =
+            Collections.unmodifiableSet(
+                    EnumSet.of(PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE,
+                               PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE));
+
     private KeyUtils() {
         throw new UnsupportedOperationException("No instance");
     }
 
     /**
+     * <P>Checks if a path has strict permissions</P></BR>
+     * <UL>
+     *      <LI>
+     *      The path may not have {@link PosixFilePermission#OTHERS_EXECUTE}
+     *      permission
+     *      </LI>
+     *      
+     *      <LI>
+     *      (For {@code Unix}) The path may not have group or others permissions
+     *      </LI>
+     *      
+     *      <LI>
+     *      (For {@code Unix}) If the path is a file, then its folder may not have
+     *      group or others permissions 
+     *      </LI>
+     * </UL>
+     * @param path The {@link Path} to be checked - ignored if {@code null}
+     * or does not exist
+     * @param options The {@link LinkOption}s to use to query the file's permissions
+     * @return The violated {@link PosixFilePermission} - {@code null} if
+     * no violations detected
+     * @throws IOException If failed to retrieve the permissions
+     * @see #STRICTLY_PROHIBITED_FILE_PERMISSION
+     */
+    public static PosixFilePermission validateStrictKeyFilePermissions(Path path, LinkOption ... options) throws IOException {
+        if ((path == null) || (!Files.exists(path, options))) {
+            return null;
+        }
+
+        Collection<PosixFilePermission> perms = IoUtils.getPermissions(path, options);
+        if (GenericUtils.isEmpty(perms)) {
+            return null;
+        }
+
+        if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
+            return PosixFilePermission.OTHERS_EXECUTE;
+        }
+
+        if (OsUtils.isUNIX()) { 
+            PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, STRICTLY_PROHIBITED_FILE_PERMISSION);
+            if (p != null) {
+                return p;
+            }
+
+            if (Files.isRegularFile(path, options)) {
+                Path parent=path.getParent();
+                if ((p = IoUtils.validateExcludedPermissions(IoUtils.getPermissions(parent, options), STRICTLY_PROHIBITED_FILE_PERMISSION)) != null) {
+                    return p;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * @param keyType The key type - {@code OpenSSH} name - e.g., {@code ssh-rsa, ssh-dss}
      * @param keySize The key size (in bits)
      * @return A {@link KeyPair} of the specified type and size
@@ -153,7 +228,28 @@ public final class KeyUtils {
             return byKeyTypeDecodersMap.get(keyType);
         }
     }
-    
+
+    /**
+     * @param kp The {@link KeyPair} to examine - ignored if {@code null}
+     * @return The matching {@link PublicKeyEntryDecoder} provided <U>both</U>
+     * the public and private keys have the same decoder - {@code null} if no
+     * match found
+     * @see #getPublicKeyEntryDecoder(Key)
+     */
+    public static PublicKeyEntryDecoder<?,?> getPublicKeyEntryDecoder(KeyPair kp) {
+        if (kp == null) {
+            return null;
+        }
+        
+        PublicKeyEntryDecoder<?,?> d1 = getPublicKeyEntryDecoder(kp.getPublic());
+        PublicKeyEntryDecoder<?,?> d2 = getPublicKeyEntryDecoder(kp.getPrivate());
+        if (d1 == d2) {
+            return d1;
+        } else {
+            return null;    // some kind of mixed keys...
+        }
+    }
+
     /**
      * @param key The {@link Key} (public or private) - ignored if {@code null}
      * @return The registered {@link PublicKeyEntryDecoder} for this key or {code null} if no match found

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 45afa6e..1614985 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -577,7 +577,8 @@ public class ScpHelper extends AbstractLoggingBean {
             readAck(false);
         }
 
-        Set<PosixFilePermission> perms = IoUtils.getPermissions(path);
+        LinkOption[] options = IoUtils.getLinkOptions(false);
+        Set<PosixFilePermission> perms = IoUtils.getPermissions(path, options);
         StringBuilder buf = new StringBuilder();
         buf.append("D");
         buf.append(preserve ? getOctalPerms(perms) : "0755");
@@ -594,7 +595,6 @@ public class ScpHelper extends AbstractLoggingBean {
             listener.startFolderEvent(FileOperation.SEND, path, perms);
 
             try {
-                LinkOption[] options = IoUtils.getLinkOptions(false);
                 for (Path child : children) {
                     if (Files.isRegularFile(child, options)) {
                         sendFile(child, preserve, bufferSize);
@@ -615,8 +615,8 @@ public class ScpHelper extends AbstractLoggingBean {
         readAck(false);
     }
 
-    public static String getOctalPerms(Path path) throws IOException {
-        return getOctalPerms(IoUtils.getPermissions(path));
+    public static String getOctalPerms(Path path, LinkOption ... options) throws IOException {
+        return getOctalPerms(IoUtils.getPermissions(path, options));
     }
 
     public static String getOctalPerms(Collection<PosixFilePermission> perms) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
index 134c9d6..5c6d6fe 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
@@ -30,6 +30,7 @@ import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.MessageDigest;
+import java.security.NoSuchProviderException;
 import java.security.SecureRandom;
 import java.security.Signature;
 import java.util.concurrent.Callable;
@@ -185,6 +186,28 @@ public class SecurityUtils {
         }
     }
 
+    /**
+     * @param resourceKey An identifier of the key being loaded - used as
+     * argument to the {@link FilePasswordProvider#getPassword(String)}
+     * invocation
+     * @param inputStream The {@link InputStream} for the <U>private</U> key
+     * @param provider A {@link FilePasswordProvider} - may be {@code null}
+     * if the loaded key is <U>guaranteed</U> not to be encrypted
+     * @return The loaded {@link KeyPair}
+     * @throws IOException If failed to read/parse the input stream
+     * @throws GeneralSecurityException If failed to generate the keys - specifically,
+     * {@link NoSuchProviderException} is thrown also if {@link #isBouncyCastleRegistered()}
+     * is {@code false}
+     */
+    public static KeyPair loadKeyPairIdentity(String resourceKey, InputStream inputStream, FilePasswordProvider provider)
+            throws IOException, GeneralSecurityException {
+        if (!isBouncyCastleRegistered()) {
+            throw new NoSuchProviderException("BouncyCastle not registered");
+        }
+        
+        return BouncyCastleInputStreamLoader.loadKeyPair(resourceKey, inputStream, provider);
+    }
+
     /* -------------------------------------------------------------------- */
 
     // use a separate class in order to avoid direct dependency

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
index 73f7b59..7afc506 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -37,6 +37,7 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 
 /**
@@ -113,11 +114,11 @@ public class IoUtils {
      * @throws IOException If failed to access the file system in order to
      * retrieve the permissions
      */
-    public static Set<PosixFilePermission> getPermissions(Path path) throws IOException {
+    public static Set<PosixFilePermission> getPermissions(Path path, LinkOption ... options) throws IOException {
         FileSystem          fs = path.getFileSystem();
         Collection<String>  views = fs.supportedFileAttributeViews();
         if (views.contains("posix")) {
-            return Files.getPosixFilePermissions(path, getLinkOptions(false));
+            return Files.getPosixFilePermissions(path, options);
         } else {
             return getPermissionsFromFile(path.toFile());
         }
@@ -291,4 +292,24 @@ public class IoUtils {
 
         return length;
     }
+
+    /**
+     * @param perms The current {@link PosixFilePermission}s - ignored if {@code null}/empty
+     * @param excluded The permissions <U>not</U> allowed to exist - ignored if {@code null)/empty
+     * @return The violating {@link PosixFilePermission} - {@code null}
+     * if no violating permission found
+     */
+    public static PosixFilePermission validateExcludedPermissions(Collection<PosixFilePermission> perms, Collection<PosixFilePermission> excluded) {
+        if (GenericUtils.isEmpty(perms) || GenericUtils.isEmpty(excluded)) {
+            return null;
+        }
+
+        for (PosixFilePermission p : excluded) {
+            if (perms.contains(p)) {
+                return p;
+            }
+        }
+        
+        return null;
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
index ddecc16..ad2a2c8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
@@ -26,12 +26,9 @@ import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Set;
 
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.session.ServerSession;
@@ -44,14 +41,6 @@ import org.apache.sshd.server.session.ServerSession;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthenticator {
-    /**
-     * The {@link Set} of {@link PosixFilePermission} <U>not</U> allowed if strict
-     * permissions are enforced
-     */
-    public static final Set<PosixFilePermission> STRICTLY_PROHIBITED_FILE_PERMISSION =
-            Collections.unmodifiableSet(
-                    EnumSet.of(PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE,
-                               PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE));
 
     /**
      * The default instance that enforces the same permissions regime as {@code OpenSSH}
@@ -120,19 +109,8 @@ public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthentica
             if (log.isDebugEnabled()) {
                 log.info("reloadAuthorizedKeys(" + username + ")[" + session + "] check permissions of " + path);
             }
-            
-            Collection<PosixFilePermission> perms = IoUtils.getPermissions(path);
-            // this is true for Windows as well
-            if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
-                throw new FileSystemException(path.toString(), path.toString(), "File is not allowed to have e(x)ecute permission");
-            }
-
-            if (OsUtils.isUNIX()) {
-                validateFilePath(path, perms, STRICTLY_PROHIBITED_FILE_PERMISSION);
 
-                Path parent=path.getParent();
-                validateFilePath(parent, IoUtils.getPermissions(parent), STRICTLY_PROHIBITED_FILE_PERMISSION);
-            }
+            KeyUtils.validateStrictKeyFilePermissions(path);
         }
 
         return super.reloadAuthorizedKeys(path, username, session);
@@ -146,14 +124,10 @@ public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthentica
      * @throws IOException If an excluded permission appears in the current ones
      */
     protected Path validateFilePath(Path path, Collection<PosixFilePermission> perms, Collection<PosixFilePermission> excluded) throws IOException {
-        if (GenericUtils.isEmpty(perms) || GenericUtils.isEmpty(excluded)) {
-            return path;
-        }
-
-        for (PosixFilePermission p : excluded) {
-            if (perms.contains(p)) {
-                throw new FileSystemException(path.toString(), path.toString(), "File is not allowed to have permission=" + p);
-            }
+        PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, excluded);
+        if (p != null) {
+            String filePath = path.toString();
+            throw new FileSystemException(filePath, filePath, "File not allowed to have " + p + " permission: " + filePath);
         }
         
         return path;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java
new file mode 100644
index 0000000..898397c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.config.keys;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.IdentityUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.SshServer;
+
+/**
+ * Loads server identity key files - e.g., {@code /etc/ssh/ssh_host_rsa_key}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see SecurityUtils#isBouncyCastleRegistered()
+ */
+public final class ServerIdentity {
+    public static final String ID_FILE_PREFIX = "ssh_host_", ID_FILE_SUFFIX = "_key"; 
+    /**
+     * The server's keys configuration multi-value
+     */
+    public static final String  HOST_KEY_CONFIG_PROP="HostKey";
+
+    private ServerIdentity() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static final Transformer<String,String> ID_GENERATOR =
+            new Transformer<String,String>() {
+                @Override
+                public String transform(String input) {
+                    return getIdentityFileName(input);
+                }
+        };
+
+    /**
+     * Sets the server's {@link KeyPairProvider} with the loaded identities - if any
+     * @param server The {@link SshServer} to configure
+     * @param props The {@link Properties} holding the server's configuration - ignored
+     * if {@code null}/empty
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @return The updated server
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #loadKeyPairProvider(Properties, boolean, LinkOption...)
+     */
+    public static <S extends SshServer> S setKeyPairProvider(S server, Properties props, boolean supportedOnly)
+            throws IOException, GeneralSecurityException {
+        KeyPairProvider provider = loadKeyPairProvider(props, supportedOnly, IoUtils.getLinkOptions(false));
+        if (provider != null) {
+            server.setKeyPairProvider(provider);
+        }
+        
+        return server;
+    }
+
+    /**
+     * @param props The {@link Properties} holding the server's configuration - ignored
+     * if {@code null}/empty
+     * @param supportedOnly If {@code true} then ignore identities that are not
+     * supported internally
+     * @param options The {@link LinkOption}s to use when checking files existence
+     * @return A {@link KeyPair} for the identities - {@code null} if no identities
+     * available (e.g., after filtering unsupported ones)
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #loadIdentities(Properties, LinkOption...)
+     * @see IdentityUtils#createKeyPairProvider(Map, boolean)
+     */
+    public static KeyPairProvider loadKeyPairProvider(Properties props, boolean supportedOnly, LinkOption ... options)
+            throws IOException, GeneralSecurityException {
+        Map<String,KeyPair> ids = loadIdentities(props, options);
+        return IdentityUtils.createKeyPairProvider(ids, supportedOnly);
+    }
+
+    /**
+     * @param props The {@link Properties} holding the server's configuration - ignored
+     * if {@code null}/empty
+     * @param options The {@link LinkOption}s to use when checking files existence
+     * @return A {@link Map} of the identities where key=identity type (case
+     * <U>insensitive</U>), value=the {@link KeyPair} of the identity
+     * @throws IOException If failed to access the file system
+     * @throws GeneralSecurityException If failed to load the keys
+     * @see #findIdentities(Properties, LinkOption...)
+     * @see IdentityUtils#loadIdentities(Map, org.apache.sshd.common.config.keys.FilePasswordProvider, java.nio.file.OpenOption...)
+     */
+    public static Map<String,KeyPair> loadIdentities(Properties props, LinkOption ... options) throws IOException, GeneralSecurityException {
+        Map<String,Path> ids = findIdentities(props, options);
+        return IdentityUtils.loadIdentities(ids, null /* server key files are never encrypted */, IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+
+    /**
+     * @param props The {@link Properties} holding the server's configuration - ignored
+     * if {@code null}/empty
+     * @param options The {@link LinkOption}s to use when checking files existence
+     * @return A {@link Map} of the found identities where key=the identity type
+     * (case <U>insensitive</I>) and value=the {@link Path} of the file holding
+     * the specific type key
+     * @throws IOException If failed to access the file system
+     * @see #getIdentityType(String)
+     * @see #HOST_KEY_CONFIG_PROP
+     * @see SshConfigFileReader#readConfigFile(File)
+     */
+    public static Map<String,Path> findIdentities(Properties props, LinkOption ... options) throws IOException {
+        if (GenericUtils.isEmpty(props)) {
+            return Collections.emptyMap();
+        }
+        
+        String keyList = props.getProperty(HOST_KEY_CONFIG_PROP);
+        String[] paths = GenericUtils.split(keyList, ',');
+        if (GenericUtils.isEmpty(paths)) {
+            return Collections.emptyMap();
+        }
+        
+        Map<String,Path> ids = new TreeMap<String, Path>(String.CASE_INSENSITIVE_ORDER);
+        for (String p : paths) {
+            File file = new File(p);
+            Path path = file.toPath();
+            if (!Files.exists(path, options)) {
+                continue;
+            }
+            
+            String type = getIdentityType(path.getFileName().toString());
+            if (GenericUtils.isEmpty(type)) {
+                type = p;   // just in case the file name does not adhere to the standard naming convention
+            }
+            Path prev = ids.put(type, path);
+            ValidateUtils.checkTrue(prev == null, "Multiple mappings for type=%s", type);
+        }
+        
+        return ids;
+    }
+
+    /**
+     * @param name The file name - ignored if {@code null}/empty
+     * @return The identity type - {@code null} if cannot determine it - e.g.,
+     * does not start/end with the {@link #ID_FILE_PREFIX}/{@link #ID_FILE_SUFFIX}
+     */
+    public static String getIdentityType(String name) {
+        if (GenericUtils.isEmpty(name)
+        || (name.length() <= (ID_FILE_PREFIX.length() + ID_FILE_SUFFIX.length()))
+        || (!name.startsWith(ID_FILE_PREFIX))
+        || (!name.endsWith(ID_FILE_SUFFIX))) {
+            return null;
+        } else {
+            return name.substring(ID_FILE_PREFIX.length(), name.length() - ID_FILE_SUFFIX.length());
+        }
+    }
+
+    public static String getIdentityFileName(NamedResource r) {
+        return getIdentityFileName((r == null) ? null : r.getName());
+    }
+
+    /**
+     * @param type The identity type - e.g., {@code rsa} - ignored
+     * if {@code null}/empty
+     * @return The matching file name for the identity - {@code null}
+     * if no name
+     * @see #ID_FILE_PREFIX
+     * @see #ID_FILE_SUFFIX
+     * @see IdentityUtils#getIdentityFileName(String, String, String)
+     */
+    public static String getIdentityFileName(String type) {
+        return IdentityUtils.getIdentityFileName(ID_FILE_PREFIX, type, ID_FILE_SUFFIX);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
index 0639c4b..d5b8364 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
@@ -128,7 +128,7 @@ public class ServerSession extends AbstractSession {
             }
 
             if (resolvedHostKeys == null) {
-                resolvedHostKeys = new StringBuilder();
+                resolvedHostKeys = new StringBuilder(supported.size() * 16 /* ecdsa-sha2-xxxx */);
             }
 
             if (resolvedHostKeys.length() > 0) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/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
new file mode 100644
index 0000000..aa8d957
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+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.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.Assume;
+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 ClientIdentityTest extends BaseTestSupport {
+    public ClientIdentityTest() {
+        super();
+    }
+
+    @Test
+    public void testLoadClientIdentities() throws Exception {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+
+        Path resFolder = getClassResourcesFolder(TEST_SUBFOLDER, getClass()).toPath();
+        LinkOption[] options = IoUtils.getLinkOptions(false);
+        Collection<BuiltinIdentities> expected = EnumSet.noneOf(BuiltinIdentities.class);
+        for (BuiltinIdentities type : BuiltinIdentities.VALUES) {
+            String fileName = ClientIdentity.getIdentityFileName(type);
+            Path file = resFolder.resolve(fileName);
+            if (!Files.exists(file, options)) {
+                System.out.println("Skip non-existing identity file " + file);
+                continue;
+            }
+            
+            if (!type.isSupported()) {
+                System.out.println("Skip unsupported identity file " + file);
+                continue;
+            }
+
+            expected.add(type);
+        }
+
+        Map<String,KeyPair> ids = ClientIdentity.loadDefaultIdentities(
+                resFolder,
+                false,   // don't be strict
+                null,    // none of the files is password protected
+                options);
+        assertEquals("Mismatched loaded ids count", GenericUtils.size(expected), GenericUtils.size(ids));
+        
+        Collection<KeyPair> pairs = new ArrayList<KeyPair>(ids.size());
+        for (BuiltinIdentities type : BuiltinIdentities.VALUES) {
+            if (expected.contains(type)) {
+                KeyPair kp = ids.get(type.getName());
+                assertNotNull("No key pair loaded for " + type, kp);
+                pairs.add(kp);
+            }
+        }
+        
+        KeyPairProvider provider = IdentityUtils.createKeyPairProvider(ids, true /* supported only */);
+        assertNotNull("No provider generated", provider);
+
+        Iterable<KeyPair> keys = provider.loadKeys();
+        for (KeyPair kp : keys) {
+            assertTrue("Unexpected loaded key: " + kp, pairs.remove(kp));
+        }
+        
+        assertEquals("Not all pairs listed", 0, pairs.size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/612a2f47/sshd-core/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
new file mode 100644
index 0000000..c0bb996
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.config.keys;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.util.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 BuiltinIdentitiesTest extends BaseTestSupport {
+    public BuiltinIdentitiesTest() {
+        super();
+    }
+
+    @Test
+    public void testFromName() {
+        for (BuiltinIdentities expected : BuiltinIdentities.VALUES) {
+            String name = expected.getName();
+            for (int index = 0; index < name.length(); index++) {
+                assertSame(name, expected, BuiltinIdentities.fromName(name));
+                name = shuffleCase(name);
+            }
+        }
+    }
+
+    @Test
+    public void testFromAlgorithm() {
+        for (BuiltinIdentities expected : BuiltinIdentities.VALUES) {
+            String algorithm = expected.getAlgorithm();
+            for (int index = 0; index < algorithm.length(); index++) {
+                assertSame(algorithm, expected, BuiltinIdentities.fromAlgorithm(algorithm));
+                algorithm = shuffleCase(algorithm);
+            }
+        }
+    }
+
+    @Test
+    public void testFromKey() throws GeneralSecurityException {
+        for (BuiltinIdentities expected : BuiltinIdentities.VALUES) {
+            String name = expected.getName();
+            if (!expected.isSupported()) {
+                System.out.println("Skip unsupported built-in identity: " + name);
+                continue;
+            }
+
+            KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(expected.getAlgorithm());
+            KeyPair kp = gen.generateKeyPair();
+            System.out.println("Checking built-in identity: " + name);
+            assertSame(name + "[pair]", expected, BuiltinIdentities.fromKeyPair(kp));
+            assertSame(name + "[public]", expected, BuiltinIdentities.fromKey(kp.getPublic()));
+            assertSame(name + "[private]", expected, BuiltinIdentities.fromKey(kp.getPrivate()));
+        }
+    }
+
+    @Test
+    public void testAllConstantsCovered() throws Exception {
+        Field[] fields = BuiltinIdentities.Constants.class.getFields();
+        for (Field f : fields) {
+            int mods = f.getModifiers();
+            if (!Modifier.isStatic(mods)) {
+                continue;
+            }
+            
+            if (!Modifier.isFinal(mods)) {
+                continue;
+            }
+            
+            Class<?> type = f.getType();
+            if (!String.class.isAssignableFrom(type)) {
+                continue;
+            }
+            
+            String name = f.getName(), value = (String) f.get(null);
+            BuiltinIdentities id = BuiltinIdentities.fromName(value);
+            assertNotNull("No match found for field " + name + "=" + value, id);
+        }
+    }
+}