[SSHD-482] Add support for authorized_keys file

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

Branch: refs/heads/master
Commit: e12434d0d42a24a312b924de7d3731606377c40a
Parents: 330d17c
Author: Lyor Goldstein <[email protected]>
Authored: Wed Jun 3 14:54:50 2015 +0300
Committer: Lyor Goldstein <[email protected]>
Committed: Wed Jun 3 14:54:50 2015 +0300

----------------------------------------------------------------------
 .../main/java/org/apache/sshd/SshBuilder.java   |  27 ++
 .../sshd/agent/common/AbstractAgentClient.java  |   2 +-
 .../sshd/client/auth/UserAuthPublicKey.java     |   2 +-
 .../org/apache/sshd/client/kex/DHGClient.java   |   2 +-
 .../org/apache/sshd/client/kex/DHGEXClient.java |   2 +-
 .../keyverifier/StaticServerKeyVerifier.java    |   2 +-
 .../org/apache/sshd/common/cipher/ECCurves.java |  23 ++
 .../keys/AbstractPublicKeyEntryDecoder.java     | 150 +++++++++
 .../config/keys/DSSPublicKeyEntryDecoder.java   |  63 ++++
 .../config/keys/ECDSAPublicKeyEntryDecoder.java | 236 +++++++++++++
 .../sshd/common/config/keys/KeyUtils.java       | 314 +++++++++++++++++
 .../sshd/common/config/keys/PublicKeyEntry.java | 218 ++++++++++++
 .../config/keys/PublicKeyEntryDecoder.java      |  56 ++++
 .../common/config/keys/RSAPublicKeyDecoder.java |  61 ++++
 .../sshd/common/kex/BuiltinDHFactories.java     |   6 +-
 .../java/org/apache/sshd/common/kex/ECDH.java   |   6 +-
 .../keyprovider/AbstractKeyPairProvider.java    |   2 +-
 .../common/keyprovider/KeyPairProvider.java     |   8 +-
 .../org/apache/sshd/common/util/Base64.java     |  64 +++-
 .../apache/sshd/common/util/GenericUtils.java   |  44 +++
 .../org/apache/sshd/common/util/IoUtils.java    |  62 ++++
 .../org/apache/sshd/common/util/KeyUtils.java   | 100 ------
 .../common/util/io/ModifiableFileWatcher.java   | 169 ++++++++++
 .../sshd/server/PublickeyAuthenticator.java     |  41 ++-
 .../auth/CachingPublicKeyAuthenticator.java     |   3 +-
 .../server/config/keys/AuthorizedKeyEntry.java  | 335 +++++++++++++++++++
 .../keys/AuthorizedKeysAuthenticator.java       | 126 +++++++
 .../DefaultAuthorizedKeysAuthenticator.java     | 161 +++++++++
 .../apache/sshd/SinglePublicKeyAuthTest.java    |   2 +-
 .../sshd/common/util/GenericUtilsTest.java      |  59 ++++
 .../apache/sshd/deprecated/UserAuthAgent.java   |   2 +-
 .../sshd/deprecated/UserAuthPublicKey.java      |   2 +-
 .../config/keys/AuthorizedKeyEntryTest.java     | 108 ++++++
 .../keys/AuthorizedKeysAuthenticatorTest.java   | 124 +++++++
 .../DefaultAuthorizedKeysAuthenticatorTest.java |  67 ++++
 .../sshd/server/config/keys/authorized_keys     |  16 +
 36 files changed, 2536 insertions(+), 129 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java 
b/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
index 367e379..cd4d7d1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
@@ -56,7 +56,9 @@ import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.common.util.ObjectBuilder;
 import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.server.PublickeyAuthenticator;
 import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
 import org.apache.sshd.server.global.CancelTcpipForwardHandler;
 import org.apache.sshd.server.global.KeepAliveHandler;
 import org.apache.sshd.server.global.NoMoreSessionsHandler;
@@ -122,6 +124,7 @@ public class SshBuilder {
             if (compressionFactories == null) {
                 compressionFactories = 
Arrays.<NamedFactory<Compression>>asList(BuiltinCompressions.none);
             }
+
             if (macFactories == null) {
                 macFactories = setUpDefaultMacs(false);
             }
@@ -129,6 +132,7 @@ public class SshBuilder {
             if (fileSystemFactory == null) {
                 fileSystemFactory = new NativeFileSystemFactory();
             }
+
             if (tcpipForwarderFactory == null) {
                 tcpipForwarderFactory = new DefaultTcpipForwarderFactory();
             }
@@ -425,6 +429,17 @@ public class SshBuilder {
                     }
                 };
 
+        protected PublickeyAuthenticator pubkeyAuthenticator;
+
+        public ServerBuilder() {
+            super();
+        }
+
+        public ServerBuilder publickeyAuthenticator(PublickeyAuthenticator 
auth) {
+            pubkeyAuthenticator = auth;
+            return this;
+        }
+
         @Override
         protected ServerBuilder fillWithDefaultValues() {
             super.fillWithDefaultValues();
@@ -446,9 +461,21 @@ public class SshBuilder {
             if (factory == null) {
                 factory = SshServer.DEFAULT_SSH_SERVER_FACTORY;
             }
+            
+            if (pubkeyAuthenticator == null) {
+                pubkeyAuthenticator = 
DefaultAuthorizedKeysAuthenticator.INSTANCE;
+            }
+
             return me();
         }
 
+        @Override
+        public SshServer build(boolean isFillWithDefaultValues) {
+            SshServer server = super.build(isFillWithDefaultValues);
+            server.setPublickeyAuthenticator(pubkeyAuthenticator);
+            return server;
+        }
+
         /**
          * @param ignoreUnsupported If {@code true} then all the default
          * key exchanges are included, regardless of whether they are currently

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java 
b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
index 9e48c7d..20510ec 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
@@ -33,8 +33,8 @@ import java.security.PublicKey;
 import java.util.List;
 
 import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.AbstractLoggingBean;
-import org.apache.sshd.common.util.KeyUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java 
b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
index d3a0279..205d6c2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
@@ -18,7 +18,7 @@
  */
 package org.apache.sshd.client.auth;
 
-import static org.apache.sshd.common.util.KeyUtils.getKeyType;
+import static org.apache.sshd.common.config.keys.KeyUtils.getKeyType;
 
 import java.io.IOException;
 import java.security.KeyPair;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index 5e98ac2..6efe31a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -23,10 +23,10 @@ import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Signature;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.kex.AbstractDH;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.session.AbstractSession;
-import org.apache.sshd.common.util.KeyUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
index b1a05f9..afe5269 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
@@ -26,10 +26,10 @@ import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Signature;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.kex.AbstractDH;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.session.AbstractSession;
-import org.apache.sshd.common.util.KeyUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
index f0f8aa4..9ebf0d4 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
@@ -24,8 +24,8 @@ import java.security.PublicKey;
 
 import org.apache.sshd.ClientSession;
 import org.apache.sshd.client.ServerKeyVerifier;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.AbstractLoggingBean;
-import org.apache.sshd.common.util.KeyUtils;
 
 /**
  * Returns the same constant answer {@code true/false} regardless

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java 
b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index 2cc250c..ad42865 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -23,6 +23,7 @@ import java.security.spec.ECFieldFp;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
 import java.security.spec.EllipticCurve;
+import java.util.Collections;
 import java.util.Map;
 import java.util.TreeMap;
 
@@ -187,4 +188,26 @@ public class ECCurves {
                 new 
BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
 16),
                 1);
     }
+    
+    private static final class LazySpecsMapHolder {
+        private static final Map<String,ECParameterSpec> specsMap =
+                Collections.unmodifiableMap(new TreeMap<String, 
ECParameterSpec>(String.CASE_INSENSITIVE_ORDER) {
+                        private static final long serialVersionUID = 1L;    // 
we're not serializing it
+                    
+                        {
+                            put(NISTP256, EllipticCurves.nistp256);
+                            put(NISTP384, EllipticCurves.nistp384);
+                            put(NISTP521, EllipticCurves.nistp521);
+                        }
+                });
+    }
+
+    @SuppressWarnings("synthetic-access")
+    public static ECParameterSpec getECParameterSpec(String curveName) {
+        if (GenericUtils.isEmpty(curveName)) {
+            return null;
+        } else {
+            return LazySpecsMapHolder.specsMap.get(curveName);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..fc97f13
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPublicKeyEntryDecoder<K extends PublicKey> 
implements PublicKeyEntryDecoder<K> {
+    private final Class<K> keyType;
+    private final Collection<String>    names;
+    
+    protected AbstractPublicKeyEntryDecoder(Class<K> keyType, 
Collection<String> names) {
+        this.keyType = ValidateUtils.checkNotNull(keyType, "No key type 
specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+        this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type 
names provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+    }
+
+    @Override
+    public final Class<K> getKeyType() {
+        return keyType;
+    }
+
+    @Override
+    public Collection<String> getSupportedTypeNames() {
+        return names;
+    }
+
+    @Override
+    public K decodePublicKey(byte... keyData) throws IOException, 
GeneralSecurityException {
+        return decodePublicKey(keyData, 0, GenericUtils.length(keyData));
+    }
+
+    @Override
+    public K decodePublicKey(byte[] keyData, int offset, int length) throws 
IOException, GeneralSecurityException {
+        if (length <= 0) {
+            return null;
+        }
+
+        try(InputStream stream=new ByteArrayInputStream(keyData, offset, 
length)) {
+            return decodePublicKey(stream);
+        }
+    }
+
+    @Override
+    public K decodePublicKey(InputStream keyData) throws IOException, 
GeneralSecurityException {
+        // the actual data is preceded by a string that repeats the key type
+        String type = decodeString(keyData);
+        if (GenericUtils.isEmpty(type)) {
+            throw new StreamCorruptedException("Missing key type string");
+        }
+
+        Collection<String> supported = getSupportedTypeNames();
+        if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) {
+            throw new InvalidKeySpecException("Reported key type (" + type + 
") not in supported list: " + supported);
+        }
+
+        return decodePublicKey(type, keyData);
+    }
+
+    public K generatePublicKey(KeySpec keySpec) throws 
GeneralSecurityException {
+        KeyFactory  factory = getKeyFactoryInstance();
+        Class<K>    keyType = getKeyType();
+        return keyType.cast(factory.generatePublic(keySpec));
+    }
+
+    public abstract KeyFactory getKeyFactoryInstance() throws 
GeneralSecurityException;
+
+    /**
+     * @param keyType The reported / encode key type
+     * @param keyData The key data bytes stream positioned after the key type 
decoding
+     * and making sure it is one of the supported types
+     * @return The decoded {@link PublicKey}
+     * @throws IOException If failed to read from the data stream
+     * @throws GeneralSecurityException If failed to generate the key
+     */
+    public abstract K decodePublicKey(String keyType, InputStream keyData) 
throws IOException, GeneralSecurityException;
+
+    @Override
+    public String toString() {
+        return getKeyType().getSimpleName() + ": " + getSupportedTypeNames();
+    }
+
+    public static final String decodeString(InputStream s) throws IOException {
+        return decodeString(s, StandardCharsets.UTF_8);
+    }
+
+    public static final String decodeString(InputStream s, String charset) 
throws IOException {
+        return decodeString(s, Charset.forName(charset));
+    }
+
+    public static final String decodeString(InputStream s, Charset cs) throws 
IOException {
+        byte[]  bytes=readRLEBytes(s);
+        return new String(bytes, cs);
+    }
+
+    public static final BigInteger decodeBigInt(InputStream s) throws 
IOException {
+        return new BigInteger(readRLEBytes(s));
+    }
+
+    public static final byte[] readRLEBytes(InputStream s) throws IOException {
+        int     len=decodeInt(s);
+        byte[]  bytes=new byte[len];
+        IoUtils.readFully(s, bytes);
+        return bytes;
+    }
+
+    public static final int decodeInt(InputStream s) throws IOException {
+        byte[]  bytes={ 0, 0, 0, 0 };
+        IoUtils.readFully(s, bytes);
+        return ((bytes[0] & 0xFF) << 24)
+             | ((bytes[1] & 0xFF) << 16)
+             | ((bytes[2] & 0xFF) << 8)
+             | (bytes[3] & 0xFF);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..62acb4c
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collections;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class DSSPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<DSAPublicKey> {
+    public static final DSSPublicKeyEntryDecoder INSTANCE = new 
DSSPublicKeyEntryDecoder();
+
+    public DSSPublicKeyEntryDecoder() {
+        super(DSAPublicKey.class, 
Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
+    }
+
+    @Override
+    public DSAPublicKey decodePublicKey(String keyType, InputStream keyData) 
throws IOException, GeneralSecurityException {
+        if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we 
were invoked directly
+            throw new InvalidKeySpecException("Unepected key type: " + 
keyType);
+        }
+
+        BigInteger  p=decodeBigInt(keyData);
+        BigInteger  q=decodeBigInt(keyData);
+        BigInteger  g=decodeBigInt(keyData);
+        BigInteger  y=decodeBigInt(keyData);
+
+        return generatePublicKey(new DSAPublicKeySpec(y, p, q, g));
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory("DSA");
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..29be249
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.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.config.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class ECDSAPublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecoder<ECPublicKey> {
+    public static final ECDSAPublicKeyEntryDecoder INSTANCE = new 
ECDSAPublicKeyEntryDecoder();
+
+    public ECDSAPublicKeyEntryDecoder() {
+        super(ECPublicKey.class,
+              Collections.unmodifiableList(
+                  Arrays.asList(KeyPairProvider.ECDSA_SHA2_NISTP256, 
KeyPairProvider.ECDSA_SHA2_NISTP384, KeyPairProvider.ECDSA_SHA2_NISTP521)));
+    }
+
+    @Override
+    public ECPublicKey decodePublicKey(String keyType, InputStream keyData) 
throws IOException, GeneralSecurityException {
+        if (GenericUtils.isEmpty(keyType) || 
(!keyType.startsWith(ECCurves.ECDSA_SHA2_PREFIX))) {
+            throw new InvalidKeySpecException("Not an EC curve name: " + 
keyType);
+        }
+        
+        if (!SecurityUtils.hasEcc()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        String keyCurveName = 
keyType.substring(ECCurves.ECDSA_SHA2_PREFIX.length());
+        ECParameterSpec paramSpec = ECCurves.getECParameterSpec(keyCurveName);
+        if (paramSpec == null) {
+            throw new InvalidKeySpecException("Unknown EC key curve name: " + 
keyCurveName);
+        }
+        
+        // see rfc5656 section 3.1
+        String encCurveName = decodeString(keyData);
+        if (!keyCurveName.equals(encCurveName)) {
+            throw new InvalidKeySpecException("Mismatched key curve name (" + 
keyCurveName + ") vs. encoded one (" + encCurveName + ")");
+        }
+
+        byte[]  octets = readRLEBytes(keyData);
+        final ECPoint w;
+        try {
+            if ((w = octetStringToEcPoint(octets)) == null) {
+                throw new InvalidKeySpecException("No ECPoint generated for 
curve=" + keyCurveName + " from octets=" + BufferUtils.printHex(':', octets));
+            }
+        } catch(RuntimeException e) {
+            throw new InvalidKeySpecException("Failed (" + 
e.getClass().getSimpleName() + ")"
+                                            + " to generate ECPoint for 
curve=" + keyCurveName
+                                            + " from octets=" + 
BufferUtils.printHex(':', octets)
+                                            + ": " + e.getMessage());
+        }
+
+        return generatePublicKey(new ECPublicKeySpec(w, paramSpec));
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        if (SecurityUtils.hasEcc()) {
+            return SecurityUtils.getKeyFactory("EC");
+        } else {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+    }
+
+    // see rfc5480 section 2.2
+    public static final byte    ECPOINT_UNCOMPRESSED_FORM_INDICATOR=0x04;
+    public static final byte    ECPOINT_COMPRESSED_VARIANT_2=0x02;
+    public static final byte    ECPOINT_COMPRESSED_VARIANT_3=0x02;
+
+    public static ECPoint octetStringToEcPoint(byte ... octets) {
+        if (GenericUtils.isEmpty(octets)) {
+            return null;
+        }
+
+        int startIndex=findFirstNonZeroIndex(octets);
+        if (startIndex < 0) {
+            throw new IllegalArgumentException("All zeroes ECPoint N/A");
+        }
+
+        byte                indicator=octets[startIndex];
+        ECPointCompression  
compression=ECPointCompression.fromIndicatorValue(indicator);
+        if (compression == null) {
+            throw new UnsupportedOperationException("Unknown compression 
indicator value: 0x" + Integer.toHexString(indicator & 0xFF));
+        }
+
+        // The coordinates actually start after the compression indicator
+        return compression.octetStringToEcPoint(octets, startIndex + 1, 
octets.length - startIndex - 1);
+    }
+
+    private static int findFirstNonZeroIndex(byte ... octets) {
+        if (GenericUtils.isEmpty(octets)) {
+            return (-1);
+        }
+
+        for (int    index=0; index < octets.length; index++) {
+            if (octets[index] != 0) {
+                return index;
+            }
+        }
+
+        return (-1);    // all zeroes
+    }
+    /**
+     * The various {@link ECPoint} representation compression indicators
+     * @author <a href="mailto:[email protected]";>Apache MINA SSHD 
Project</a>
+     * @see <A HREF="https://www.ietf.org/rfc/rfc5480.txt";>RFC-5480 - section 
2.2</A>
+     */
+    public enum ECPointCompression {
+        // see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00
+        // see 
http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point
+        VARIANT2((byte) 0x02) {
+                @Override
+                public ECPoint octetStringToEcPoint(byte[] octets, int 
startIndex, int len) {
+                    byte[] xp=new byte[len];
+                    System.arraycopy(octets, startIndex, xp, 0, len);
+                    BigInteger  x=octetStringToInteger(xp);
+
+                    // TODO derive even Y...
+                    throw new 
UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + 
") compression support N/A");
+                }
+            },
+        VARIANT3((byte) 0x03) {
+                @Override
+                public ECPoint octetStringToEcPoint(byte[] octets, int 
startIndex, int len) {
+                    byte[] xp=new byte[len];
+                    System.arraycopy(octets, startIndex, xp, 0, len);
+                    BigInteger  x=octetStringToInteger(xp);
+
+                    // TODO derive odd Y...
+                    throw new 
UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + 
") compression support N/A");
+                }
+            },
+        UNCOMPRESSED((byte) 0x04) {
+                @Override
+                public ECPoint octetStringToEcPoint(byte[] octets, int 
startIndex, int len) {
+                    int numElements=len / 2;    /* x, y */
+                    if (len != (numElements * 2 )) {    // make sure length is 
not odd
+                        throw new 
IllegalArgumentException("octetStringToEcPoint(" + name() + ") "
+                                                         + " invalid remainder 
octets representation: "
+                                                         + " expected=" + (2 * 
numElements) + ", actual=" + len);
+                    }
+
+                    byte[] xp=new byte[numElements], yp=new byte[numElements];
+                    System.arraycopy(octets, startIndex, xp, 0, numElements);
+                    System.arraycopy(octets, startIndex + numElements, yp, 0, 
numElements);
+
+                    BigInteger  x=octetStringToInteger(xp);
+                    BigInteger  y=octetStringToInteger(yp);
+                    return new ECPoint(x, y);
+                }
+            };
+
+        private final byte  indicatorValue;
+        public final byte getIndicatorValue() {
+            return indicatorValue;
+        }
+
+        ECPointCompression(byte indicator) {
+            indicatorValue = indicator;
+        }
+
+        public abstract ECPoint octetStringToEcPoint(byte[] octets, int 
startIndex, int len);
+
+        public static final Set<ECPointCompression> VALUES=
+                
Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
+        public static final ECPointCompression fromIndicatorValue(int value) {
+            if ((value < 0) || (value > 0xFF)) {
+                return null;    // must be a byte value
+            }
+
+            for (ECPointCompression c : VALUES) {
+                if (value == c.getIndicatorValue()) {
+                    return c;
+                }
+            }
+
+            return null;
+        }
+
+        /**
+         * Converts the given octet string (defined by ASN.1 specifications) 
to a {@link BigInteger}
+         * As octet strings always represent positive integers, a zero-byte is 
prepended to
+         * the given array if necessary (if is MSB equal to 1), then this is 
converted to BigInteger
+         * The conversion is defined in the Section 2.3.8
+         * @param octets - octet string bytes to be converted
+         * @return The {@link BigInteger} representation of the octet string
+         */
+        public static BigInteger octetStringToInteger(byte ... octets) {
+            if (octets == null) {
+                return null;
+            } else if (octets.length == 0) {
+                return BigInteger.ZERO;
+            } else {
+                return new BigInteger(1, octets);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
new file mode 100644
index 0000000..4d9b2af
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.config.keys;
+
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.interfaces.DSAKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.Digest;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+/**
+ * Utility class for keys
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class KeyUtils {
+    private static final Map<String,PublicKeyEntryDecoder<? extends 
PublicKey>> byKeyTypeDecodersMap =
+            new TreeMap<String, PublicKeyEntryDecoder<? extends 
PublicKey>>(String.CASE_INSENSITIVE_ORDER);
+    private static final Map<Class<?>,PublicKeyEntryDecoder<? extends 
PublicKey>> byKeyClassDecodersMap =
+            new HashMap<Class<?>, PublicKeyEntryDecoder<? extends 
PublicKey>>();
+
+    static {
+        registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
+        registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
+        registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
+    }
+
+    private KeyUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder<? 
extends PublicKey> decoder) {
+        ValidateUtils.checkNotNull(decoder, "No decoder specified", 
GenericUtils.EMPTY_OBJECT_ARRAY);
+
+        Class<?> keyType = ValidateUtils.checkNotNull(decoder.getKeyType(), 
"No key type declared", GenericUtils.EMPTY_OBJECT_ARRAY);
+        synchronized(byKeyClassDecodersMap) {
+            byKeyClassDecodersMap.put(keyType, decoder);
+        }
+
+        Collection<String> names = 
ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No 
supported key type", GenericUtils.EMPTY_OBJECT_ARRAY);
+        synchronized(byKeyTypeDecodersMap) {
+            for (String n : names) {
+                PublicKeyEntryDecoder<? extends PublicKey>  prev = 
byKeyTypeDecodersMap.put(n, decoder);
+                if (prev != null) {
+                    continue;   // debug breakpoint
+                }
+            }
+        }
+    }
+
+    /**
+     * @param keyType The {@code OpenSSH} key type string - ignored if {@code 
null}/empty
+     * @return The registered {@link PublicKeyEntryDecoder} or {code null} if 
not found
+     */
+    public static PublicKeyEntryDecoder<? extends PublicKey> 
getPublicKeyEntryDecoder(String keyType) {
+        if (GenericUtils.isEmpty(keyType)) {
+            return null;
+        }
+        
+        synchronized(byKeyTypeDecodersMap) {
+            return byKeyTypeDecodersMap.get(keyType);
+        }
+    }
+    
+    /**
+     * @param key The {@link PublicKey} - ignored if {@code null}
+     * @return The registered {@link PublicKeyEntryDecoder} for this key or 
{code null} if no match found
+     * @see #getPublicKeyEntryDecoder(Class)
+     */
+    public static PublicKeyEntryDecoder<? extends PublicKey> 
getPublicKeyEntryDecoder(PublicKey key) {
+        if (key == null) {
+            return null;
+        } else {
+            return getPublicKeyEntryDecoder(key.getClass());
+        }
+    }
+
+    /**
+     * @param keyType The key {@link Class} - ignored if {@code null} or not a 
{@link PublicKey}
+     * compatible type
+     * @return The registered {@link PublicKeyEntryDecoder} or {code null} if 
no match found
+     */
+    public static PublicKeyEntryDecoder<? extends PublicKey> 
getPublicKeyEntryDecoder(Class<?> keyType) {
+        if ((keyType == null) || (!PublicKey.class.isAssignableFrom(keyType))) 
{
+            return null;
+        }
+        
+        synchronized(byKeyTypeDecodersMap) {
+            {
+                PublicKeyEntryDecoder<? extends PublicKey>  
decoder=byKeyTypeDecodersMap.get(keyType);
+                if (decoder != null) {
+                    return decoder;
+                }
+            }
+            
+            // in case it is a derived class
+            for (PublicKeyEntryDecoder<? extends PublicKey> decoder : 
byKeyTypeDecodersMap.values()) {
+                Class<?> t = decoder.getKeyType();
+                if (t.isAssignableFrom(keyType)) {
+                    return decoder;
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Retrieve the public key fingerprint
+     *
+     * @param key the public key - ignored if {@code null}
+     * @return the fingerprint or {@code null} if no key
+     */
+    public static String getFingerPrint(PublicKey key) {
+        if (key == null) {
+            return null;
+        }
+
+        try {
+            Buffer buffer = new ByteArrayBuffer();
+            buffer.putRawPublicKey(key);
+            Digest md5 = BuiltinDigests.md5.create();
+            md5.init();
+            md5.update(buffer.array(), 0, buffer.wpos());
+            byte[] data = md5.digest();
+            return BufferUtils.printHex(data, 0, data.length, ':');
+        } catch(Exception e) {
+            return "Unable to compute fingerprint";
+        }
+    }
+
+    /**
+     * Retrieve the key type
+     *
+     * @param kp a key pair
+     * @return the key type
+     */
+    public static String getKeyType(KeyPair kp) {
+        return getKeyType(kp.getPrivate() != null ? kp.getPrivate() : 
kp.getPublic());
+    }
+
+    /**
+     * Retrieve the key type
+     *
+     * @param key a public or private key
+     * @return the key type
+     */
+    public static String getKeyType(Key key) {
+        if (key instanceof DSAKey) {
+            return KeyPairProvider.SSH_DSS;
+        } else if (key instanceof RSAKey) {
+            return KeyPairProvider.SSH_RSA;
+        } else if (key instanceof ECKey) {
+            ECKey ecKey = (ECKey) key;
+            ECParameterSpec ecSpec = ecKey.getParams();
+            return ECCurves.ECDSA_SHA2_PREFIX + ECCurves.getCurveName(ecSpec);
+        }
+        return null;
+    }
+
+    /**
+     * @param key The {@link PublicKey} to be checked - ignored if {@code null}
+     * @param keySet The keys to be searched - ignored if {@code null}/empty
+     * @return The matching {@link PublicKey} from the keys or {@code null} if
+     * no match found
+     * @see #compareKeys(PublicKey, PublicKey)
+     */
+    public static PublicKey findMatchingKey(PublicKey key, PublicKey ... 
keySet) {
+        if ((key == null) || GenericUtils.isEmpty(keySet)) {
+            return null;
+        } else {
+            return findMatchingKey(key, Arrays.asList(keySet));
+        }
+    }
+
+    /**
+     * @param key The {@link PublicKey} to be checked - ignored if {@code null}
+     * @param keySet The keys to be searched - ignored if {@code null}/empty
+     * @return The matching {@link PublicKey} from the keys or {@code null} if
+     * no match found
+     * @see #compareKeys(PublicKey, PublicKey)
+     */
+    public static PublicKey findMatchingKey(PublicKey key, Collection<? 
extends PublicKey> keySet) {
+        if ((key == null) || GenericUtils.isEmpty(keySet)) {
+            return null;
+        }
+        
+        for (PublicKey k : keySet) {
+            if (compareKeys(key, k)) {
+                return k;
+            }
+        }
+        
+        return null;
+    }
+
+    public static boolean compareKeys(PublicKey k1, PublicKey k2) {
+        if ((k1 instanceof RSAPublicKey) && (k2 instanceof RSAPublicKey)) {
+            return compareRSAKeys(RSAPublicKey.class.cast(k1), 
RSAPublicKey.class.cast(k2));
+        } else if ((k1 instanceof DSAPublicKey) && (k2 instanceof 
DSAPublicKey)) {
+            return compareDSAKeys(DSAPublicKey.class.cast(k1), 
DSAPublicKey.class.cast(k2));
+        } else if ((k1 instanceof ECPublicKey) && (k2 instanceof ECPublicKey)) 
{
+            return compareECKeys(ECPublicKey.class.cast(k1), 
ECPublicKey.class.cast(k2));
+        } else {
+            return false;   // either key is null or not of same class
+        }
+    }
+    
+    public static boolean compareRSAKeys(RSAPublicKey k1, RSAPublicKey k2) {
+        if (Objects.equals(k1, k2)) {
+            return true;
+        } else if ((k1 == null) || (k2 == null)) {
+            return false;   // both null is covered by Objects#equals
+        } else if (Objects.equals(k1.getPublicExponent(), 
k2.getPublicExponent())
+                && Objects.equals(k1.getModulus(), k2.getModulus())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public static boolean compareDSAKeys(DSAPublicKey k1, DSAPublicKey k2) {
+        if (Objects.equals(k1, k2)) {
+            return true;
+        } else if ((k1 == null) || (k2 == null)) {
+            return false;   // both null is covered by Objects#equals
+        } else if (Objects.equals(k1.getY(), k2.getY())
+                && compareDSAParams(k1.getParams(), k2.getParams())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    public static boolean compareDSAParams(DSAParams p1, DSAParams p2) {
+        if (Objects.equals(p1, p2)) {
+            return true;
+        } else if ((p1 == null) || (p2 == null)) {
+            return false;   // both null is covered by Objects#equals
+        } else if (Objects.equals(p1.getG(), p2.getG())
+                && Objects.equals(p1.getP(), p2.getP())
+                && Objects.equals(p1.getQ(), p2.getQ())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    public static boolean compareECKeys(ECPublicKey k1, ECPublicKey k2) {
+        if (Objects.equals(k1, k2)) {
+            return true;
+        } else if ((k1 == null) || (k2 == null)) {
+            return false;   // both null is covered by Objects#equals
+        } else if (Objects.equals(k1.getW(), k2.getW())
+                && compareECParams(k1.getParams(), k2.getParams())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public static boolean compareECParams(ECParameterSpec s1, ECParameterSpec 
s2) {
+        if (Objects.equals(s1, s2)) {
+            return true;
+        } else if ((s1 == null) || (s2 == null)) {
+            return false;   // both null is covered by Objects#equals
+        } else if (Objects.equals(s1.getOrder(), s2.getOrder())
+                && (s1.getCofactor() == s2.getCofactor())
+                && Objects.equals(s1.getGenerator(), s2.getGenerator())
+                && Objects.equals(s1.getCurve(), s2.getCurve())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
new file mode 100644
index 0000000..a3dbf8b
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.Base64;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * <P>Represents a {@link PublicKey} whose data is formatted according to
+ * the <A HREF="http://en.wikibooks.org/wiki/OpenSSH";>OpenSSH</A> 
format:</P></BR>
+ * <CODE><PRE>
+ *      <key-type> <base64-encoded-public-key-data>
+ * </CODE></PRE>
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class PublicKeyEntry implements Serializable {
+    private static final long serialVersionUID = -585506072687602760L;
+
+    private String  keyType;
+    private byte[]  keyData;
+
+    public PublicKeyEntry() {
+        super();
+    }
+
+    public PublicKeyEntry(String keyType, byte ... keyData) {
+        this.keyType = keyType;
+        this.keyData = keyData;
+    }
+
+    public String getKeyType() {
+        return keyType;
+    }
+
+    public void setKeyType(String value) {
+        this.keyType = value;
+    }
+
+    public byte[] getKeyData() {
+        return keyData;
+    }
+
+    public void setKeyData(byte[] value) {
+        this.keyData = value;
+    }
+
+    /**
+     * @return The resolved {@link PublicKey} - never {@code null}.
+     * <B>Note:</B> may be called only after key type and data bytes have
+     * been set or exception(s) may be thrown
+     * @throws IOException If failed to decode the key
+     * @throws GeneralSecurityException If failed to generate the key
+     */
+    public PublicKey resolvePublicKey() throws IOException, 
GeneralSecurityException {
+        String kt = getKeyType();
+        PublicKeyEntryDecoder<? extends PublicKey> decoder = 
KeyUtils.getPublicKeyEntryDecoder(kt);
+        if (decoder == null) {
+            throw new InvalidKeySpecException("No decoder registered for key 
type=" + kt);
+        }
+        
+        byte[] data = getKeyData();
+        PublicKey key = decoder.decodePublicKey(data);
+        if (key == null) {
+            throw new InvalidKeyException("No key of type=" + kt + " decoded 
for data=" + BufferUtils.printHex(':', data));
+        }
+        
+        return key;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(getKeyType())
+             + Arrays.hashCode(getKeyData())
+             ;
+    }
+
+    /*
+     * In case some derived class wants to define some "extended" equality
+     * without having to repeat this code
+     */
+    protected boolean isEquivalent(PublicKeyEntry e) {
+        if (this == e) {
+            return true;
+        }
+        
+        if (Objects.equals(getKeyType(), e.getKeyType())
+         && Arrays.equals(getKeyData(), e.getKeyData())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null)
+            return false;
+        if (this == obj)
+            return true;
+        if (getClass() != obj.getClass())
+            return false;
+
+        if (isEquivalent((PublicKeyEntry) obj)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    @Override
+    public String toString() {
+        byte[] data = getKeyData();
+        return getKeyType() + " " + (GenericUtils.isEmpty(data) ? "<no-key>" : 
Base64.encodeToString(data));
+    }
+    
+    /**
+     * @param data Assumed to contain at least {@code key-type base64-data} 
(anything
+     * beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+     * @return A {@link PublicKeyEntry} or {@code null} if no data
+     * @throws IllegalArgumentException if bad format found
+     * @see #parsePublicKeyEntry(PublicKeyEntry, String)
+     */
+    public static final PublicKeyEntry parsePublicKeyEntry(String data) throws 
IllegalArgumentException {
+        if (GenericUtils.isEmpty(data)) {
+            return null;
+        } else {
+            return parsePublicKeyEntry(new PublicKeyEntry(), data);
+        }
+    }
+
+    /**
+     * @param entry The {@link PublicKeyEntry} whose contents are to be
+     * updated - ignored if {@code null}
+     * @param data Assumed to contain at least {@code key-type base64-data} 
(anything
+     * beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+     * @return The updated entry instance
+     * @throws IllegalArgumentException if bad format found
+     */
+    public static final <E extends PublicKeyEntry> E parsePublicKeyEntry(E 
entry, String data) throws IllegalArgumentException {
+        if (GenericUtils.isEmpty(data) || (entry == null)) {
+            return entry;
+        }
+        
+        int startPos = data.indexOf(' ');
+        if (startPos <= 0) {
+            throw new IllegalArgumentException("Bad format (no key data 
delimiter): " + data);
+        }
+
+        int endPos = data.indexOf(' ', startPos + 1);
+        if (endPos <= startPos) {   // OK if no continuation beyond the BASE64 
encoded data
+            endPos = data.length();
+        }
+
+        String  keyType = data.substring(0, startPos);
+        String  b64Data = data.substring(startPos + 1, endPos).trim();
+        byte[]  keyData = Base64.decodeString(b64Data);
+        if (GenericUtils.isEmpty(keyData)) {
+            throw new IllegalArgumentException("Bad format (no BASE64 key 
data): " + data);
+        }
+        
+        entry.setKeyType(keyType);
+        entry.setKeyData(keyData);
+        return entry;
+    }
+
+    /**
+     * Character used to denote a comment line in the keys file
+     */
+    public static final char COMMENT_CHAR='#';
+
+
+    /**
+     * Standard folder name used by OpenSSH to hold key files
+     */
+    public static final String STD_KEYFILE_FOLDER_NAME=".ssh";
+
+    private static final class LazyDefaultKeysFolderHolder {
+        private static final File   folder=
+                new File(System.getProperty("user.home") + File.separator + 
STD_KEYFILE_FOLDER_NAME);
+    }
+
+    /**
+     * @return The default OpenSSH folder used to hold key files - e.g.,
+     * {@code known_hosts}, {@code authorized_keys}, etc.
+     */
+    @SuppressWarnings("synthetic-access")
+    public static final File getDefaultKeysFolder() {
+        return LazyDefaultKeysFolderHolder.folder;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
new file mode 100644
index 0000000..1ac3ab3
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.Collection;
+
+/**
+ * Represents a decoder of an {@code OpenSSH} encoded key data
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyEntryDecoder<K extends PublicKey> {
+    /**
+     * @return The {@link Class} of the {@link PublicKey} that is the result
+     * of decoding
+     */
+    Class<K> getKeyType();
+
+    /**
+     * @return The {@link Collection} of {@code OpenSSH} key type names that
+     * are supported by this decoder - e.g., ECDSA keys have several curve 
names.
+     * <B>Caveat:</B> this collection may be un-modifiable...
+     */
+    Collection<String> getSupportedTypeNames();
+    
+    /**
+     * @param keyData The key data bytes in {@code OpenSSH} format (after
+     * BASE64 decoding) - ignored if {@code null}/empty
+     * @return The decoded {@link PublicKey} - or {@code null} if no data
+     * @throws IOException If failed to decode the key
+     * @throws GeneralSecurityException If failed to generate the key
+     */
+    K decodePublicKey(byte ... keyData) throws IOException, 
GeneralSecurityException;
+    K decodePublicKey(byte[] keyData, int offset, int length) throws 
IOException, GeneralSecurityException;
+    K decodePublicKey(InputStream keyData) throws IOException, 
GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
new file mode 100644
index 0000000..08d5bc7
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collections;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class RSAPublicKeyDecoder extends 
AbstractPublicKeyEntryDecoder<RSAPublicKey> {
+    public static final RSAPublicKeyDecoder INSTANCE = new 
RSAPublicKeyDecoder();
+
+    public RSAPublicKeyDecoder() {
+        super(RSAPublicKey.class, 
Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+    }
+
+    @Override
+    public RSAPublicKey decodePublicKey(String keyType, InputStream keyData) 
throws IOException, GeneralSecurityException {
+        if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we 
were invoked directly
+            throw new InvalidKeySpecException("Unepected key type: " + 
keyType);
+        }
+
+        BigInteger  e=decodeBigInt(keyData);
+        BigInteger  n=decodeBigInt(keyData);
+
+        return generatePublicKey(new RSAPublicKeySpec(n, e));
+    }
+    
+    @Override
+    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory("RSA");
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java 
b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
index 9503486..0d4b5ef 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
@@ -110,7 +110,7 @@ public enum BuiltinDHFactories implements DHFactory {
             if (!GenericUtils.isEmpty(params)) {
                 throw new IllegalArgumentException("No accepted parameters for 
" + getName());
             }
-            return new ECDH(ECCurves.EllipticCurves.nistp256);
+            return new ECDH(ECCurves.NISTP256);
         }
 
         @Override
@@ -124,7 +124,7 @@ public enum BuiltinDHFactories implements DHFactory {
             if (!GenericUtils.isEmpty(params)) {
                 throw new IllegalArgumentException("No accepted parameters for 
" + getName());
             }
-            return new ECDH(ECCurves.EllipticCurves.nistp384);
+            return new ECDH(ECCurves.NISTP384);
         }
 
         @Override
@@ -138,7 +138,7 @@ public enum BuiltinDHFactories implements DHFactory {
             if (!GenericUtils.isEmpty(params)) {
                 throw new IllegalArgumentException("No accepted parameters for 
" + getName());
             }
-            return new ECDH(ECCurves.EllipticCurves.nistp521);
+            return new ECDH(ECCurves.NISTP521);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java 
b/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
index 08dc695..741f99f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
@@ -51,7 +51,11 @@ public class ECDH extends AbstractDH {
     private KeyAgreement myKeyAgree;
 
     public ECDH() throws Exception {
-        this(null);
+        this((ECParameterSpec) null);
+    }
+
+    public ECDH(String curveName) throws Exception {
+        
this(ValidateUtils.checkNotNull(ECCurves.getECParameterSpec(curveName), 
"Unknown curve name: %s", curveName));
     }
 
     public ECDH(ECParameterSpec paramSpec) throws Exception {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
index 4a5dd6e..03ba5f2 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
@@ -22,9 +22,9 @@ import java.security.KeyPair;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.AbstractLoggingBean;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.KeyUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
index 445fc11..37af11c 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
@@ -21,6 +21,8 @@ package org.apache.sshd.common.keyprovider;
 import java.security.KeyPair;
 import java.util.Collections;
 
+import org.apache.sshd.common.cipher.ECCurves;
+
 /**
  * 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.
@@ -42,17 +44,17 @@ public interface KeyPairProvider {
     /**
      * SSH identifier for EC keys in NIST curve P-256
      */
-    String ECDSA_SHA2_NISTP256 = "ecdsa-sha2-nistp256";
+    String ECDSA_SHA2_NISTP256 = ECCurves.ECDSA_SHA2_PREFIX + 
ECCurves.NISTP256;
 
     /**
      * SSH identifier for EC keys in NIST curve P-384
      */
-    String ECDSA_SHA2_NISTP384 = "ecdsa-sha2-nistp384";
+    String ECDSA_SHA2_NISTP384 = ECCurves.ECDSA_SHA2_PREFIX + 
ECCurves.NISTP384;
 
     /**
      * SSH identifier for EC keys in NIST curve P-521
      */
-    String ECDSA_SHA2_NISTP521 = "ecdsa-sha2-nistp521";
+    String ECDSA_SHA2_NISTP521 = ECCurves.ECDSA_SHA2_PREFIX + 
ECCurves.NISTP521;
 
     /**
      * Load available keys.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java 
b/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
index ffc476c..a7cc8ca 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
@@ -19,6 +19,7 @@
  */
 package org.apache.sshd.common.util;
 
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidParameterException;
 
 /**
@@ -33,6 +34,7 @@ import java.security.InvalidParameterException;
  *  This class was 
  * @author Apache Software Foundation commons codec 
(http://commons.apache.org/codec/)
  * @author <a href="http://mina.apache.org";>Apache MINA Project</a>
+ * TODO replace this class with {@code java.util.Base64} when upgrading to JDK 
1.8 
  */
 public class Base64 {
 
@@ -168,6 +170,10 @@ public class Base64 {
         return true;
     }
 
+    public static String encodeToString(byte ... bytes) {
+        return new String(encodeBase64(bytes), StandardCharsets.UTF_8);
+    }
+
     /**
      * Encodes binary data using the base64 algorithm but
      * does not chunk the output.
@@ -334,6 +340,14 @@ public class Base64 {
         return encodedData;
     }
 
+    public static byte[] decodeString(String s) {
+        if (GenericUtils.isEmpty(s)) {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
+        } else {
+            return decodeBase64(s.getBytes(StandardCharsets.UTF_8));
+        }
+    }
+
     /**
      * Decodes Base64 data into octects
      *
@@ -345,8 +359,8 @@ public class Base64 {
         base64Data = discardNonBase64(base64Data);
 
         // handle the edge case, so we don't have to worry about it later
-        if (base64Data.length == 0) {
-            return new byte[0];
+        if (GenericUtils.isEmpty(base64Data)) {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
         }
 
         int numberQuadruple = base64Data.length / FOURBYTE;
@@ -363,7 +377,7 @@ public class Base64 {
             // ignore the '=' padding
             while (base64Data[lastData - 1] == PAD) {
                 if (--lastData == 0) {
-                    return new byte[0];
+                    return GenericUtils.EMPTY_BYTE_ARRAY;
                 }
             }
             decodedData = new byte[lastData - numberQuadruple];
@@ -437,22 +451,52 @@ public class Base64 {
      * encoded data."
      *
      * @param data The base-64 encoded data to groom
-     * @return The data, less non-base64 characters (see RFC 2045).
+     * @return The data, less non-base64 characters (see RFC 2045) - 
+     * may be same as input if all data was base-64
      */
-    static byte[] discardNonBase64(byte[] data) {
-        byte groomedData[] = new byte[data.length];
+    public static byte[] discardNonBase64(byte[] data) {
+        if (GenericUtils.isEmpty(data)) {
+            return data;
+        }
+
+        byte groomedData[] = null;
         int bytesCopied = 0;
 
         for (int i = 0; i < data.length; i++) {
-            if (isBase64(data[i])) {
-                groomedData[bytesCopied++] = data[i];
+            byte b = data[i];
+
+            if (isBase64(b)) {
+                if (groomedData != null) {
+                    // we had to filter out some non-BASE64 bytes
+                    groomedData[bytesCopied++] = b;
+                }
+            } else {
+                // this means ALL the characters up to this index were BASE64
+                if (groomedData == null) {
+                    groomedData = new byte[data.length - 1 /* the current 
character, which is NOT BASE64 */];
+
+                    if ((bytesCopied=i) > 0) {
+                        System.arraycopy(data, 0, groomedData, 0, bytesCopied);
+                    }
+                }
             }
         }
 
-        byte packedData[] = new byte[bytesCopied];
+        if (groomedData == null) {
+            return data;    // all characters where BASE64
+        }
 
-        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+        if (bytesCopied <= 0) {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
+        }
+
+        // if we were lucky and only ONE character was groomed
+        if (bytesCopied == groomedData.length) {
+            return groomedData;
+        }
 
+        byte packedData[] = new byte[bytesCopied];
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
         return packedData;
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index c75235b..657a6ad 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -289,4 +289,48 @@ public class GenericUtils {
         
         return set;
     }
+    
+    public static final String QUOTES="\"'";
+
+    /**
+     * @param s The {@link CharSequence} to be checked
+     * @return If the sequence contains any of the {@link #QUOTES}
+     * on <U>both</U> ends, then they are stripped, otherwise
+     * nothing is done
+     * @see #stripDelimiters(CharSeqeuence, char)
+     */
+    public static final CharSequence stripQuotes(CharSequence s) {
+        if (isEmpty(s)) {
+            return s;
+        }
+        
+        for (int index=0; index < QUOTES.length(); index++) {
+            char delim = QUOTES.charAt(index);
+            CharSequence v = stripDelimiters(s, delim);
+            if (v != s) {   // if stripped one don't continue
+                return v;
+            }
+        }
+        
+        return s;
+    }
+
+    /**
+     * @param s The {@link CharSequence} to be checked
+     * @param delim The expected delimiter
+     * @return If the sequence contains the delimiter on <U>both</U> ends,
+     * then it is are stripped, otherwise nothing is done
+     */
+    public static final CharSequence stripDelimiters(CharSequence s, char 
delim) {
+        if (isEmpty(s) || (s.length() < 2)) {
+            return s;
+        }
+        
+        int lastPos = s.length() - 1;
+        if ((s.charAt(0) != delim) || (s.charAt(lastPos) != delim)) {
+            return s;
+        } else {
+            return s.subSequence(1, lastPos);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
index 7483542..da17c22 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
@@ -19,6 +19,7 @@
 package org.apache.sshd.common.util;
 
 import java.io.Closeable;
+import java.io.EOFException;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -226,4 +227,65 @@ public class IoUtils {
             return null;
         }
     }
+
+    /**
+     * Read the requested number of bytes or fail if there are not enough left.
+     * @param input where to read input from
+     * @param buffer destination
+     * @throws IOException if there is a problem reading the file
+     * @throws EOFException if the number of bytes read was incorrect
+     */
+    public static void readFully(InputStream input, byte[] buffer) throws 
IOException {
+        readFully(input, buffer, 0, buffer.length);
+    }
+
+    /**
+     * Read the requested number of bytes or fail if there are not enough left.
+     * @param input where to read input from
+     * @param buffer destination
+     * @param offset initial offset into buffer
+     * @param length length to read, must be >= 0
+     * @throws IOException if there is a problem reading the file
+     * @throws EOFException if the number of bytes read was incorrect
+     */
+    public static void readFully(InputStream input, byte[] buffer, int offset, 
int length) throws IOException {
+        int actual = read(input, buffer, offset, length);
+        if (actual != length) {
+            throw new EOFException("Premature EOF - expected=" + length + ", 
actual=" + actual);
+        }
+    }
+
+    /**
+     * Read as many bytes as possible until EOF or achieved required length
+     * @param input where to read input from
+     * @param buffer destination
+     * @return actual length read; may be less than requested if EOF was 
reached
+     * @throws IOException if a read error occurs
+     */
+    public static int read(InputStream input, byte[] buffer) throws 
IOException {
+        return read(input, buffer, 0, buffer.length);
+    }
+
+    /**
+     * Read as many bytes as possible until EOF or achieved required length
+     * @param input where to read input from
+     * @param buffer destination
+     * @param offset initial offset into buffer
+     * @param length length to read - ignored if non-positive
+     * @return actual length read; may be less than requested if EOF was 
reached
+     * @throws IOException if a read error occurs
+     */
+    public static int read(InputStream input, byte[] buffer, int offset, int 
length) throws IOException {
+        for (int remaining = length, curOffset = offset; remaining > 0; ) {
+            int count = input.read(buffer, curOffset, remaining);
+            if (count == (-1)) { // EOF before achieved required length
+                return curOffset - offset;
+            }
+
+            remaining -= count;
+            curOffset += count;
+        }
+
+        return length;
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java
deleted file mode 100644
index 6bea4d3..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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;
-
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.security.interfaces.DSAKey;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.ECParameterSpec;
-
-import org.apache.sshd.common.Digest;
-import org.apache.sshd.common.cipher.ECCurves;
-import org.apache.sshd.common.digest.BuiltinDigests;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-
-/**
- * Utility class for keys
- *
- * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
- */
-public class KeyUtils {
-
-    /**
-     * Retrieve the public key fingerprint
-     *
-     * @param key the public key - ignored if {@code null}
-     * @return the fingerprint or {@code null} if no key
-     */
-    public static String getFingerPrint(PublicKey key) {
-        if (key == null) {
-            return null;
-        }
-
-        try {
-            Buffer buffer = new ByteArrayBuffer();
-            buffer.putRawPublicKey(key);
-            Digest md5 = BuiltinDigests.md5.create();
-            md5.init();
-            md5.update(buffer.array(), 0, buffer.wpos());
-            byte[] data = md5.digest();
-            return BufferUtils.printHex(data, 0, data.length, ':');
-        } catch(Exception e) {
-            return "Unable to compute fingerprint";
-        }
-    }
-
-    /**
-     * Retrieve the key type
-     *
-     * @param kp a key pair
-     * @return the key type
-     */
-    public static String getKeyType(KeyPair kp) {
-        return getKeyType(kp.getPrivate() != null ? kp.getPrivate() : 
kp.getPublic());
-    }
-
-    /**
-     * Retrieve the key type
-     *
-     * @param key a public or private key
-     * @return the key type
-     */
-    public static String getKeyType(Key key) {
-        if (key instanceof DSAKey) {
-            return KeyPairProvider.SSH_DSS;
-        } else if (key instanceof RSAKey) {
-            return KeyPairProvider.SSH_RSA;
-        } else if (key instanceof ECKey) {
-            ECKey ecKey = (ECKey) key;
-            ECParameterSpec ecSpec = ecKey.getParams();
-            return ECCurves.ECDSA_SHA2_PREFIX + ECCurves.getCurveName(ecSpec);
-        }
-        return null;
-    }
-
-    private KeyUtils() {
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
new file mode 100644
index 0000000..83c1656
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
@@ -0,0 +1,169 @@
+/*
+ * 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.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sshd.common.util.AbstractLoggingBean;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Watches over changes for a file and re-loads them if file has changed - 
including
+ * if file is deleted or (re-)created
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class ModifiableFileWatcher extends AbstractLoggingBean {
+    private final Path file;
+    private final AtomicBoolean lastExisted = new AtomicBoolean(false);
+    private final AtomicLong lastSize = new AtomicLong(Long.MIN_VALUE);
+    private final AtomicLong lastModified = new AtomicLong(-1L);
+    protected final LinkOption[] options;
+
+    public ModifiableFileWatcher(File file) {
+        this(ValidateUtils.checkNotNull(file, "No file to watch", 
GenericUtils.EMPTY_OBJECT_ARRAY).toPath());
+    }
+
+    public ModifiableFileWatcher(Path file) {
+        this(file, IoUtils.getLinkOptions(false));
+    }
+
+    public ModifiableFileWatcher(Path file, LinkOption ... options) {
+        this.file = ValidateUtils.checkNotNull(file, "No path to watch", 
GenericUtils.EMPTY_OBJECT_ARRAY);
+        // use a clone to avoid being sensitive to changes in the passed array
+        this.options = (options == null) ? IoUtils.EMPTY_OPTIONS : 
options.clone();
+    }
+    
+    /**
+     * @return The watched {@link Path}
+     */
+    public final Path getPath() {
+        return file;
+    }
+    
+    public final boolean exists() throws IOException {
+        return Files.exists(getPath(), options);
+    }
+
+    public final long size() throws IOException {
+        if (exists()) {
+            return Files.size(getPath());
+        } else {
+            return (-1L);
+        }
+    }
+
+    public final FileTime lastModified() throws IOException {
+        if (exists()) {
+            BasicFileAttributes attrs = Files.readAttributes(getPath(), 
BasicFileAttributes.class, options);
+            return attrs.lastModifiedTime();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return {@code true} if the watched file has probably been changed
+     * @throws IOException If failed to query file data
+     */
+    public boolean checkReloadRequired() throws IOException {
+        boolean exists = exists();
+        // if existence state changed from last time
+        if (exists != lastExisted.getAndSet(exists)) {
+            return true;
+        }
+        
+        if (!exists) {
+            // file did not exist and still does not exist
+            resetReloadAttributes();
+            return false;
+        }
+        
+        long size = size();
+        if (size < 0L) {
+            // means file no longer exists
+            resetReloadAttributes();
+            return true;
+        }
+
+        // if size changed then obviously need reload
+        if (size != lastSize.getAndSet(size)) {
+            return true;
+        }
+
+        FileTime modifiedTime = lastModified();
+        if (modifiedTime == null) {
+            // means file no longer exists
+            resetReloadAttributes();
+            return true;
+        }
+
+        long timestamp = modifiedTime.toMillis();
+        if (timestamp != lastModified.getAndSet(timestamp)) {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Resets the state attributes used to detect changes to the initial
+     * construction values - i.e., file assumed not to exist and no known
+     * size of modify time
+     */
+    public void resetReloadAttributes() {
+        lastExisted.set(false);
+        lastSize.set(Long.MIN_VALUE);
+        lastModified.set(-1L);
+    }
+
+    /**
+     * May be called to refresh the state attributes used to detect changes
+     * e.g., file existence, size and last-modified time once re-loading is
+     * successfully completed. If the file does not exist then the attributes
+     * are reset to an &quot;unknown&quot; state.
+     * @throws IOException If failed to access the file (if exists)
+     * @see #resetReloadAttributes()
+     */
+    public void updateReloadAttributes() throws IOException {
+        if (exists()) {
+            long size = size();
+            FileTime modifiedTime = lastModified();
+
+            if ((size >= 0L) && (modifiedTime != null)) {
+                lastExisted.set(true);
+                lastSize.set(size);
+                lastModified.set(modifiedTime.toMillis());
+                return;
+            }
+        }
+
+        resetReloadAttributes();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java 
b/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
index 78c8adf..d6224fb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
@@ -19,9 +19,12 @@
 package org.apache.sshd.server;
 
 import java.security.PublicKey;
+import java.util.Collection;
+import java.util.Collections;
 
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.AbstractLoggingBean;
-import org.apache.sshd.common.util.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
@@ -42,6 +45,42 @@ public interface PublickeyAuthenticator {
     boolean authenticate(String username, PublicKey key, ServerSession 
session);
 
     /**
+     * Checks against a {@link Collection} of {@link PublicKey}s
+     */
+    public static class KeySetPublickeyAuthenticator extends 
AbstractLoggingBean implements PublickeyAuthenticator {
+        private final Collection<? extends PublicKey> keySet;
+
+        public KeySetPublickeyAuthenticator(Collection<? extends PublicKey> 
keySet) {
+            this.keySet = (keySet == null) ? 
Collections.<PublicKey>emptyList() : keySet;
+        }
+
+        public final Collection<? extends PublicKey> getKeySet() {
+            return keySet;
+        }
+
+        @Override
+        public boolean authenticate(String username, PublicKey key, 
ServerSession session) {
+            return authenticate(username, key, session, getKeySet());
+        }
+        
+        public boolean authenticate(String username, PublicKey key, 
ServerSession session, Collection<? extends PublicKey> keys) {
+            if (GenericUtils.isEmpty(keys)) {
+                if (log.isDebugEnabled()) {
+                    log.debug("authenticate(" + username + ")[" + session + "] 
no keys");
+                }
+                
+                return false;
+            }
+            
+            PublicKey matchKey = KeyUtils.findMatchingKey(key, keys);
+            boolean matchFound = (matchKey != null);
+            if (log.isDebugEnabled()) {
+                log.debug("authenticate(" + username + ")[" + session + "] 
match found=" + matchFound);
+            }
+            return matchFound;
+        }
+    }
+    /**
      * Returns the same constant result {@code true/false} regardless
      */
     public static abstract class StaticPublickeyAuthenticator extends 
AbstractLoggingBean implements PublickeyAuthenticator {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
index d836f4e..0e0880b 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
@@ -28,8 +28,7 @@ import org.apache.sshd.server.PublickeyAuthenticator;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
- * TODO Add javadoc
- *
+ * Caches the result per session
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
  */
 public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, 
SessionListener {

Reply via email to