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 2018/09/06 16:03:52 UTC

[42/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common utilities code from sshd-core into sshd-common (new artifact)

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestUtils.java
new file mode 100644
index 0000000..30369ac
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestUtils.java
@@ -0,0 +1,228 @@
+/*
+ * 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.digest;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class DigestUtils {
+    private DigestUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    /**
+     * @param algorithm The digest algorithm - never {@code null}/empty
+     * @return {@code true} if this digest algorithm is supported
+     * @see SecurityUtils#getMessageDigest(String)
+     */
+    public static boolean checkSupported(String algorithm) {
+        ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm");
+        try {
+            MessageDigest digest = SecurityUtils.getMessageDigest(algorithm);
+            return digest != null;  // just in case
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * @param <D> The generic type of digest factory
+     * @param algo The required algorithm name - ignored if {@code null}/empty
+     * @param comp The {@link Comparator} to use to compare algorithm names
+     * @param digests The factories to check - ignored if {@code null}/empty
+     * @return The first {@link DigestFactory} whose algorithm matches the required one
+     * according to the comparator - {@code null} if no match found
+     */
+    public static <D extends Digest> D findDigestByAlgorithm(String algo, Comparator<? super String> comp, Collection<? extends D> digests) {
+        if (GenericUtils.isEmpty(algo) || GenericUtils.isEmpty(digests)) {
+            return null;
+        }
+
+        for (D d : digests) {
+            if (comp.compare(algo, d.getAlgorithm()) == 0) {
+                return d;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param <F> The generic type of digest factory
+     * @param algo The required algorithm name - ignored if {@code null}/empty
+     * @param comp The {@link Comparator} to use to compare algorithm names
+     * @param factories The factories to check - ignored if {@code null}/empty
+     * @return The first {@link DigestFactory} whose algorithm matches the required one
+     * according to the comparator - {@code null} if no match found
+     */
+    public static <F extends DigestFactory> F findFactoryByAlgorithm(String algo, Comparator<? super String> comp, Collection<? extends F> factories) {
+        if (GenericUtils.isEmpty(algo) || GenericUtils.isEmpty(factories)) {
+            return null;
+        }
+
+        for (F f : factories) {
+            if (comp.compare(algo, f.getAlgorithm()) == 0) {
+                return f;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param f The {@link Factory} to create the {@link Digest} to use
+     * @param s The {@link String} to digest - ignored if {@code null}/empty,
+     *          otherwise its UTF-8 representation is used as input for the fingerprint
+     * @return The fingerprint - {@code null} if {@code null}/empty input
+     * @throws Exception If failed to calculate the digest
+     * @see #getFingerPrint(Digest, String, Charset)
+     */
+    public static String getFingerPrint(Factory<? extends Digest> f, String s) throws Exception {
+        return getFingerPrint(f, s, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @param f       The {@link Factory} to create the {@link Digest} to use
+     * @param s       The {@link String} to digest - ignored if {@code null}/empty
+     * @param charset The {@link Charset} to use in order to convert the
+     *                string to its byte representation to use as input for the fingerprint
+     * @return The fingerprint - {@code null} if {@code null}/empty input
+     * @throws Exception If failed to calculate the digest
+     */
+    public static String getFingerPrint(Factory<? extends Digest> f, String s, Charset charset) throws Exception {
+        return getFingerPrint(Objects.requireNonNull(f, "No factory").create(), s, charset);
+    }
+
+    /**
+     * @param d The {@link Digest} to use
+     * @param s The {@link String} to digest - ignored if {@code null}/empty,
+     *          otherwise its UTF-8 representation is used as input for the fingerprint
+     * @return The fingerprint - {@code null} if {@code null}/empty input
+     * @throws Exception If failed to calculate the digest
+     * @see #getFingerPrint(Digest, String, Charset)
+     */
+    public static String getFingerPrint(Digest d, String s) throws Exception {
+        return getFingerPrint(d, s, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @param d       The {@link Digest} to use
+     * @param s       The {@link String} to digest - ignored if {@code null}/empty
+     * @param charset The {@link Charset} to use in order to convert the
+     *                string to its byte representation to use as input for the fingerprint
+     * @return The fingerprint - {@code null} if {@code null}/empty input
+     * @throws Exception If failed to calculate the digest
+     */
+    public static String getFingerPrint(Digest d, String s, Charset charset) throws Exception {
+        if (GenericUtils.isEmpty(s)) {
+            return null;
+        } else {
+            return DigestUtils.getFingerPrint(d, s.getBytes(charset));
+        }
+    }
+
+    /**
+     * @param f   The {@link Factory} to create the {@link Digest} to use
+     * @param buf The data buffer to be fingerprint-ed
+     * @return The fingerprint - {@code null} if empty data buffer
+     * @throws Exception If failed to calculate the fingerprint
+     * @see #getFingerPrint(Factory, byte[], int, int)
+     */
+    public static String getFingerPrint(Factory<? extends Digest> f, byte... buf) throws Exception {
+        return getFingerPrint(f, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * @param f      The {@link Factory} to create the {@link Digest} to use
+     * @param buf    The data buffer to be fingerprint-ed
+     * @param offset The offset of the data in the buffer
+     * @param len    The length of data - ignored if non-positive
+     * @return The fingerprint - {@code null} if non-positive length
+     * @throws Exception If failed to calculate the fingerprint
+     */
+    public static String getFingerPrint(Factory<? extends Digest> f, byte[] buf, int offset, int len) throws Exception {
+        return getFingerPrint(Objects.requireNonNull(f, "No factory").create(), buf, offset, len);
+    }
+
+    /**
+     * @param d   The {@link Digest} to use
+     * @param buf The data buffer to be fingerprint-ed
+     * @return The fingerprint - {@code null} if empty data buffer
+     * @throws Exception If failed to calculate the fingerprint
+     * @see #getFingerPrint(Digest, byte[], int, int)
+     */
+    public static String getFingerPrint(Digest d, byte... buf) throws Exception {
+        return getFingerPrint(d, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * @param d      The {@link Digest} to use
+     * @param buf    The data buffer to be fingerprint-ed
+     * @param offset The offset of the data in the buffer
+     * @param len    The length of data - ignored if non-positive
+     * @return The fingerprint - {@code null} if non-positive length
+     * @throws Exception If failed to calculate the fingerprint
+     * @see #getRawFingerprint(Digest, byte[], int, int)
+     */
+    public static String getFingerPrint(Digest d, byte[] buf, int offset, int len) throws Exception {
+        if (len <= 0) {
+            return null;
+        }
+
+        byte[] data = getRawFingerprint(d, buf, offset, len);
+        String algo = d.getAlgorithm();
+        if (BuiltinDigests.md5.getAlgorithm().equals(algo)) {
+            return algo + ":" + BufferUtils.toHex(':', data).toLowerCase();
+        }
+
+        Base64.Encoder encoder = Base64.getEncoder();
+        return algo.replace("-", "").toUpperCase() + ":" + encoder.encodeToString(data).replaceAll("=", "");
+    }
+
+    public static byte[] getRawFingerprint(Digest d, byte... buf) throws Exception {
+        return getRawFingerprint(d, buf, 0, NumberUtils.length(buf));
+    }
+
+    public static byte[] getRawFingerprint(Digest d, byte[] buf, int offset, int len) throws Exception {
+        if (len <= 0) {
+            return null;
+        }
+
+        Objects.requireNonNull(d, "No digest").init();
+        d.update(buf, offset, len);
+
+        return d.digest();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/digest/package.html
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/package.html b/sshd-common/src/main/java/org/apache/sshd/common/digest/package.html
new file mode 100644
index 0000000..65940e0
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/package.html
@@ -0,0 +1,25 @@
+<!--
+    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.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<a href="{@docRoot}/org/apache/sshd/common/digest/Digest.html"><code>Digest</code></a> implementations.
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
new file mode 100644
index 0000000..1dca54c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
@@ -0,0 +1,194 @@
+/*
+ * 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.future;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.StreamCorruptedException;
+import java.util.function.Function;
+
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @param <T> Type of future
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSshFuture<T extends SshFuture> extends AbstractLoggingBean implements SshFuture<T> {
+    /**
+     * A default value to indicate the future has been canceled
+     */
+    protected static final Object CANCELED = new Object();
+
+    protected final boolean debugEnabled;
+    protected final boolean traceEnabled;
+    private final Object id;
+
+    /**
+     * @param id Some identifier useful as {@link #toString()} value
+     */
+    protected AbstractSshFuture(Object id) {
+        this.id = id;
+        this.debugEnabled = log.isDebugEnabled();
+        this.traceEnabled = log.isTraceEnabled();
+    }
+
+    @Override
+    public Object getId() {
+        return id;
+    }
+
+    @Override
+    public boolean await(long timeoutMillis) throws IOException {
+        return await0(timeoutMillis, true) != null;
+    }
+
+    @Override
+    public boolean awaitUninterruptibly(long timeoutMillis) {
+        try {
+            return await0(timeoutMillis, false) != null;
+        } catch (InterruptedIOException e) {
+            throw formatExceptionMessage(msg -> new InternalError(msg, e),
+                    "Unexpected interrupted exception wile awaitUninterruptibly %d msec: %s",
+                    timeoutMillis, e.getMessage());
+        }
+    }
+
+    /**
+     * <P>Waits (interruptible) for the specified timeout (msec.) and then checks
+     * the result:</P>
+     * <UL>
+     *      <LI><P>
+     *      If result is {@code null} then timeout is assumed to have expired - throw
+     *      an appropriate {@link IOException}
+     *      </P></LI>
+     *
+     *      <LI><P>
+     *      If the result is of the expected type, then cast and return it
+     *      </P></LI>
+     *
+     *      <LI><P>
+     *      If the result is an {@link IOException} then re-throw it
+     *      </P></LI>
+     *
+     *      <LI><P>
+     *      If the result is a {@link Throwable} then throw an {@link IOException}
+     *      whose cause is the original exception
+     *      </P></LI>
+     *
+     *      <LI><P>
+     *      Otherwise (should never happen), throw a {@link StreamCorruptedException}
+     *      with the name of the result type
+     *      </P></LI>
+     * </UL>
+     *
+     * @param <R>          The generic result type
+     * @param expectedType The expected result type
+     * @param timeout      The timeout (millis) to wait for a result
+     * @return The (never {@code null}) result
+     * @throws IOException If failed to retrieve the expected result on time
+     */
+    protected <R> R verifyResult(Class<? extends R> expectedType, long timeout) throws IOException {
+        Object value = await0(timeout, true);
+        if (value == null) {
+            throw formatExceptionMessage(SshException::new, "Failed to get operation result within specified timeout: %s", timeout);
+        }
+
+        Class<?> actualType = value.getClass();
+        if (expectedType.isAssignableFrom(actualType)) {
+            return expectedType.cast(value);
+        }
+
+        if (Throwable.class.isAssignableFrom(actualType)) {
+            Throwable t = GenericUtils.peelException((Throwable) value);
+            if (t != value) {
+                value = t;
+                actualType = value.getClass();
+            }
+
+            if (IOException.class.isAssignableFrom(actualType)) {
+                throw (IOException) value;
+            }
+
+            Throwable cause = GenericUtils.resolveExceptionCause(t);
+            throw formatExceptionMessage(msg -> new SshException(msg, cause), "Failed (%s) to execute: %s", t.getClass().getSimpleName(), t.getMessage());
+        } else {    // what else can it be ????
+            throw formatExceptionMessage(StreamCorruptedException::new, "Unknown result type: %s", actualType.getName());
+        }
+    }
+
+    /**
+     * Wait for the Future to be ready. If the requested delay is 0 or
+     * negative, this method returns immediately.
+     *
+     * @param timeoutMillis The delay we will wait for the Future to be ready
+     * @param interruptable Tells if the wait can be interrupted or not.
+     * If {@code true} and the thread is interrupted then an {@link InterruptedIOException}
+     * is thrown.
+     * @return The non-{@code null} result object if the Future is ready,
+     * {@code null} if the timeout expired and no result was received
+     * @throws InterruptedIOException If the thread has been interrupted when it's not allowed.
+     */
+    protected abstract Object await0(long timeoutMillis, boolean interruptable) throws InterruptedIOException;
+
+    @SuppressWarnings("unchecked")
+    protected SshFutureListener<T> asListener(Object o) {
+        return (SshFutureListener<T>) o;
+    }
+
+    protected void notifyListener(SshFutureListener<T> l) {
+        try {
+            l.operationComplete(asT());
+        } catch (Throwable t) {
+            log.warn("notifyListener({}) failed ({}) to invoke {}: {}",
+                     this, t.getClass().getSimpleName(), l, t.getMessage());
+            if (debugEnabled) {
+                log.debug("notifyListener(" + this + ")[" + l + "] invocation failure details", t);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected T asT() {
+        return (T) this;
+    }
+
+    /**
+     * Generates an exception whose message is prefixed by the future simple class name + {@link #getId() identifier}
+     * as a hint to the context of the failure.
+     *
+     * @param <E> Type of {@link Throwable} being generated
+     * @param exceptionCreator The exception creator from the formatted message
+     * @param format The extra payload format as per {@link String#format(String, Object...)}
+     * @param args The formatting arguments
+     * @return The generated exception
+     */
+    protected <E extends Throwable> E formatExceptionMessage(Function<? super String, ? extends E> exceptionCreator, String format, Object... args) {
+        String messagePayload = String.format(format, args);
+        String excMessage = getClass().getSimpleName() + "[" + getId() + "]: " + messagePayload;
+        return exceptionCreator.apply(excMessage);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[id=" + getId() + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/CloseFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/CloseFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/CloseFuture.java
new file mode 100644
index 0000000..808716d
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/CloseFuture.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.future;
+
+/**
+ * An {@link SshFuture} for asynchronous close requests.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface CloseFuture extends SshFuture<CloseFuture> {
+
+    /**
+     * @return <tt>true</tt> if the close request is finished and the target is closed.
+     */
+    boolean isClosed();
+
+    /**
+     * Marks this future as closed and notifies all threads waiting for this
+     * future.  This method is invoked by SSHD internally.  Please do not call
+     * this method directly.
+     */
+    void setClosed();
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultCloseFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultCloseFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultCloseFuture.java
new file mode 100644
index 0000000..4c34a06
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultCloseFuture.java
@@ -0,0 +1,52 @@
+/*
+ * 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.future;
+
+/**
+ * A default implementation of {@link CloseFuture}.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultCloseFuture extends DefaultSshFuture<CloseFuture> implements CloseFuture {
+
+    /**
+     * Create a new instance
+     *
+     * @param id Some identifier useful as {@link #toString()} value
+     * @param lock A synchronization object for locking access - if {@code null}
+     * then synchronization occurs on {@code this} instance
+     */
+    public DefaultCloseFuture(Object id, Object lock) {
+        super(id, lock);
+    }
+
+    @Override
+    public boolean isClosed() {
+        if (isDone()) {
+            return (Boolean) getValue();
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void setClosed() {
+        setValue(Boolean.TRUE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
new file mode 100644
index 0000000..cd475ff
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
@@ -0,0 +1,236 @@
+/*
+ * 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.future;
+
+import java.io.InterruptedIOException;
+import java.lang.reflect.Array;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * A default implementation of {@link SshFuture}.
+ *
+ * @param <T> Type of future
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultSshFuture<T extends SshFuture> extends AbstractSshFuture<T> {
+    /**
+     * A lock used by the wait() method
+     */
+    private final Object lock;
+    private Object listeners;
+    private Object result;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param id Some identifier useful as {@link #toString()} value
+     * @param lock A synchronization object for locking access - if {@code null}
+     * then synchronization occurs on {@code this} instance
+     */
+    public DefaultSshFuture(Object id, Object lock) {
+        super(id);
+
+        this.lock = (lock != null) ? lock : this;
+    }
+
+    @Override
+    protected Object await0(long timeoutMillis, boolean interruptable) throws InterruptedIOException {
+        ValidateUtils.checkTrue(timeoutMillis >= 0L, "Negative timeout N/A: %d", timeoutMillis);
+        long startTime = System.currentTimeMillis();
+        long curTime = startTime;
+        long endTime = ((Long.MAX_VALUE - timeoutMillis) < curTime) ? Long.MAX_VALUE : (curTime + timeoutMillis);
+
+        synchronized (lock) {
+            if ((result != null) || (timeoutMillis <= 0)) {
+                return result;
+            }
+
+            for (;;) {
+                try {
+                    lock.wait(endTime - curTime);
+                } catch (InterruptedException e) {
+                    if (interruptable) {
+                        curTime = System.currentTimeMillis();
+                        throw formatExceptionMessage(msg -> {
+                            InterruptedIOException exc = new InterruptedIOException(msg);
+                            exc.initCause(e);
+                            return exc;
+                        }, "Interrupted after %d msec.", curTime - startTime);
+                    }
+                }
+
+                curTime = System.currentTimeMillis();
+                if ((result != null) || (curTime >= endTime)) {
+                    return result;
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isDone() {
+        synchronized (lock) {
+            return result != null;
+        }
+    }
+
+    /**
+     * Sets the result of the asynchronous operation, and mark it as finished.
+     *
+     * @param newValue The operation result
+     */
+    public void setValue(Object newValue) {
+        synchronized (lock) {
+            // Allow only once.
+            if (result != null) {
+                return;
+            }
+
+            result = (newValue != null) ? newValue : GenericUtils.NULL;
+            lock.notifyAll();
+        }
+
+        notifyListeners();
+    }
+
+    public int getNumRegisteredListeners() {
+        synchronized (lock) {
+            if (listeners == null) {
+                return 0;
+            } else if (listeners instanceof SshFutureListener) {
+                return 1;
+            } else {
+                int l = Array.getLength(listeners);
+                int count = 0;
+                for (int i = 0; i < l; i++) {
+                    if (Array.get(listeners, i) != null) {
+                        count++;
+                    }
+                }
+                return count;
+            }
+        }
+    }
+
+    /**
+     * @return The result of the asynchronous operation - or {@code null}
+     * if none set.
+     */
+    public Object getValue() {
+        synchronized (lock) {
+            return (result == GenericUtils.NULL) ? null : result;
+        }
+    }
+
+    @Override
+    public T addListener(SshFutureListener<T> listener) {
+        Objects.requireNonNull(listener, "Missing listener argument");
+        boolean notifyNow = false;
+        synchronized (lock) {
+            // if already have a result don't register the listener and invoke it directly
+            if (result != null) {
+                notifyNow = true;
+            } else if (listeners == null) {
+                listeners = listener;   // 1st listener ?
+            } else if (listeners instanceof SshFutureListener) {
+                listeners = new Object[]{listeners, listener};
+            } else {    // increase array of registered listeners
+                Object[] ol = (Object[]) listeners;
+                int l = ol.length;
+                Object[] nl = new Object[l + 1];
+                System.arraycopy(ol, 0, nl, 0, l);
+                nl[l] = listener;
+                listeners = nl;
+            }
+        }
+
+        if (notifyNow) {
+            notifyListener(listener);
+        }
+
+        return asT();
+    }
+
+    @Override
+    public T removeListener(SshFutureListener<T> listener) {
+        Objects.requireNonNull(listener, "No listener provided");
+
+        synchronized (lock) {
+            if (result != null) {
+                return asT();   // the train has already left the station...
+            }
+
+            if (listeners == null) {
+                return asT();   // no registered instances anyway
+            }
+
+            if (listeners == listener) {
+                listeners = null;   // the one and only
+            } else if (!(listeners instanceof SshFutureListener))  {
+                int l = Array.getLength(listeners);
+                for (int i = 0; i < l; i++) {
+                    if (Array.get(listeners, i) == listener) {
+                        Array.set(listeners, i, null);
+                        break;
+                    }
+                }
+            }
+        }
+
+        return asT();
+    }
+
+    protected void notifyListeners() {
+        /*
+         * There won't be any visibility problem or concurrent modification
+         * because result value is checked in both addListener and
+         * removeListener calls under lock. If the result is already set then
+         * both methods will not modify the internal listeners
+         */
+        if (listeners != null) {
+            if (listeners instanceof SshFutureListener) {
+                notifyListener(asListener(listeners));
+            } else {
+                int l = Array.getLength(listeners);
+                for (int i = 0; i < l; i++) {
+                    SshFutureListener<T> listener = asListener(Array.get(listeners, i));
+                    if (listener != null) {
+                        notifyListener(listener);
+                    }
+                }
+            }
+        }
+    }
+
+    public boolean isCanceled() {
+        return getValue() == CANCELED;
+    }
+
+    public void cancel() {
+        setValue(CANCELED);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[value=" + result + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultVerifiableSshFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultVerifiableSshFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultVerifiableSshFuture.java
new file mode 100644
index 0000000..1b02fed
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/DefaultVerifiableSshFuture.java
@@ -0,0 +1,30 @@
+/*
+ * 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.future;
+
+/**
+ * @param <T> Type of future
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class DefaultVerifiableSshFuture<T extends SshFuture> extends DefaultSshFuture<T> implements VerifiableFuture<T> {
+    protected DefaultVerifiableSshFuture(Object id, Object lock) {
+        super(id, lock);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/SshFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/SshFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/SshFuture.java
new file mode 100644
index 0000000..b0d8e3c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/SshFuture.java
@@ -0,0 +1,48 @@
+/*
+ * 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.future;
+
+/**
+ * Represents the completion of an asynchronous SSH operation on a given object
+ * (it may be an SSH session or an SSH channel).
+ * Can be listened for completion using a {@link SshFutureListener}.
+ *
+ * @param <T> Type of future
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SshFuture<T extends SshFuture> extends WaitableFuture {
+    /**
+     * Adds an event <tt>listener</tt> which is notified when
+     * this future is completed. If the listener is added
+     * after the completion, the listener is directly notified.
+     *
+     * @param listener The {@link SshFutureListener} instance to add
+     * @return The future instance
+     */
+    T addListener(SshFutureListener<T> listener);
+
+    /**
+     * Removes an existing event <tt>listener</tt> so it won't be notified when
+     * the future is completed.
+     *
+     * @param listener The {@link SshFutureListener} instance to remove
+     * @return The future instance
+     */
+    T removeListener(SshFutureListener<T> listener);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/SshFutureListener.java b/sshd-common/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
new file mode 100644
index 0000000..a005c0f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.future;
+
+import org.apache.sshd.common.util.SshdEventListener;
+
+/**
+ * Something interested in being notified when the completion
+ * of an asynchronous SSH operation : {@link SshFuture}.
+ *
+ * @param <T> type of future
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@SuppressWarnings("rawtypes")
+@FunctionalInterface
+public interface SshFutureListener<T extends SshFuture> extends SshdEventListener {
+
+    /**
+     * Invoked when the operation associated with the {@link SshFuture}
+     * has been completed even if you add the listener after the completion.
+     *
+     * @param future The source {@link SshFuture} which called this
+     *               callback.
+     */
+    void operationComplete(T future);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/VerifiableFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/VerifiableFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/VerifiableFuture.java
new file mode 100644
index 0000000..e318de7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/VerifiableFuture.java
@@ -0,0 +1,68 @@
+/*
+ * 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.future;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents an asynchronous operation whose successful result can be
+ * verified somehow. The contract guarantees that if the {@code verifyXXX}
+ * method returns without an exception then the operation was completed
+ * <U>successfully</U>
+ *
+ * @param <T> Type of verification result
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface VerifiableFuture<T> {
+    /**
+     * Wait {@link Long#MAX_VALUE} msec. and verify that the operation was successful
+     *
+     * @return The (same) future instance
+     * @throws IOException If failed to verify successfully on time
+     * @see #verify(long)
+     */
+    default T verify() throws IOException {
+        return verify(Long.MAX_VALUE);
+    }
+
+    /**
+     * Wait and verify that the operation was successful
+     *
+     * @param timeout The number of time units to wait
+     * @param unit    The wait {@link TimeUnit}
+     * @return The (same) future instance
+     * @throws IOException If failed to verify successfully on time
+     * @see #verify(long)
+     */
+    default T verify(long timeout, TimeUnit unit) throws IOException {
+        return verify(unit.toMillis(timeout));
+    }
+
+    /**
+     * Wait and verify that the operation was successful
+     *
+     * @param timeoutMillis Wait timeout in milliseconds
+     * @return The (same) future instance
+     * @throws IOException If failed to verify successfully on time
+     */
+    T verify(long timeoutMillis) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/future/WaitableFuture.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/WaitableFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/WaitableFuture.java
new file mode 100644
index 0000000..aff4adc
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/WaitableFuture.java
@@ -0,0 +1,118 @@
+/*
+ * 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.future;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents an asynchronous operation which one can wait for its completion.
+ * <B>Note:</B> the only thing guaranteed is that if {@code true} is returned
+ * from one of the {@code awaitXXX} methods then the operation has completed.
+ * However, the <B>caller</B> has to determine whether it was a successful or
+ * failed completion.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface WaitableFuture {
+    /**
+     * @return Some identifier useful as {@link #toString()} value
+     */
+    Object getId();
+
+    /**
+     * Wait {@link Long#MAX_VALUE} msec. for the asynchronous operation to complete.
+     * The attached listeners will be notified when the operation is
+     * completed.
+     *
+     * @return {@code true} if the operation is completed.
+     * @throws IOException if failed - specifically {@link java.io.InterruptedIOException}
+     *                     if waiting was interrupted
+     * @see #await(long)
+     */
+    default boolean await() throws IOException {
+        return await(Long.MAX_VALUE);
+    }
+
+    /**
+     * Wait for the asynchronous operation to complete with the specified timeout.
+     *
+     * @param timeout   The number of time units to wait
+     * @param unit      The {@link TimeUnit} for waiting
+     * @return {@code true} if the operation is completed.
+     * @throws IOException if failed - specifically {@link java.io.InterruptedIOException}
+     *                     if waiting was interrupted
+     * @see #await(long)
+     */
+    default boolean await(long timeout, TimeUnit unit) throws IOException {
+        return await(unit.toMillis(timeout));
+    }
+
+    /**
+     * Wait for the asynchronous operation to complete with the specified timeout.
+     *
+     * @param timeoutMillis Wait time in milliseconds
+     * @return {@code true} if the operation is completed.
+     * @throws IOException if failed - specifically {@link java.io.InterruptedIOException}
+     *                     if waiting was interrupted
+     */
+    boolean await(long timeoutMillis) throws IOException;
+
+    /**
+     * Wait {@link Long#MAX_VALUE} msec. for the asynchronous operation to complete
+     * uninterruptibly. The attached listeners will be notified when the operation is
+     * completed.
+     *
+     * @return {@code true} if the operation is completed.
+     * @see #awaitUninterruptibly(long)
+     */
+    default boolean awaitUninterruptibly() {
+        return awaitUninterruptibly(Long.MAX_VALUE);
+    }
+
+    /**
+     * Wait for the asynchronous operation to complete with the specified timeout
+     * uninterruptibly.
+     *
+     * @param timeout   The number of time units to wait
+     * @param unit      The {@link TimeUnit} for waiting
+     * @return {@code true} if the operation is completed.
+     * @see #awaitUninterruptibly(long)
+     */
+    default boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
+        return awaitUninterruptibly(unit.toMillis(timeout));
+    }
+
+    /**
+     * Wait for the asynchronous operation to complete with the specified timeout
+     * uninterruptibly.
+     *
+     * @param timeoutMillis Wait time in milliseconds
+     * @return {@code true} if the operation is finished.
+     */
+    boolean awaitUninterruptibly(long timeoutMillis);
+
+    /**
+     * @return {@code true} if the asynchronous operation is completed. <B>Note:</B>
+     * it is up to the <B>caller</B> to determine whether it was a successful or
+     * failed completion.
+     */
+    boolean isDone();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
new file mode 100644
index 0000000..077d199
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * Provides a default implementation for some {@link KeyPairProvider} methods
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractKeyPairProvider extends AbstractLoggingBean implements KeyPairProvider {
+    protected AbstractKeyPairProvider() {
+        super();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java
new file mode 100644
index 0000000..e4e941d
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java
@@ -0,0 +1,234 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @param <R> Type of resource from which the {@link KeyPair} is generated
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractResourceKeyPairProvider<R> extends AbstractKeyPairProvider {
+    private FilePasswordProvider passwordFinder;
+    /*
+     * NOTE: the map is case insensitive even for Linux, as it is (very) bad
+     * practice to have 2 key files that differ from one another only in their
+     * case...
+     */
+    private final Map<String, KeyPair> cacheMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+    protected AbstractResourceKeyPairProvider() {
+        super();
+    }
+
+    public FilePasswordProvider getPasswordFinder() {
+        return passwordFinder;
+    }
+
+    public void setPasswordFinder(FilePasswordProvider passwordFinder) {
+        this.passwordFinder = passwordFinder;
+    }
+
+    /**
+     * Checks which of the new resources we already loaded and can keep the
+     * associated key pair
+     *
+     * @param resources The collection of new resources - can be {@code null}/empty
+     * in which case the cache is cleared
+     */
+    protected void resetCacheMap(Collection<?> resources) {
+        // if have any cached pairs then see what we can keep from previous load
+        Collection<String> toDelete = Collections.emptySet();
+        synchronized (cacheMap) {
+            if (cacheMap.size() <= 0) {
+                return; // already empty - nothing to keep
+            }
+
+            if (GenericUtils.isEmpty(resources)) {
+                cacheMap.clear();
+                return;
+            }
+
+            for (Object r : resources) {
+                String resourceKey = ValidateUtils.checkNotNullAndNotEmpty(Objects.toString(r, null), "No resource key value");
+                if (cacheMap.containsKey(resourceKey)) {
+                    continue;
+                }
+
+                if (toDelete.isEmpty()) {
+                    toDelete = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+                }
+
+                if (!toDelete.add(resourceKey)) {
+                    continue;   // debug breakpoint
+                }
+            }
+
+            if (GenericUtils.size(toDelete) > 0) {
+                toDelete.forEach(cacheMap::remove);
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("resetCacheMap(" + resources + ") removed previous cached keys for " + toDelete);
+        }
+    }
+
+    protected Iterable<KeyPair> loadKeys(final Collection<? extends R> resources) {
+        if (GenericUtils.isEmpty(resources)) {
+            return Collections.emptyList();
+        } else {
+            return () -> new KeyPairIterator(resources);
+        }
+    }
+
+    protected KeyPair doLoadKey(R resource) throws IOException, GeneralSecurityException {
+        String resourceKey = ValidateUtils.checkNotNullAndNotEmpty(Objects.toString(resource, null), "No resource string value");
+        KeyPair kp;
+        synchronized (cacheMap) {
+            // check if lucky enough to have already loaded this file
+            kp = cacheMap.get(resourceKey);
+        }
+
+        if (kp != null) {
+            if (log.isTraceEnabled()) {
+                PublicKey key = kp.getPublic();
+                log.trace("doLoadKey({}) use cached key {}-{}",
+                          resourceKey, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
+            }
+            return kp;
+        }
+
+        kp = doLoadKey(resourceKey, resource, getPasswordFinder());
+        if (kp != null) {
+            boolean reusedKey;
+            synchronized (cacheMap) {
+                // if somebody else beat us to it, use the cached key - just in case file contents changed
+                reusedKey = cacheMap.containsKey(resourceKey);
+                if (reusedKey) {
+                    kp = cacheMap.get(resourceKey);
+                } else {
+                    cacheMap.put(resourceKey, kp);
+                }
+            }
+
+            if (log.isDebugEnabled()) {
+                PublicKey key = kp.getPublic();
+                log.debug("doLoadKey({}) {} {}-{}",
+                          resourceKey, reusedKey ? "re-loaded" : "loaded",
+                          KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("doLoadKey({}) no key loaded", resourceKey);
+            }
+        }
+
+        return kp;
+    }
+
+    protected KeyPair doLoadKey(String resourceKey, R resource, FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+        try (InputStream inputStream = openKeyPairResource(resourceKey, resource)) {
+            return doLoadKey(resourceKey, inputStream, provider);
+        }
+    }
+
+    protected abstract InputStream openKeyPairResource(String resourceKey, R resource) throws IOException;
+
+    protected KeyPair doLoadKey(String resourceKey, InputStream inputStream, FilePasswordProvider provider)
+            throws IOException, GeneralSecurityException {
+        return SecurityUtils.loadKeyPairIdentity(resourceKey, inputStream, provider);
+    }
+
+    protected class KeyPairIterator implements Iterator<KeyPair> {
+        private final Iterator<? extends R> iterator;
+        private KeyPair nextKeyPair;
+        private boolean nextKeyPairSet;
+
+        protected KeyPairIterator(Collection<? extends R> resources) {
+            iterator = resources.iterator();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return nextKeyPairSet || setNextObject();
+        }
+
+        @Override
+        public KeyPair next() {
+            if (!nextKeyPairSet) {
+                if (!setNextObject()) {
+                    throw new NoSuchElementException("Out of files to try");
+                }
+            }
+            nextKeyPairSet = false;
+            return nextKeyPair;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("loadKeys(files) Iterator#remove() N/A");
+        }
+
+        @SuppressWarnings("synthetic-access")
+        private boolean setNextObject() {
+            boolean debugEnabled = log.isDebugEnabled();
+            while (iterator.hasNext()) {
+                R r = iterator.next();
+                try {
+                    nextKeyPair = doLoadKey(r);
+                } catch (Throwable e) {
+                    log.warn("Failed (" + e.getClass().getSimpleName() + ")"
+                           + " to load key resource=" + r + ": " + e.getMessage());
+                    if (debugEnabled) {
+                        log.debug("Key resource=" + r + " load failure details", e);
+                    }
+                    nextKeyPair = null;
+                    continue;
+                }
+
+                if (nextKeyPair != null) {
+                    nextKeyPairSet = true;
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/ClassLoadableResourceKeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/ClassLoadableResourceKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/ClassLoadableResourceKeyPairProvider.java
new file mode 100644
index 0000000..6e8da54
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/ClassLoadableResourceKeyPairProvider.java
@@ -0,0 +1,113 @@
+/*
+ * 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.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+
+/**
+ * This provider loads private keys from the specified resources that
+ * are accessible via {@link ClassLoader#getResourceAsStream(String)}.
+ * If no loader configured via {@link #setResourceLoader(ClassLoader)}, then
+ * {@link ThreadUtils#resolveDefaultClassLoader(Class)} is used
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ClassLoadableResourceKeyPairProvider extends AbstractResourceKeyPairProvider<String> {
+    private ClassLoader classLoader;
+    private Collection<String> resources;
+
+    public ClassLoadableResourceKeyPairProvider() {
+        this(Collections.emptyList());
+    }
+
+    public ClassLoadableResourceKeyPairProvider(ClassLoader cl) {
+        this(cl, Collections.emptyList());
+    }
+
+    public ClassLoadableResourceKeyPairProvider(String res) {
+        this(Collections.singletonList(ValidateUtils.checkNotNullAndNotEmpty(res, "No resource specified")));
+    }
+
+    public ClassLoadableResourceKeyPairProvider(ClassLoader cl, String res) {
+        this(cl, Collections.singletonList(ValidateUtils.checkNotNullAndNotEmpty(res, "No resource specified")));
+    }
+
+    public ClassLoadableResourceKeyPairProvider(Collection<String> resources) {
+        this.classLoader = ThreadUtils.resolveDefaultClassLoader(getClass());
+        this.resources = (resources == null) ? Collections.emptyList() : resources;
+    }
+
+    public ClassLoadableResourceKeyPairProvider(ClassLoader cl, Collection<String> resources) {
+        this.classLoader = cl;
+        this.resources = (resources == null) ? Collections.emptyList() : resources;
+    }
+
+    public Collection<String> getResources() {
+        return resources;
+    }
+
+    public void setResources(Collection<String> resources) {
+        this.resources = (resources == null) ? Collections.emptyList() : resources;
+    }
+
+    public ClassLoader getResourceLoader() {
+        return classLoader;
+    }
+
+    public void setResourceLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    @Override
+    public Iterable<KeyPair> loadKeys() {
+        return loadKeys(getResources());
+    }
+
+    @Override
+    protected InputStream openKeyPairResource(String resourceKey, String resource) throws IOException {
+        ClassLoader cl = resolveClassLoader();
+        if (cl == null) {
+            throw new StreamCorruptedException("No resource loader for " + resource);
+        }
+
+        InputStream input = cl.getResourceAsStream(resource);
+        if (input == null) {
+            throw new FileNotFoundException("Cannot find resource " + resource);
+        }
+
+        return input;
+    }
+
+    protected ClassLoader resolveClassLoader() {
+        ClassLoader cl = getResourceLoader();
+        if (cl == null) {
+            cl = ThreadUtils.resolveDefaultClassLoader(getClass());
+        }
+        return cl;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java
new file mode 100644
index 0000000..c4aae97
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * This host key provider loads private keys from the specified files. The
+ * loading is <U>lazy</U> - i.e., a file is not loaded until it is actually
+ * required. Once required though, its loaded {@link KeyPair} result is
+ * <U>cached</U> and not re-loaded.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FileKeyPairProvider extends AbstractResourceKeyPairProvider<Path> {
+    private Collection<? extends Path> files;
+
+    public FileKeyPairProvider() {
+        super();
+    }
+
+    public FileKeyPairProvider(Path path) {
+        this(Collections.singletonList(Objects.requireNonNull(path, "No path provided")));
+    }
+
+    public FileKeyPairProvider(Path... files) {
+        this(Arrays.asList(files));
+    }
+
+    public FileKeyPairProvider(Collection<? extends Path> files) {
+        this.files = files;
+    }
+
+    public Collection<? extends Path> getPaths() {
+        return files;
+    }
+
+    public void setFiles(Collection<File> files) {
+        setPaths(GenericUtils.map(files, File::toPath));
+    }
+
+    public void setPaths(Collection<? extends Path> paths) {
+        // use absolute path in order to have unique cache keys
+        Collection<Path> resolved = GenericUtils.map(paths, Path::toAbsolutePath);
+        resetCacheMap(resolved);
+        files = resolved;
+    }
+
+    @Override
+    public Iterable<KeyPair> loadKeys() {
+        return loadKeys(getPaths());
+    }
+
+    @Override
+    protected KeyPair doLoadKey(Path resource) throws IOException, GeneralSecurityException {
+        return super.doLoadKey((resource == null) ? null : resource.toAbsolutePath());
+    }
+
+    @Override
+    protected InputStream openKeyPairResource(String resourceKey, Path resource) throws IOException {
+        return Files.newInputStream(resource, IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
new file mode 100644
index 0000000..d0b35d9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
@@ -0,0 +1,170 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface KeyIdentityProvider {
+    /**
+     * An &quot;empty&quot; implementation of {@link KeyIdentityProvider} that
+     * returns an empty group of key pairs
+     */
+    KeyIdentityProvider EMPTY_KEYS_PROVIDER = new KeyIdentityProvider() {
+        @Override
+        public Iterable<KeyPair> loadKeys() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
+     * Invokes {@link KeyIdentityProvider#loadKeys()} and returns the result - ignores
+     * {@code null} providers (i.e., returns an empty iterable instance)
+     */
+    Function<KeyIdentityProvider, Iterable<KeyPair>> LOADER = p ->
+            (p == null) ? Collections.<KeyPair>emptyList() : p.loadKeys();
+
+    /**
+     * Load available keys.
+     *
+     * @return an {@link Iterable} instance of available keys - ignored if {@code null}
+     */
+    Iterable<KeyPair> loadKeys();
+
+    /**
+     * Creates a &quot;unified&quot; {@link Iterator} of {@link KeyPair}s out of 2 possible
+     * {@link KeyIdentityProvider}
+     *
+     * @param identities The registered keys identities
+     * @param keys Extra available key pairs
+     * @return The wrapping iterator
+     * @see #resolveKeyIdentityProvider(KeyIdentityProvider, KeyIdentityProvider)
+     */
+    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
+     */
+    static Iterator<KeyPair> iteratorOf(KeyIdentityProvider provider) {
+        return GenericUtils.iteratorOf((provider == null) ? null : provider.loadKeys());
+    }
+
+    /**
+     * <P>Creates a &quot;unified&quot; {@link KeyIdentityProvider} out of 2 possible ones
+     * as follows:</P></BR>
+     * <UL>
+     *      <LI>If both are {@code null} then return {@code null}.</LI>
+     *      <LI>If either one is {@code null} then use the non-{@code null} one.</LI>
+     *      <LI>If both are the same instance then use it.</U>
+     *      <LI>Otherwise, returns a wrapper that groups both providers.</LI>
+     * </UL>
+     * @param identities The registered key pair identities
+     * @param keys The extra available key pairs
+     * @return The resolved provider
+     * @see #multiProvider(KeyIdentityProvider...)
+     */
+    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);
+        }
+    }
+
+    /**
+     * Wraps a group of {@link KeyIdentityProvider} into a single one
+     *
+     * @param providers The providers - ignored if {@code null}/empty (i.e., returns
+     * {@link #EMPTY_KEYS_PROVIDER})
+     * @return The wrapping provider
+     * @see #multiProvider(Collection)
+     */
+    static KeyIdentityProvider multiProvider(KeyIdentityProvider... providers) {
+        return multiProvider(GenericUtils.asList(providers));
+    }
+
+    /**
+     * Wraps a group of {@link KeyIdentityProvider} into a single one
+     *
+     * @param providers The providers - ignored if {@code null}/empty (i.e., returns
+     * {@link #EMPTY_KEYS_PROVIDER})
+     * @return The wrapping provider
+     */
+    static KeyIdentityProvider multiProvider(Collection<? extends KeyIdentityProvider> providers) {
+        return GenericUtils.isEmpty(providers) ? EMPTY_KEYS_PROVIDER : wrapKeyPairs(iterableOf(providers));
+    }
+
+    /**
+     * Wraps a group of {@link KeyIdentityProvider} into an {@link Iterable} of {@link KeyPair}s
+     *
+     * @param providers The group of providers - ignored if {@code null}/empty (i.e., returns an
+     * empty iterable instance)
+     * @return The wrapping iterable
+     */
+    static Iterable<KeyPair> iterableOf(Collection<? extends KeyIdentityProvider> providers) {
+        Iterable<Supplier<Iterable<KeyPair>>> keysSuppliers =
+                GenericUtils.<KeyIdentityProvider, Supplier<Iterable<KeyPair>>>wrapIterable(providers, p -> p::loadKeys);
+        return GenericUtils.multiIterableSuppliers(keysSuppliers);
+    }
+
+    /**
+     * Wraps a group of {@link KeyPair}s into a {@link KeyIdentityProvider}
+     *
+     * @param pairs The key pairs - ignored if {@code null}/empty (i.e., returns
+     * {@link #EMPTY_KEYS_PROVIDER}).
+     * @return The provider wrapper
+     */
+    static KeyIdentityProvider wrapKeyPairs(KeyPair... pairs) {
+        return wrapKeyPairs(GenericUtils.asList(pairs));
+    }
+
+    /**
+     * Wraps a group of {@link KeyPair}s into a {@link KeyIdentityProvider}
+     *
+     * @param pairs The key pairs {@link Iterable} - ignored if {@code null} (i.e., returns
+     * {@link #EMPTY_KEYS_PROVIDER}).
+     * @return The provider wrapper
+     */
+    static KeyIdentityProvider wrapKeyPairs(Iterable<KeyPair> pairs) {
+        return (pairs == null) ? EMPTY_KEYS_PROVIDER : () -> pairs;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
new file mode 100644
index 0000000..a816304
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
@@ -0,0 +1,181 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Provider for key pairs.  This provider is used on the server side to provide
+ * the host key, or on the client side to provide the user key.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyPairProvider extends KeyIdentityProvider {
+
+    /**
+     * SSH identifier for RSA keys
+     */
+    String SSH_RSA = "ssh-rsa";
+
+    /**
+     * SSH identifier for DSA keys
+     */
+    String SSH_DSS = "ssh-dss";
+
+    /**
+     * SSH identifier for ED25519 elliptic curve keys
+     */
+    String SSH_ED25519 = "ssh-ed25519";
+
+    /**
+     * SSH identifier for EC keys in NIST curve P-256
+     */
+    String ECDSA_SHA2_NISTP256 = ECCurves.nistp256.getKeyType();
+
+    /**
+     * SSH identifier for EC keys in NIST curve P-384
+     */
+    String ECDSA_SHA2_NISTP384 = ECCurves.nistp384.getKeyType();
+
+    /**
+     * SSH identifier for EC keys in NIST curve P-521
+     */
+    String ECDSA_SHA2_NISTP521 = ECCurves.nistp521.getKeyType();
+
+    /**
+     * A {@link KeyPairProvider} that has no keys
+     */
+    KeyPairProvider EMPTY_KEYPAIR_PROVIDER =
+        new KeyPairProvider() {
+            @Override
+            public KeyPair loadKey(String type) {
+                return null;
+            }
+
+            @Override
+            public Iterable<String> getKeyTypes() {
+                return Collections.emptyList();
+            }
+
+            @Override
+            public Iterable<KeyPair> loadKeys() {
+                return Collections.emptyList();
+            }
+
+            @Override
+            public String toString() {
+                return "EMPTY_KEYPAIR_PROVIDER";
+            }
+        };
+
+    /**
+     * Load a key of the specified type which can be &quot;ssh-rsa&quot;, &quot;ssh-dss&quot;,
+     * or &quot;ecdsa-sha2-nistp{256,384,521}&quot;. If there is no key of this type, return
+     * {@code null}
+     *
+     * @param type the type of key to load
+     * @return a valid key pair or {@code null} if this type of key is not available
+     */
+    default KeyPair loadKey(String type) {
+        ValidateUtils.checkNotNullAndNotEmpty(type, "No key type to load");
+        return GenericUtils.stream(loadKeys())
+                .filter(key -> type.equals(KeyUtils.getKeyType(key)))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * @return The available {@link Iterable} key types in preferred order - never {@code null}
+     */
+    default Iterable<String> getKeyTypes() {
+        return GenericUtils.stream(loadKeys())
+                .map(KeyUtils::getKeyType)
+                .filter(GenericUtils::isNotEmpty)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Wrap the provided {@link KeyPair}s into a {@link KeyPairProvider}
+     *
+     * @param pairs The available pairs - ignored if {@code null}/empty (i.e.,
+     * returns {@link #EMPTY_KEYPAIR_PROVIDER})
+     * @return The provider wrapper
+     * @see #wrap(Iterable)
+     */
+    static KeyPairProvider wrap(KeyPair... pairs) {
+        return GenericUtils.isEmpty(pairs) ? EMPTY_KEYPAIR_PROVIDER : wrap(Arrays.asList(pairs));
+    }
+
+    /**
+     * Wrap the provided {@link KeyPair}s into a {@link KeyPairProvider}
+     *
+     * @param pairs The available pairs {@link Iterable} - ignored if {@code null} (i.e.,
+     * returns {@link #EMPTY_KEYPAIR_PROVIDER})
+     * @return The provider wrapper
+     */
+    static KeyPairProvider wrap(Iterable<KeyPair> pairs) {
+        return (pairs == null) ? EMPTY_KEYPAIR_PROVIDER : new KeyPairProvider() {
+            @Override
+            public Iterable<KeyPair> loadKeys() {
+                return pairs;
+            }
+
+            @Override
+            public KeyPair loadKey(String type) {
+                for (KeyPair kp : pairs) {
+                    String t = KeyUtils.getKeyType(kp);
+                    if (Objects.equals(type, t)) {
+                        return kp;
+                    }
+                }
+
+                return null;
+            }
+
+            @Override
+            public Iterable<String> getKeyTypes() {
+                // use a LinkedHashSet so as to preserve the order but avoid duplicates
+                Collection<String> types = new LinkedHashSet<>();
+                for (KeyPair kp : pairs) {
+                    String t = KeyUtils.getKeyType(kp);
+                    if (GenericUtils.isEmpty(t)) {
+                        continue;   // avoid unknown key types
+                    }
+
+                    if (!types.add(t)) {
+                        continue;   // debug breakpoint
+                    }
+                }
+
+                return types;
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProviderHolder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProviderHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProviderHolder.java
new file mode 100644
index 0000000..553d553
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProviderHolder.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface KeyPairProviderHolder {
+    /**
+     * Retrieve the <code>KeyPairProvider</code> that will be used to find
+     * the host key to use on the server side or the user key on the client side.
+     *
+     * @return the <code>KeyPairProvider</code>, never {@code null}
+     */
+    KeyPairProvider getKeyPairProvider();
+
+    void setKeyPairProvider(KeyPairProvider keyPairProvider);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MappedKeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MappedKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MappedKeyPairProvider.java
new file mode 100644
index 0000000..bf3ec8b
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MappedKeyPairProvider.java
@@ -0,0 +1,97 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Holds a {@link Map} of {@link String}-&gt;{@link KeyPair} where the map key
+ * is the type and value is the associated {@link KeyPair}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MappedKeyPairProvider implements KeyPairProvider {
+    /**
+     * Transforms a {@link Map} of {@link String}-&gt;{@link KeyPair} to a
+     * {@link KeyPairProvider} where map key is the type and value is the
+     * associated {@link KeyPair}
+     */
+    public static final Function<Map<String, KeyPair>, KeyPairProvider> MAP_TO_KEY_PAIR_PROVIDER =
+            MappedKeyPairProvider::new;
+
+    private final Map<String, KeyPair> pairsMap;
+
+    public MappedKeyPairProvider(KeyPair... pairs) {
+        this(GenericUtils.isEmpty(pairs) ? Collections.emptyList() : Arrays.asList(pairs));
+    }
+
+    public MappedKeyPairProvider(Collection<? extends KeyPair> pairs) {
+        this(mapUniquePairs(pairs));
+    }
+
+    public MappedKeyPairProvider(Map<String, KeyPair> pairsMap) {
+        this.pairsMap = ValidateUtils.checkNotNullAndNotEmpty(pairsMap, "No pairs map provided");
+    }
+
+    @Override
+    public Iterable<KeyPair> loadKeys() {
+        return pairsMap.values();
+    }
+
+    @Override
+    public KeyPair loadKey(String type) {
+        return pairsMap.get(type);
+    }
+
+    @Override
+    public Iterable<String> getKeyTypes() {
+        return pairsMap.keySet();
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(getKeyTypes());
+    }
+
+    public static Map<String, KeyPair> mapUniquePairs(Collection<? extends KeyPair> pairs) {
+        if (GenericUtils.isEmpty(pairs)) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, KeyPair> pairsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        for (KeyPair kp : pairs) {
+            String keyType = ValidateUtils.checkNotNullAndNotEmpty(KeyUtils.getKeyType(kp), "Cannot determine key type");
+            KeyPair prev = pairsMap.put(keyType, kp);
+            ValidateUtils.checkTrue(prev == null, "Multiple keys of type=%s", keyType);
+        }
+
+        return pairsMap;
+    }
+}