http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java
new file mode 100644
index 0000000..793965c
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.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.util.security.eddsa;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
+import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public final class Ed25519PublicKeyDecoder extends 
AbstractPublicKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
+    public static final Ed25519PublicKeyDecoder INSTANCE = new 
Ed25519PublicKeyDecoder();
+
+    private Ed25519PublicKeyDecoder() {
+        super(EdDSAPublicKey.class, EdDSAPrivateKey.class, 
Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_ED25519)));
+    }
+
+    @Override
+    public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws 
GeneralSecurityException {
+        if (key == null) {
+            return null;
+        } else {
+            return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), 
key.getParams()));
+        }
+    }
+
+    @Override
+    public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws 
GeneralSecurityException {
+        if (key == null) {
+            return null;
+        } else {
+            return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), 
key.getParams()));
+        }
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws 
GeneralSecurityException {
+        return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+    }
+
+    @Override
+    public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws 
IOException {
+        Objects.requireNonNull(key, "No public key provided");
+        KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519);
+        byte[] seed = getSeedValue(key);
+        KeyEntryResolver.writeRLEBytes(s, seed);
+        return KeyPairProvider.SSH_ED25519;
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+    }
+
+    @Override
+    public EdDSAPublicKey decodePublicKey(String keyType, InputStream keyData) 
throws IOException, GeneralSecurityException {
+        byte[] seed = KeyEntryResolver.readRLEBytes(keyData);
+        return 
EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed));
+    }
+
+    public static byte[] getSeedValue(EdDSAPublicKey key) {
+        // a bit of reverse-engineering on the EdDSAPublicKeySpec
+        return (key == null) ? null : key.getAbyte();
+    }
+}
\ 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/util/security/eddsa/EdDSASecurityProviderRegistrar.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
new file mode 100644
index 0000000..61f16e9
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
@@ -0,0 +1,104 @@
+/*
+ * 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.util.security.eddsa;
+
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.Signature;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ReflectionUtils;
+import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class EdDSASecurityProviderRegistrar extends 
AbstractSecurityProviderRegistrar {
+    public static final String PROVIDER_CLASS = 
"net.i2p.crypto.eddsa.EdDSASecurityProvider";
+    // Do not define a static registrar instance to minimize class loading 
issues
+    private final AtomicReference<Boolean> supportHolder = new 
AtomicReference<>(null);
+
+    public EdDSASecurityProviderRegistrar() {
+        super(SecurityUtils.EDDSA);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        if (!super.isEnabled()) {
+            return false;
+        }
+
+        // For backward compatibility
+        return this.getBooleanProperty(SecurityUtils.EDDSA_SUPPORTED_PROP, 
true);
+    }
+
+    @Override
+    public Provider getSecurityProvider() {
+        try {
+            return getOrCreateProvider(PROVIDER_CLASS);
+        } catch (ReflectiveOperationException t) {
+            Throwable e = GenericUtils.peelException(t);
+            log.error("getSecurityProvider({}) failed ({}) to instantiate {}: 
{}",
+                      getName(), e.getClass().getSimpleName(), PROVIDER_CLASS, 
e.getMessage());
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isSecurityEntitySupported(Class<?> entityType, String name) 
{
+        if (!isSupported()) {
+            return false;
+        }
+
+        if (KeyPairGenerator.class.isAssignableFrom(entityType)
+                || KeyFactory.class.isAssignableFrom(entityType)) {
+            return Objects.compare(name, getName(), 
String.CASE_INSENSITIVE_ORDER) == 0;
+        } else if (Signature.class.isAssignableFrom(entityType)) {
+            return Objects.compare(SecurityUtils.CURVE_ED25519_SHA512, name, 
String.CASE_INSENSITIVE_ORDER) == 0;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isSupported() {
+        Boolean supported;
+        synchronized (supportHolder) {
+            supported = supportHolder.get();
+            if (supported != null) {
+                return supported.booleanValue();
+            }
+
+            ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass());
+            supported = ReflectionUtils.isClassAvailable(cl, 
"net.i2p.crypto.eddsa.EdDSAKey");
+            supportHolder.set(supported);
+        }
+
+        return supported.booleanValue();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
new file mode 100644
index 0000000..242f550
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.security.eddsa;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+import net.i2p.crypto.eddsa.EdDSAEngine;
+import net.i2p.crypto.eddsa.EdDSAKey;
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
+import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public final class EdDSASecurityProviderUtils {
+    // See EdDSANamedCurveTable
+    public static final String CURVE_ED25519_SHA512 = "Ed25519";
+
+    private EdDSASecurityProviderUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static Class<? extends PublicKey> getEDDSAPublicKeyType() {
+        return EdDSAPublicKey.class;
+    }
+
+    public static Class<? extends PrivateKey> getEDDSAPrivateKeyType() {
+        return EdDSAPrivateKey.class;
+    }
+
+    public static int getEDDSAKeySize(Key key) {
+        return (SecurityUtils.isEDDSACurveSupported() && (key instanceof 
EdDSAKey)) ? 256 : -1;
+    }
+
+    public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            return false;
+        }
+
+        if ((k1 instanceof EdDSAPublicKey) && (k2 instanceof EdDSAPublicKey)) {
+            if (Objects.equals(k1, k2)) {
+                return true;
+            } else if (k1 == null || k2 == null) {
+                return false;   // both null is covered by Objects#equals
+            }
+
+            EdDSAPublicKey ed1 = (EdDSAPublicKey) k1;
+            EdDSAPublicKey ed2 = (EdDSAPublicKey) k2;
+            return Arrays.equals(ed1.getAbyte(), ed2.getAbyte())
+                && compareEDDSAKeyParams(ed1.getParams(), ed2.getParams());
+        }
+
+        return false;
+    }
+
+    public static boolean isEDDSASignatureAlgorithm(String algorithm) {
+        return EdDSAEngine.SIGNATURE_ALGORITHM.equalsIgnoreCase(algorithm);
+    }
+
+    public static EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws 
GeneralSecurityException {
+        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), 
SecurityUtils.EDDSA + " not supported");
+        if (!(key instanceof EdDSAPrivateKey)) {
+            throw new InvalidKeyException("Private key is not " + 
SecurityUtils.EDDSA);
+        }
+
+        EdDSAPrivateKey prvKey = (EdDSAPrivateKey) key;
+        EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getAbyte(), 
prvKey.getParams());
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec));
+    }
+
+    public static org.apache.sshd.common.signature.Signature 
getEDDSASignature() {
+        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), 
SecurityUtils.EDDSA + " not supported");
+        return new SignatureEd25519();
+    }
+
+    public static boolean isEDDSAKeyFactoryAlgorithm(String algorithm) {
+        return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
+    }
+
+    public static boolean isEDDSAKeyPairGeneratorAlgorithm(String algorithm) {
+        return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
+    }
+
+    public static PublicKeyEntryDecoder<? extends PublicKey, ? extends 
PrivateKey> getEDDSAPublicKeyEntryDecoder() {
+        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), 
SecurityUtils.EDDSA + " not supported");
+        return Ed25519PublicKeyDecoder.INSTANCE;
+    }
+
+    public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends 
PrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
+        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), 
SecurityUtils.EDDSA + " not supported");
+        return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
+    }
+
+    public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey 
k2) {
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            return false;
+        }
+
+        if ((k1 instanceof EdDSAPrivateKey) && (k2 instanceof 
EdDSAPrivateKey)) {
+            if (Objects.equals(k1, k2)) {
+                return true;
+            } else if (k1 == null || k2 == null) {
+                return false;   // both null is covered by Objects#equals
+            }
+
+            EdDSAPrivateKey ed1 = (EdDSAPrivateKey) k1;
+            EdDSAPrivateKey ed2 = (EdDSAPrivateKey) k2;
+            return Arrays.equals(ed1.getSeed(), ed2.getSeed())
+                && compareEDDSAKeyParams(ed1.getParams(), ed2.getParams());
+        }
+
+        return false;
+    }
+
+    public static boolean compareEDDSAKeyParams(EdDSAParameterSpec s1, 
EdDSAParameterSpec s2) {
+        if (Objects.equals(s1, s2)) {
+            return true;
+        } else if (s1 == null || s2 == null) {
+            return false;   // both null is covered by Objects#equals
+        } else {
+            return Objects.equals(s1.getHashAlgorithm(), s2.getHashAlgorithm())
+                && Objects.equals(s1.getCurve(), s2.getCurve())
+                && Objects.equals(s1.getB(), s2.getB());
+        }
+    }
+
+    public static PublicKey generateEDDSAPublicKey(byte[] seed) throws 
GeneralSecurityException {
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not 
supported");
+        }
+
+        EdDSAParameterSpec params = 
EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
+        EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(seed, params);
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        return factory.generatePublic(keySpec);
+    }
+
+    public static PrivateKey generateEDDSAPrivateKey(byte[] seed) throws 
GeneralSecurityException {
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not 
supported");
+        }
+
+        EdDSAParameterSpec params = 
EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
+        EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
+        KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+        return factory.generatePrivate(keySpec);
+    }
+
+    public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, 
PublicKey key) {
+        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), 
SecurityUtils.EDDSA + " not supported");
+        EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, 
EdDSAPublicKey.class, "Not an EDDSA public key: %s", key);
+        byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(edKey);
+        ValidateUtils.checkNotNull(seed, "No seed extracted from key: %s", 
edKey.getA());
+        buffer.putString(KeyPairProvider.SSH_ED25519);
+        buffer.putBytes(seed);
+        return buffer;
+    }
+
+    public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey 
pubKey, PrivateKey prvKey) {
+        ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), 
SecurityUtils.EDDSA + " not supported");
+        ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an 
EDDSA public key: %s", pubKey);
+        ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an 
EDDSA private key: %s", prvKey);
+        throw new UnsupportedOperationException("Full SSHD-440 implementation 
N/A");
+    }
+}
\ 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/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
new file mode 100644
index 0000000..4888818
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -0,0 +1,172 @@
+/*
+ * 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.util.security.eddsa;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
+import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class OpenSSHEd25519PrivateKeyEntryDecoder extends 
AbstractPrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
+    public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new 
OpenSSHEd25519PrivateKeyEntryDecoder();
+    private static final int PK_SIZE = 32;
+    private static final int SK_SIZE = 32;
+    private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE;
+
+    public OpenSSHEd25519PrivateKeyEntryDecoder() {
+        super(EdDSAPublicKey.class, EdDSAPrivateKey.class, 
Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_ED25519)));
+    }
+
+    @Override
+    public EdDSAPrivateKey decodePrivateKey(String keyType, 
FilePasswordProvider passwordProvider, InputStream keyData)
+            throws IOException, GeneralSecurityException {
+        if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
+            throw new InvalidKeyException("Unsupported key type: " + keyType);
+        }
+
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " 
provider not supported");
+        }
+
+        // ed25519 bernstein naming: pk .. public key, sk .. secret key
+        // we expect to find two byte arrays with the following structure 
(type:size):
+        // [pk:32], [sk:32,pk:32]
+
+        byte[] pk = KeyEntryResolver.readRLEBytes(keyData);
+        byte[] keypair = KeyEntryResolver.readRLEBytes(keyData);
+
+        if (pk.length != PK_SIZE) {
+            throw new InvalidKeyException(String.format(Locale.ENGLISH, 
"Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE));
+        }
+
+        if (keypair.length != KEYPAIR_SIZE) {
+            throw new InvalidKeyException(String.format(Locale.ENGLISH, 
"Unexpected keypair size: %s (expected %s)", keypair.length, KEYPAIR_SIZE));
+        }
+
+        byte[] sk = Arrays.copyOf(keypair, SK_SIZE);
+
+        // verify that the keypair contains the expected pk
+        // yes, it's stored redundant, this seems to mimic the output 
structure of the keypair generation interface
+        if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, 
KEYPAIR_SIZE))) {
+            throw new InvalidKeyException("Keypair did not contain the public 
key.");
+        }
+
+        // create the private key
+        EdDSAParameterSpec params = 
EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512);
+        EdDSAPrivateKey privateKey = generatePrivateKey(new 
EdDSAPrivateKeySpec(sk, params));
+
+        // the private key class contains the calculated public key (Abyte)
+        // pointers to the corresponding code:
+        // EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], 
EdDSAParameterSpec): A = spec.getB().scalarMultiply(a);
+        // EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = 
this.A.toByteArray();
+
+        // we can now verify the generated pk matches the one we read
+        if (!Arrays.equals(privateKey.getAbyte(), pk)) {
+            throw new InvalidKeyException("The provided pk does NOT match the 
computed pk for the given sk.");
+        }
+
+        return privateKey;
+    }
+
+    @Override
+    public String encodePrivateKey(OutputStream s, EdDSAPrivateKey key) throws 
IOException {
+        Objects.requireNonNull(key, "No private key provided");
+
+        // ed25519 bernstein naming: pk .. public key, sk .. secret key
+        // we are expected to write the following arrays (type:size):
+        // [pk:32], [sk:32,pk:32]
+
+        byte[] sk = key.getSeed();
+        byte[] pk = key.getAbyte();
+
+        Objects.requireNonNull(sk, "No seed");
+
+        byte[] keypair = new byte[KEYPAIR_SIZE];
+        System.arraycopy(sk, 0, keypair, 0, SK_SIZE);
+        System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE);
+
+        KeyEntryResolver.writeRLEBytes(s, pk);
+        KeyEntryResolver.writeRLEBytes(s, keypair);
+
+        return KeyPairProvider.SSH_ED25519;
+    }
+
+    @Override
+    public boolean isPublicKeyRecoverySupported() {
+        return true;
+    }
+
+    @Override
+    public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws 
GeneralSecurityException {
+        return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(prvKey);
+    }
+
+    @Override
+    public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws 
GeneralSecurityException {
+        if (key == null) {
+            return null;
+        } else {
+            return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), 
key.getParams()));
+        }
+    }
+
+    @Override
+    public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws 
GeneralSecurityException {
+        if (key == null) {
+            return null;
+        } else {
+            return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), 
key.getParams()));
+        }
+    }
+
+    @Override
+    public KeyPairGenerator getKeyPairGenerator() throws 
GeneralSecurityException {
+        return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
new file mode 100644
index 0000000..012be95
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
@@ -0,0 +1,49 @@
+/*
+ * 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.util.security.eddsa;
+
+import java.util.Map;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.signature.AbstractSignature;
+import org.apache.sshd.common.util.ValidateUtils;
+
+import net.i2p.crypto.eddsa.EdDSAEngine;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class SignatureEd25519 extends AbstractSignature {
+    public SignatureEd25519() {
+        super(EdDSAEngine.SIGNATURE_ALGORITHM);
+    }
+
+    @Override
+    public boolean verify(byte[] sig) throws Exception {
+        byte[] data = sig;
+        Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+        if (encoding != null) {
+            String keyType = encoding.getKey();
+            
ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), 
"Mismatched key type: %s", keyType);
+            data = encoding.getValue();
+        }
+
+        return doVerify(data);
+    }
+}
\ 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/util/threads/CloseableExecutorService.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/threads/CloseableExecutorService.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/CloseableExecutorService.java
new file mode 100644
index 0000000..3b9beeb
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/CloseableExecutorService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.util.threads;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.common.Closeable;
+
+public interface CloseableExecutorService extends ExecutorService, Closeable {
+    // Nothing extra
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ExecutorServiceCarrier.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ExecutorServiceCarrier.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ExecutorServiceCarrier.java
new file mode 100644
index 0000000..b44bd46
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ExecutorServiceCarrier.java
@@ -0,0 +1,31 @@
+/*
+ * 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.util.threads;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public interface ExecutorServiceCarrier {
+    /**
+     * @return The {@link CloseableExecutorService} to use
+     */
+    CloseableExecutorService getExecutorService();
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/threads/NoCloseExecutor.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/threads/NoCloseExecutor.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/NoCloseExecutor.java
new file mode 100644
index 0000000..cb42805
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/NoCloseExecutor.java
@@ -0,0 +1,160 @@
+/*
+ * 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.util.threads;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Wraps an {@link ExecutorService} as a {@link CloseableExecutorService}
+ * and avoids calling its {@code shutdown} methods when the wrapper is shut 
down
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class NoCloseExecutor implements CloseableExecutorService {
+    protected final ExecutorService executor;
+    protected final CloseFuture closeFuture;
+
+    public NoCloseExecutor(ExecutorService executor) {
+        this.executor = executor;
+        closeFuture = new DefaultCloseFuture(null, null);
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.submit(task);
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.submit(task, result);
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.submit(task);
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> 
tasks)
+            throws InterruptedException {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.invokeAll(tasks);
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> 
tasks, long timeout, TimeUnit unit)
+            throws InterruptedException {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.invokeAll(tasks, timeout, unit);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+            throws InterruptedException, ExecutionException {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.invokeAny(tasks);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long 
timeout, TimeUnit unit)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        return executor.invokeAny(tasks, timeout, unit);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        ValidateUtils.checkState(!isShutdown(), "Executor has been shut down");
+        executor.execute(command);
+    }
+
+    @Override
+    public void shutdown() {
+        close(true);
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+        close(true);
+        return Collections.emptyList();
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return isClosed();
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return isClosed();
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws 
InterruptedException {
+        try {
+            return closeFuture.await(timeout, unit);
+        } catch (IOException e) {
+            throw (InterruptedException) new 
InterruptedException().initCause(e);
+        }
+    }
+
+    @Override
+    public CloseFuture close(boolean immediately) {
+        closeFuture.setClosed();
+        return closeFuture;
+    }
+
+    @Override
+    public void addCloseFutureListener(SshFutureListener<CloseFuture> 
listener) {
+        closeFuture.addListener(listener);
+    }
+
+    @Override
+    public void removeCloseFutureListener(SshFutureListener<CloseFuture> 
listener) {
+        closeFuture.removeListener(listener);
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closeFuture.isClosed();
+    }
+
+    @Override
+    public boolean isClosing() {
+        return isClosed();
+    }
+
+}
\ 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/util/threads/SshThreadPoolExecutor.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/threads/SshThreadPoolExecutor.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/SshThreadPoolExecutor.java
new file mode 100644
index 0000000..ccaa655
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/SshThreadPoolExecutor.java
@@ -0,0 +1,138 @@
+/*
+ * 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.util.threads;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.util.closeable.AbstractCloseable;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class SshThreadPoolExecutor extends ThreadPoolExecutor implements 
CloseableExecutorService {
+    protected final DelegateCloseable closeable = new DelegateCloseable();
+
+    protected class DelegateCloseable extends AbstractCloseable {
+        protected DelegateCloseable() {
+            super();
+        }
+
+        @Override
+        protected CloseFuture doCloseGracefully() {
+            shutdown();
+            return closeFuture;
+        }
+
+        @Override
+        protected void doCloseImmediately() {
+            shutdownNow();
+            super.doCloseImmediately();
+        }
+
+        protected void setClosed() {
+            closeFuture.setClosed();
+        }
+    }
+
+    public SshThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long 
keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+    }
+
+    public SshThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long 
keepAliveTime, TimeUnit unit,
+            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
threadFactory);
+    }
+
+    public SshThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long 
keepAliveTime, TimeUnit unit,
+            BlockingQueue<Runnable> workQueue, RejectedExecutionHandler 
handler) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
handler);
+    }
+
+    public SshThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long 
keepAliveTime, TimeUnit unit,
+            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, 
RejectedExecutionHandler handler) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
threadFactory, handler);
+    }
+
+    @Override
+    protected void terminated() {
+        closeable.doCloseImmediately();
+    }
+
+    @Override
+    public void shutdown() {
+        super.shutdown();
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+        return super.shutdownNow();
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return super.isShutdown();
+    }
+
+    @Override
+    public boolean isTerminating() {
+        return super.isTerminating();
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return super.isTerminated();
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws 
InterruptedException {
+        return super.awaitTermination(timeout, unit);
+    }
+
+    @Override
+    public CloseFuture close(boolean immediately) {
+        return closeable.close(immediately);
+    }
+
+    @Override
+    public void addCloseFutureListener(SshFutureListener<CloseFuture> 
listener) {
+        closeable.addCloseFutureListener(listener);
+    }
+
+    @Override
+    public void removeCloseFutureListener(SshFutureListener<CloseFuture> 
listener) {
+        closeable.removeCloseFutureListener(listener);
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closeable.isClosed();
+    }
+
+    @Override
+    public boolean isClosing() {
+        return closeable.isClosing();
+    }
+}
\ 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/util/threads/SshdThreadFactory.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/threads/SshdThreadFactory.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/SshdThreadFactory.java
new file mode 100644
index 0000000..5dc0c7b
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/SshdThreadFactory.java
@@ -0,0 +1,78 @@
+/*
+ * 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.util.threads;
+
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * Default {@link ThreadFactory} used by {@link ThreadUtils} to create
+ * thread pools if user did provide one
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class SshdThreadFactory extends AbstractLoggingBean implements 
ThreadFactory {
+    private final ThreadGroup group;
+    private final AtomicInteger threadNumber = new AtomicInteger(1);
+    private final String namePrefix;
+
+    public SshdThreadFactory(String name) {
+        SecurityManager s = System.getSecurityManager();
+        group = (s != null) ? s.getThreadGroup() : 
Thread.currentThread().getThreadGroup();
+        String effectiveName = name.replace(' ', '-');
+        namePrefix = "sshd-" + effectiveName + "-thread-";
+    }
+
+    @Override
+    public Thread newThread(Runnable r) {
+        Thread t;
+        try {
+            // see SSHD-668
+            if (System.getSecurityManager() != null) {
+                t = 
AccessController.doPrivileged((PrivilegedExceptionAction<Thread>) () ->
+                        new Thread(group, r, namePrefix + 
threadNumber.getAndIncrement(), 0));
+            } else {
+                t = new Thread(group, r, namePrefix + 
threadNumber.getAndIncrement(), 0);
+            }
+        } catch (PrivilegedActionException e) {
+            Exception err = e.getException();
+            if (err instanceof RuntimeException) {
+                throw (RuntimeException) err;
+            } else {
+                throw new RuntimeException(err);
+            }
+        }
+
+        if (!t.isDaemon()) {
+            t.setDaemon(true);
+        }
+        if (t.getPriority() != Thread.NORM_PRIORITY) {
+            t.setPriority(Thread.NORM_PRIORITY);
+        }
+        if (log.isTraceEnabled()) {
+            log.trace("newThread({})[{}] runnable={}", group, t.getName(), r);
+        }
+        return t;
+    }
+}
\ 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/util/threads/ThreadUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ThreadUtils.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ThreadUtils.java
new file mode 100644
index 0000000..c803389
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/threads/ThreadUtils.java
@@ -0,0 +1,185 @@
+/*
+ * 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.util.threads;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility class for thread pools.
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public final class ThreadUtils {
+    private ThreadUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    /**
+     * Wraps an {@link CloseableExecutorService} in such a way as to 
&quot;protect&quot;
+     * it for calls to the {@link CloseableExecutorService#shutdown()} or
+     * {@link CloseableExecutorService#shutdownNow()}. All other calls are 
delegated as-is
+     * to the original service. <B>Note:</B> the exposed wrapped proxy will
+     * answer correctly the {@link CloseableExecutorService#isShutdown()} 
query if indeed
+     * one of the {@code shutdown} methods was invoked.
+     *
+     * @param executorService The original service - ignored if {@code null}
+     * @param shutdownOnExit  If {@code true} then it is OK to shutdown the 
executor
+     *                        so no wrapping takes place.
+     * @return Either the original service or a wrapped one - depending on the
+     * value of the <tt>shutdownOnExit</tt> parameter
+     */
+    public static CloseableExecutorService 
protectExecutorServiceShutdown(CloseableExecutorService executorService, 
boolean shutdownOnExit) {
+        if (executorService == null || shutdownOnExit || executorService 
instanceof NoCloseExecutor) {
+            return executorService;
+        } else {
+            return new NoCloseExecutor(executorService);
+        }
+    }
+
+    public static CloseableExecutorService noClose(CloseableExecutorService 
executorService) {
+        return protectExecutorServiceShutdown(executorService, false);
+    }
+
+    public static ClassLoader resolveDefaultClassLoader(Object anchor) {
+        return resolveDefaultClassLoader(anchor == null ? null : 
anchor.getClass());
+    }
+
+    public static Iterable<ClassLoader> resolveDefaultClassLoaders(Object 
anchor) {
+        return resolveDefaultClassLoaders(anchor == null ? null : 
anchor.getClass());
+    }
+
+    public static <T> T createDefaultInstance(Class<?> anchor, Class<T> 
targetType, String className)
+            throws ReflectiveOperationException {
+        return createDefaultInstance(resolveDefaultClassLoaders(anchor), 
targetType, className);
+    }
+
+    public static <T> T createDefaultInstance(ClassLoader cl, Class<T> 
targetType, String className)
+            throws ReflectiveOperationException {
+        Class<?> instanceType = cl.loadClass(className);
+        Object instance = instanceType.newInstance();
+        return targetType.cast(instance);
+    }
+
+    public static <T> T createDefaultInstance(Iterable<ClassLoader> cls, 
Class<T> targetType, String className)
+            throws ReflectiveOperationException {
+        for (ClassLoader cl : cls) {
+            try {
+                return createDefaultInstance(cl, targetType, className);
+            } catch (ClassNotFoundException e) {
+                // Ignore
+            }
+        }
+        throw new ClassNotFoundException(className);
+    }
+
+    /**
+     * <P>Attempts to find the most suitable {@link ClassLoader} as 
follows:</P>
+     * <UL>
+     * <LI><P>
+     * Check the {@link Thread#getContextClassLoader()} value
+     * </P></LI>
+     *
+     * <LI><P>
+     * If no thread context class loader then check the anchor
+     * class (if given) for its class loader
+     * </P></LI>
+     *
+     * <LI><P>
+     * If still no loader available, then use {@link 
ClassLoader#getSystemClassLoader()}
+     * </P></LI>
+     * </UL>
+     *
+     * @param anchor The anchor {@link Class} to use if no current thread
+     *               - ignored if {@code null}
+     *               context class loader
+     * @return The resolver {@link ClassLoader}
+     */
+    public static ClassLoader resolveDefaultClassLoader(Class<?> anchor) {
+        Thread thread = Thread.currentThread();
+        ClassLoader cl = thread.getContextClassLoader();
+        if (cl != null) {
+            return cl;
+        }
+
+        if (anchor != null) {
+            cl = anchor.getClassLoader();
+        }
+
+        if (cl == null) {   // can happen for core Java classes
+            cl = ClassLoader.getSystemClassLoader();
+        }
+
+        return cl;
+    }
+
+    public static Iterable<ClassLoader> resolveDefaultClassLoaders(Class<?> 
anchor) {
+        Set<ClassLoader> cls = new LinkedHashSet<>();
+        Thread thread = Thread.currentThread();
+        ClassLoader cl = thread.getContextClassLoader();
+        if (cl != null) {
+            cls.add(cl);
+        }
+        if (anchor != null) {
+            cls.add(anchor.getClassLoader());
+        }
+        cls.add(ClassLoader.getSystemClassLoader());
+        return cls;
+    }
+
+    public static CloseableExecutorService 
newFixedThreadPoolIf(CloseableExecutorService executorService, String poolName, 
int nThreads) {
+        return executorService == null ? newFixedThreadPool(poolName, 
nThreads) : executorService;
+    }
+
+    public static CloseableExecutorService newFixedThreadPool(String poolName, 
int nThreads) {
+        return new SshThreadPoolExecutor(
+                nThreads, nThreads,
+                0L, TimeUnit.MILLISECONDS, // TODO make this configurable
+                new LinkedBlockingQueue<>(),
+                new SshdThreadFactory(poolName),
+                new ThreadPoolExecutor.CallerRunsPolicy());
+    }
+
+    public static CloseableExecutorService 
newCachedThreadPoolIf(CloseableExecutorService executorService, String 
poolName) {
+        return executorService == null ? newCachedThreadPool(poolName) : 
executorService;
+    }
+
+    public static CloseableExecutorService newCachedThreadPool(String 
poolName) {
+        return new SshThreadPoolExecutor(
+                0, Integer.MAX_VALUE, // TODO make this configurable
+                60L, TimeUnit.SECONDS, // TODO make this configurable
+                new SynchronousQueue<>(),
+                new SshdThreadFactory(poolName),
+                new ThreadPoolExecutor.CallerRunsPolicy());
+    }
+
+    public static ScheduledExecutorService 
newSingleThreadScheduledExecutor(String poolName) {
+        return new ScheduledThreadPoolExecutor(1, new 
SshdThreadFactory(poolName));
+    }
+
+    public static CloseableExecutorService newSingleThreadExecutor(String 
poolName) {
+        return newFixedThreadPool(poolName, 1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
 
b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
new file mode 100644
index 0000000..9131f99
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.keyprovider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Holds a <U>single</U> {@link KeyPair} which is generated the 1st time
+ * {@link #loadKeys()} is called. If there is a file backing it up and the
+ * file exists, the key is loaded from it. Otherwise a new key pair is
+ * generated and saved (provided a path is configured and {@link 
#isOverwriteAllowed()}
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractGeneratorHostKeyProvider extends 
AbstractKeyPairProvider {
+    public static final String DEFAULT_ALGORITHM = KeyUtils.RSA_ALGORITHM;
+    public static final boolean DEFAULT_ALLOWED_TO_OVERWRITE = true;
+
+    private final AtomicReference<KeyPair> keyPairHolder = new 
AtomicReference<>();
+
+    private Path path;
+    private String algorithm = DEFAULT_ALGORITHM;
+    private int keySize;
+    private AlgorithmParameterSpec keySpec;
+    private boolean overwriteAllowed = DEFAULT_ALLOWED_TO_OVERWRITE;
+
+    protected AbstractGeneratorHostKeyProvider() {
+        super();
+    }
+
+    public Path getPath() {
+        return path;
+    }
+
+    public void setFile(File file) {
+        setPath((file == null) ? null : file.toPath());
+    }
+
+    public void setPath(Path path) {
+        this.path = (path == null) ? null : path.toAbsolutePath();
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getKeySize() {
+        return keySize;
+    }
+
+    public void setKeySize(int keySize) {
+        this.keySize = keySize;
+    }
+
+    public AlgorithmParameterSpec getKeySpec() {
+        return keySpec;
+    }
+
+    public void setKeySpec(AlgorithmParameterSpec keySpec) {
+        this.keySpec = keySpec;
+    }
+
+    public boolean isOverwriteAllowed() {
+        return overwriteAllowed;
+    }
+
+    public void setOverwriteAllowed(boolean overwriteAllowed) {
+        this.overwriteAllowed = overwriteAllowed;
+    }
+
+    public void clearLoadedKeys() {
+        KeyPair kp;
+        synchronized (keyPairHolder) {
+            kp = keyPairHolder.getAndSet(null);
+        }
+
+        if ((kp != null) & log.isDebugEnabled()) {
+            PublicKey key = kp.getPublic();
+            log.debug("clearLoadedKeys({}) removed key={}-{}",
+                      getPath(), KeyUtils.getKeyType(key), 
KeyUtils.getFingerPrint(key));
+        }
+    }
+
+    @Override   // co-variant return
+    public synchronized List<KeyPair> loadKeys() {
+        Path keyPath = getPath();
+        KeyPair kp;
+        synchronized (keyPairHolder) {
+            kp = keyPairHolder.get();
+            if (kp == null) {
+                try {
+                    kp = resolveKeyPair(keyPath);
+                    if (kp != null) {
+                        keyPairHolder.set(kp);
+                    }
+                } catch (Throwable t) {
+                    log.warn("loadKeys({}) Failed ({}) to resolve: {}",
+                            keyPath, t.getClass().getSimpleName(), 
t.getMessage());
+                    if (log.isDebugEnabled()) {
+                        log.debug("loadKeys(" + keyPath + ") resolution 
failure details", t);
+                    }
+                }
+            }
+        }
+
+        if (kp == null) {
+            return Collections.emptyList();
+        } else {
+            return Collections.singletonList(kp);
+        }
+    }
+
+    protected KeyPair resolveKeyPair(Path keyPath) throws IOException, 
GeneralSecurityException {
+        String alg = getAlgorithm();
+        KeyPair kp;
+        if (keyPath != null) {
+            try {
+                kp = loadFromFile(alg, keyPath);
+                if (kp != null) {
+                    return kp;
+                }
+            } catch (Throwable e) {
+                log.warn("resolveKeyPair({}) Failed ({}) to load: {}",
+                        keyPath, e.getClass().getSimpleName(), e.getMessage());
+                if (log.isDebugEnabled()) {
+                    log.debug("resolveKeyPair(" + keyPath + ") load failure 
details", e);
+                }
+            }
+        }
+
+        // either no file specified or no key in file
+        try {
+            kp = generateKeyPair(alg);
+            if (kp == null) {
+                return null;
+            }
+
+            if (log.isDebugEnabled()) {
+                PublicKey key = kp.getPublic();
+                log.debug("resolveKeyPair({}) generated {} key={}-{}",
+                          keyPath, alg, KeyUtils.getKeyType(key), 
KeyUtils.getFingerPrint(key));
+            }
+        } catch (Throwable e) {
+            log.warn("resolveKeyPair({})[{}] Failed ({}) to generate {} 
key-pair: {}",
+                     keyPath, alg, e.getClass().getSimpleName(), alg, 
e.getMessage());
+            if (log.isDebugEnabled()) {
+                log.debug("resolveKeyPair(" + keyPath + ")[" + alg + "] 
key-pair generation failure details", e);
+            }
+
+            return null;
+        }
+
+        if (keyPath != null) {
+            try {
+                writeKeyPair(kp, keyPath);
+            } catch (Throwable e) {
+                log.warn("resolveKeyPair({})[{}] Failed ({}) to write {} key: 
{}",
+                         alg, keyPath, e.getClass().getSimpleName(), alg, 
e.getMessage());
+                if (log.isDebugEnabled()) {
+                    log.debug("resolveKeyPair(" + keyPath + ")[" + alg + "] 
write failure details", e);
+                }
+            }
+        }
+
+        return kp;
+    }
+
+    protected KeyPair loadFromFile(String alg, Path keyPath) throws 
IOException, GeneralSecurityException {
+        LinkOption[] options = IoUtils.getLinkOptions(true);
+        if ((!Files.exists(keyPath, options)) || 
(!Files.isRegularFile(keyPath, options))) {
+            return null;
+        }
+
+        KeyPair kp = readKeyPair(keyPath, IoUtils.EMPTY_OPEN_OPTIONS);
+        if (kp == null) {
+            return null;
+        }
+
+        PublicKey key = kp.getPublic();
+        String keyAlgorithm = key.getAlgorithm();
+        if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) {
+            keyAlgorithm = KeyUtils.EC_ALGORITHM;
+        } else if 
(BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyAlgorithm)) {
+            keyAlgorithm = SecurityUtils.EDDSA;
+        }
+
+        if (Objects.equals(alg, keyAlgorithm)) {
+            if (log.isDebugEnabled()) {
+                log.debug("resolveKeyPair({}) loaded key={}-{}",
+                          keyPath, KeyUtils.getKeyType(key), 
KeyUtils.getFingerPrint(key));
+            }
+            return kp;
+        }
+
+        // Not same algorithm - start again
+        if (log.isDebugEnabled()) {
+            log.debug("resolveKeyPair({}) mismatched loaded key algorithm: 
expected={}, loaded={}",
+                      keyPath, alg, keyAlgorithm);
+        }
+        Files.deleteIfExists(keyPath);
+        return null;
+    }
+
+    protected KeyPair readKeyPair(Path keyPath, OpenOption... options) throws 
IOException, GeneralSecurityException {
+        try (InputStream inputStream = Files.newInputStream(keyPath, options)) 
{
+            return doReadKeyPair(keyPath.toString(), inputStream);
+        }
+    }
+
+    protected KeyPair doReadKeyPair(String resourceKey, InputStream 
inputStream) throws IOException, GeneralSecurityException {
+        return SecurityUtils.loadKeyPairIdentity(resourceKey, inputStream, 
null);
+    }
+
+    protected void writeKeyPair(KeyPair kp, Path keyPath, OpenOption... 
options) throws IOException, GeneralSecurityException {
+        if ((!Files.exists(keyPath)) || isOverwriteAllowed()) {
+            try (OutputStream os = Files.newOutputStream(keyPath, options)) {
+                doWriteKeyPair(keyPath.toString(), kp, os);
+            } catch (Throwable e) {
+                log.warn("writeKeyPair({}) failed ({}) to write key {}: {}",
+                         keyPath, e.getClass().getSimpleName(), 
e.getMessage());
+                if (log.isDebugEnabled()) {
+                    log.debug("writeKeyPair(" + keyPath + ") write failure 
details", e);
+                }
+            }
+        } else {
+            log.error("Overwriting key ({}) is disabled: using throwaway {}: 
{}",
+                      keyPath, KeyUtils.getKeyType(kp), 
KeyUtils.getFingerPrint((kp == null) ? null : kp.getPublic()));
+        }
+    }
+
+    protected abstract void doWriteKeyPair(String resourceKey, KeyPair kp, 
OutputStream outputStream) throws IOException, GeneralSecurityException;
+
+    protected KeyPair generateKeyPair(String algorithm) throws 
GeneralSecurityException {
+        KeyPairGenerator generator = 
SecurityUtils.getKeyPairGenerator(algorithm);
+        if (keySpec != null) {
+            generator.initialize(keySpec);
+            log.info("generateKeyPair(" + algorithm + ") generating host key - 
spec=" + keySpec.getClass().getSimpleName());
+        } else if (keySize != 0) {
+            generator.initialize(keySize);
+            log.info("generateKeyPair(" + algorithm + ") generating host key - 
size=" + keySize);
+        } else if (KeyUtils.EC_ALGORITHM.equals(algorithm)) {
+            // If left to our own devices choose the biggest key size possible
+            int numCurves = ECCurves.SORTED_KEY_SIZE.size();
+            ECCurves curve = ECCurves.SORTED_KEY_SIZE.get(numCurves - 1);
+            generator.initialize(curve.getParameters());
+        }
+
+        return generator.generateKeyPair();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java
 
b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java
new file mode 100644
index 0000000..3bccde8
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.keyprovider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.spec.InvalidKeySpecException;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class SimpleGeneratorHostKeyProvider extends 
AbstractGeneratorHostKeyProvider {
+    public SimpleGeneratorHostKeyProvider() {
+        super();
+    }
+
+    public SimpleGeneratorHostKeyProvider(File file) {
+        this((file == null) ? null : file.toPath());
+    }
+
+    public SimpleGeneratorHostKeyProvider(Path path) {
+        setPath(path);
+    }
+
+    @Override
+    protected KeyPair doReadKeyPair(String resourceKey, InputStream 
inputStream) throws IOException, GeneralSecurityException {
+        try (ObjectInputStream r = new ObjectInputStream(inputStream)) {
+            try {
+                return (KeyPair) r.readObject();
+            } catch (ClassNotFoundException e) {
+                throw new InvalidKeySpecException("Missing classes: " + 
e.getMessage(), e);
+            }
+        }
+    }
+
+    @Override
+    protected void doWriteKeyPair(String resourceKey, KeyPair kp, OutputStream 
outputStream) throws IOException, GeneralSecurityException {
+        try (ObjectOutputStream w = new ObjectOutputStream(outputStream)) {
+            w.writeObject(kp);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java
new file mode 100644
index 0000000..165805d
--- /dev/null
+++ 
b/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.auth.password;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class PasswordIdentityProviderTest extends JUnitTestSupport {
+    public PasswordIdentityProviderTest() {
+        super();
+    }
+
+    @Test
+    public void testMultiProvider() {
+        String[][] values = {
+            {getClass().getSimpleName(), getCurrentTestName()},
+            {new Date(System.currentTimeMillis()).toString()},
+            {getClass().getPackage().getName()}
+        };
+        List<String> expected = new ArrayList<>();
+        Collection<PasswordIdentityProvider> providers = new LinkedList<>();
+        for (String[] va : values) {
+            Collection<String> passwords = Arrays.asList(va);
+            expected.addAll(passwords);
+
+            PasswordIdentityProvider p = 
PasswordIdentityProvider.wrapPasswords(passwords);
+            assertProviderContents("Wrapped", p, passwords);
+            providers.add(p);
+        }
+
+        PasswordIdentityProvider p = 
PasswordIdentityProvider.multiProvider(providers);
+        assertProviderContents("Multi", p, expected);
+    }
+
+    private static void assertProviderContents(String message, 
PasswordIdentityProvider p, Iterable<String> expected) {
+        assertNotNull(message + ": no provider", p);
+        assertEquals(message, expected, p.loadPasswords());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
new file mode 100644
index 0000000..741ba7b
--- /dev/null
+++ 
b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolverTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.hosts;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class ConfigFileHostEntryResolverTest extends JUnitTestSupport {
+    public ConfigFileHostEntryResolverTest() {
+        super();
+    }
+
+    @Test
+    public void testConfigFileReload() throws IOException {
+        Path dir = getTempTargetRelativeFile(getClass().getSimpleName());
+        AtomicInteger reloadCount = new AtomicInteger();
+        ConfigFileHostEntryResolver resolver = new 
ConfigFileHostEntryResolver(assertHierarchyTargetFolderExists(dir).resolve(getCurrentTestName()
 + ".config.txt")) {
+            @Override
+            protected List<HostConfigEntry> reloadHostConfigEntries(Path path, 
String host, int port, String username)
+                    throws IOException {
+                reloadCount.incrementAndGet();
+                return super.reloadHostConfigEntries(path, host, port, 
username);
+            }
+        };
+        Path path = resolver.getPath();
+
+        HostConfigEntry expected = new HostConfigEntry(getCurrentTestName(), 
getCurrentTestName(), 7365, getCurrentTestName());
+        testConfigFileReload("Non-existing", path, reloadCount, null, 
resolver, expected, null);
+        testConfigFileReload("Empty", path, reloadCount, 
Collections.emptyList(), resolver, expected, null);
+        testConfigFileReload("Global", path, reloadCount,
+                Collections.singletonList(new 
HostConfigEntry(HostPatternsHolder.ALL_HOSTS_PATTERN, expected.getHost(), 
expected.getPort(), expected.getUsername())),
+                resolver, expected, expected);
+        testConfigFileReload("Wildcard", path, reloadCount,
+                Arrays.asList(
+                        new HostConfigEntry(
+                                HostPatternsHolder.ALL_HOSTS_PATTERN,
+                                getClass().getSimpleName(),
+                                1234,
+                                getClass().getSimpleName()),
+                        new HostConfigEntry(
+                                expected.getHost() + 
Character.toString(HostPatternsHolder.WILDCARD_PATTERN),
+                                expected.getHost(),
+                                expected.getPort(),
+                                expected.getUsername())),
+                resolver, expected, expected);
+        testConfigFileReload("Specific", path, reloadCount,
+                Arrays.asList(
+                        new HostConfigEntry(
+                                HostPatternsHolder.ALL_HOSTS_PATTERN,
+                                getClass().getSimpleName(),
+                                1234,
+                                getClass().getSimpleName()),
+                        new HostConfigEntry(
+                                getClass().getSimpleName() + 
Character.toString(HostPatternsHolder.WILDCARD_PATTERN),
+                                getClass().getSimpleName(),
+                                1234,
+                                getClass().getSimpleName()),
+                        expected),
+                resolver, expected, expected);
+    }
+
+    private static void testConfigFileReload(
+            String phase, Path path, AtomicInteger reloadCount,
+            Collection<? extends HostConfigEntry> entries,
+            HostConfigEntryResolver resolver,
+            HostConfigEntry query,
+            HostConfigEntry expected)
+                    throws IOException {
+        if (entries == null) {
+            if (Files.exists(path)) {
+                Files.delete(path);
+            }
+        } else {
+            HostConfigEntry.writeHostConfigEntries(path, entries, 
IoUtils.EMPTY_OPEN_OPTIONS);
+        }
+
+        reloadCount.set(0);
+
+        for (int index = 1; index < Byte.SIZE; index++) {
+            HostConfigEntry actual =
+                    resolver.resolveEffectiveHost(query.getHostName(), 
query.getPort(), query.getUsername());
+
+            if (entries == null) {
+                assertEquals(phase + "[" + index + "]: mismatched reload 
count", 0, reloadCount.get());
+            } else {
+                assertEquals(phase + "[" + index + "]: mismatched reload 
count", 1, reloadCount.get());
+            }
+
+            if (expected == null) {
+                assertNull(phase + "[" + index + "]: Unexpected success for " 
+ query, actual);
+            } else {
+                assertNotNull(phase + "[" + index + "]: No result for " + 
query, actual);
+                assertNotSame(phase + "[" + index + "]: No cloned result for " 
+ query, expected, actual);
+                assertEquals(phase + "[" + index + "]: Mismatched host for " + 
query,
+                        expected.getHostName(), actual.getHostName());
+                assertEquals(phase + "[" + index + "]: Mismatched port for " + 
query,
+                        expected.getPort(), actual.getPort());
+                assertEquals(phase + "[" + index + "]: Mismatched user for " + 
query,
+                        expected.getUsername(), actual.getUsername());
+            }
+        }
+    }
+}

Reply via email to