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 "empty" 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 "unified" {@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 "unified" {@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 "ssh-rsa", "ssh-dss",
+ * or "ecdsa-sha2-nistp{256,384,521}". 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}->{@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}->{@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;
+ }
+}